From cf787a176a8c2bc153bb7c92a4312d6545d4214b Mon Sep 17 00:00:00 2001 From: Puneet Agarwal <28705989+puagarwa@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:02:45 +0530 Subject: [PATCH] feat(playwrighttesting): Add support to publish attachments (#46780) * Add support to upload attachments * fix tests * minor comments fixes --- .../src/Processor/TestProcessor.cs | 89 ++++++++++++++++--- .../src/Utility/ReporterUtils.cs | 10 +++ .../tests/Processor/TestProcessorTests.cs | 2 +- 3 files changed, 90 insertions(+), 11 deletions(-) diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Processor/TestProcessor.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Processor/TestProcessor.cs index db8142ca2a407..d90affe987a6f 100644 --- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Processor/TestProcessor.cs +++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Processor/TestProcessor.cs @@ -34,10 +34,13 @@ internal class TestProcessor : ITestProcessor internal int PassedTestCount { get; set; } = 0; internal int FailedTestCount { get; set; } = 0; internal int SkippedTestCount { get; set; } = 0; + internal int TotalArtifactCount { get; set; } = 0; + internal int TotalArtifactSizeInBytes { get; set; } = 0; internal List TestResults { get; set; } = new List(); internal ConcurrentDictionary RawTestResultsMap { get; set; } = new(); internal bool FatalTestExecution { get; set; } = false; internal TestRunShardDto? _testRunShard; + internal TestResultsUri? _testResultsSasUri; public TestProcessor(CloudRunMetadata cloudRunMetadata, CIInfo cIInfo, ILogger? logger = null, IDataProcessor? dataProcessor = null, ICloudRunErrorParser? cloudRunErrorParser = null, IServiceClient? serviceClient = null, IConsoleWriter? consoleWriter = null) { @@ -93,15 +96,22 @@ public void TestCaseResultHandler(object? sender, TestResultEventArgs e) TestResult testResultSource = e.Result; TestResults? testResult = _dataProcessor.GetTestCaseResultData(testResultSource); RawTestResult rawResult = DataProcessor.GetRawResultObject(testResultSource); - RawTestResultsMap.TryAdd(testResult.TestExecutionId, rawResult); // TODO - Send error to blob _cloudRunErrorParser.HandleScalableRunErrorMessage(testResultSource.ErrorMessage); _cloudRunErrorParser.HandleScalableRunErrorMessage(testResultSource.ErrorStackTrace); - if (!_cloudRunMetadata.EnableResultPublish) + if (!_cloudRunMetadata.EnableResultPublish || FatalTestExecution) { return; } + + // TODO move rawResult upload here same as JS + RawTestResultsMap.TryAdd(testResult.TestExecutionId, rawResult); + + // Upload Attachments + UploadAttachment(e, testResult.TestExecutionId); + + // Update Test Count if (testResult != null) { TotalTestCount++; @@ -143,7 +153,7 @@ public void TestRunCompleteHandler(object? sender, TestRunCompleteEventArgs e) } try { - TestResultsUri? sasUri = _serviceClient.GetTestRunResultsUri(); + TestResultsUri? sasUri = CheckAndRenewSasUri(); if (!string.IsNullOrEmpty(sasUri?.Uri)) { foreach (TestResults testResult in TestResults) @@ -151,12 +161,7 @@ public void TestRunCompleteHandler(object? sender, TestRunCompleteEventArgs e) if (RawTestResultsMap.TryGetValue(testResult.TestExecutionId!, out RawTestResult? rawResult) && rawResult != null) { // Renew the SAS URI if needed - var reporterUtils = new ReporterUtils(); - if (sasUri == null || !reporterUtils.IsTimeGreaterThanCurrentPlus10Minutes(sasUri.Uri)) - { - sasUri = _serviceClient.GetTestRunResultsUri(); // Create new SAS URI - _logger.Info($"Fetched SAS URI with validity: {sasUri?.ExpiresAt} and access: {sasUri?.AccessLevel}."); - } + sasUri = CheckAndRenewSasUri(); if (sasUri == null) { _logger.Warning("SAS URI is empty"); @@ -189,6 +194,60 @@ public void TestRunCompleteHandler(object? sender, TestRunCompleteEventArgs e) } #region Test Processor Helper Methods + + private void UploadAttachment(TestResultEventArgs e, string testExecutionId) + { + _testResultsSasUri = CheckAndRenewSasUri(); + if (e.Result.Attachments != null) + { + foreach (var attachmentSet in e.Result.Attachments) + { + foreach (var attachmentData in attachmentSet.Attachments) + { + var filePath = attachmentData.Uri.LocalPath; + _logger.Info($"Uploading attachment: {filePath}"); + if (!File.Exists( filePath )) + { + _logger.Error($"Attachment file not found: {filePath}"); + continue; + } + try + { + // get file size + var fileSize = new FileInfo(filePath).Length; + var cloudFileName = ReporterUtils.GetCloudFileName(filePath, testExecutionId); + if (cloudFileName != null) { + UploadBlobFile(_testResultsSasUri!.Uri!, cloudFileName, filePath); + TotalArtifactCount++; + TotalArtifactSizeInBytes = TotalArtifactSizeInBytes + (int)fileSize; + } + else + { + _logger.Error($"Attachment file Upload Failed: {filePath}"); + } + } + catch (Exception ex) + { + var error = $"Cannot Upload '{filePath}' file: {ex.Message}"; + + _logger.Error(error); + } + } + } + } + } + + private TestResultsUri? CheckAndRenewSasUri() + { + var reporterUtils = new ReporterUtils(); + if (_testResultsSasUri == null || !reporterUtils.IsTimeGreaterThanCurrentPlus10Minutes(_testResultsSasUri.Uri)) + { + _testResultsSasUri = _serviceClient.GetTestRunResultsUri(); + _logger.Info($"Fetched SAS URI with validity: {_testResultsSasUri?.ExpiresAt} and access: {_testResultsSasUri?.AccessLevel}."); + } + return _testResultsSasUri; + } + private void EndTestRun(TestRunCompleteEventArgs e) { if (_cloudRunMetadata.EnableResultPublish && !FatalTestExecution) @@ -227,6 +286,16 @@ private void UploadBuffer(string uri, string buffer, string fileRelativePath) blobClient.Upload(new BinaryData(bufferBytes), overwrite: true); _logger.Info($"Uploaded buffer to {fileRelativePath}"); } + + private void UploadBlobFile(string uri, string fileRelativePath, string filePath) + { + string cloudFilePath = GetCloudFilePath(uri, fileRelativePath); + BlobClient blobClient = new(new Uri(cloudFilePath)); + // Upload filePath to Blob + blobClient.Upload(filePath, overwrite: true); + _logger.Info($"Uploaded file {filePath} to {fileRelativePath}"); + } + private TestRunShardDto GetTestRunEndShard(TestRunCompleteEventArgs e) { DateTime testRunEndedOn = DateTime.UtcNow; @@ -247,7 +316,7 @@ private TestRunShardDto GetTestRunEndShard(TestRunCompleteEventArgs e) testRunShard.Summary.StartTime = _cloudRunMetadata.TestRunStartTime.ToString("yyyy-MM-ddTHH:mm:ssZ"); testRunShard.Summary.EndTime = testRunEndedOn.ToString("yyyy-MM-ddTHH:mm:ssZ"); testRunShard.Summary.TotalTime = durationInMs; - testRunShard.Summary.UploadMetadata = new UploadMetadata() { NumTestResults = TotalTestCount, NumTotalAttachments = 0, SizeTotalAttachments = 0 }; + testRunShard.Summary.UploadMetadata = new UploadMetadata() { NumTestResults = TotalTestCount, NumTotalAttachments = TotalArtifactCount, SizeTotalAttachments = TotalArtifactSizeInBytes }; testRunShard.UploadCompleted = true; return testRunShard; } diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Utility/ReporterUtils.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Utility/ReporterUtils.cs index 5d7e3f4fc7af5..d17aa019b4881 100644 --- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Utility/ReporterUtils.cs +++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Utility/ReporterUtils.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Linq; using System.Security.Cryptography; using System.Threading.Tasks; @@ -114,6 +115,15 @@ internal static string GetCurrentOS() else return OSConstants.s_wINDOWS; } + internal static string? GetCloudFileName(string filePath, string testExecutionId) + { + var fileName = Path.GetFileName(filePath); + if (fileName == null) + { + return null; + } + return $"{testExecutionId}/{fileName}"; // TODO check if we need to add {Guid.NewGuid()} for file with same name + } internal TokenDetails ParseWorkspaceIdFromAccessToken(JsonWebTokenHandler? jsonWebTokenHandler, string? accessToken) { diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/tests/Processor/TestProcessorTests.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/tests/Processor/TestProcessorTests.cs index 2ebe866cc5f2b..652f73195b3ec 100644 --- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/tests/Processor/TestProcessorTests.cs +++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/tests/Processor/TestProcessorTests.cs @@ -355,7 +355,7 @@ public void TestCaseResultHandler_EnableResultPublishFalse_OnlyParseScalableErro testProcessor.TestCaseResultHandler(sender, new TestResultEventArgs(testResult)); Assert.AreEqual(0, testProcessor.TestResults.Count); - Assert.IsTrue(testProcessor.RawTestResultsMap.Keys.Count == 1); + Assert.IsTrue(testProcessor.RawTestResultsMap.Keys.Count == 0); Assert.IsTrue(testProcessor.PassedTestCount == 0); Assert.IsTrue(testProcessor.FailedTestCount == 0); Assert.IsTrue(testProcessor.SkippedTestCount == 0);