Skip to content

Commit

Permalink
feat(playwrighttesting): separate api, data processing and utility la…
Browse files Browse the repository at this point in the history
…yers
  • Loading branch information
Siddharth Singha Roy committed Oct 16, 2024
1 parent 59f2ec4 commit 7345746
Show file tree
Hide file tree
Showing 19 changed files with 1,034 additions and 824 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Utility;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger;

/// <summary>
Expand Down Expand Up @@ -183,6 +187,7 @@ internal class Constants
// Default constants
internal static readonly string s_default_os = ServiceOs.Linux;
internal static readonly string s_default_expose_network = "<loopback>";
internal static readonly string s_pLAYWRIGHT_SERVICE_DEBUG = "PLAYWRIGHT_SERVICE_DEBUG";

// Entra id access token constants
internal static readonly int s_entra_access_token_lifetime_left_threshold_in_minutes_for_rotation = 15;
Expand All @@ -204,3 +209,119 @@ internal class Constants
internal static readonly string s_playwright_service_reporting_url_environment_variable = "PLAYWRIGHT_SERVICE_REPORTING_URL";
internal static readonly string s_playwright_service_workspace_id_environment_variable = "PLAYWRIGHT_SERVICE_WORKSPACE_ID";
}

internal class OSConstants
{
internal static readonly string s_lINUX = "Linux";
internal static readonly string s_wINDOWS = "Windows";
internal static readonly string s_mACOS = "MacOS";
}

internal class ReporterConstants
{
internal static readonly string s_executionIdPropertyIdentifier = "ExecutionId";
internal static readonly string s_parentExecutionIdPropertyIdentifier = "ParentExecId";
internal static readonly string s_testTypePropertyIdentifier = "TestType";
internal static readonly string s_sASUriSeparator = "?";
internal static readonly string s_portalBaseUrl = "https://playwright.microsoft.com/workspaces/";
internal static readonly string s_reportingRoute = "/runs/";
internal static readonly string s_reportingAPIVersion_2024_04_30_preview = "2024-04-30-preview";
internal static readonly string s_reportingAPIVersion_2024_05_20_preview = "2024-05-20-preview";
internal static readonly string s_pLAYWRIGHT_SERVICE_REPORTING_URL = "PLAYWRIGHT_SERVICE_REPORTING_URL";
internal static readonly string s_pLAYWRIGHT_SERVICE_WORKSPACE_ID = "PLAYWRIGHT_SERVICE_WORKSPACE_ID";
internal static readonly string s_aPPLICATION_JSON = "application/json";
}

internal class CIConstants
{
internal static readonly string s_gITHUB_ACTIONS = "GitHub Actions";
internal static readonly string s_aZURE_DEVOPS = "Azure DevOps";
internal static readonly string s_dEFAULT = "Default";
}

internal class TestCaseResultStatus
{
internal static readonly string s_pASSED = "passed";
internal static readonly string s_fAILED = "failed";
internal static readonly string s_sKIPPED = "skipped";
internal static readonly string s_iNCONCLUSIVE = "inconclusive";
}

internal class TestResultError
{
internal string? Key { get; set; } = string.Empty;
internal string? Message { get; set; } = string.Empty;
internal Regex Pattern { get; set; } = new Regex(string.Empty);
internal TestErrorType Type { get; set; }
}

internal enum TestErrorType
{
Scalable
}

internal static class TestResultErrorConstants
{
public static List<TestResultError> ErrorConstants = new()
{
new TestResultError
{
Key = "Unauthorized_Scalable",
Message = "The authentication token provided is invalid. Please check the token and try again.",
Pattern = new Regex(@"(?=.*Microsoft\.Playwright\.PlaywrightException)(?=.*401 Unauthorized)", RegexOptions.IgnoreCase),
Type = TestErrorType.Scalable
},
new TestResultError
{
Key = "NoPermissionOnWorkspace_Scalable",
Message = @"You do not have the required permissions to run tests. This could be because:
a. You do not have the required roles on the workspace. Only Owner and Contributor roles can run tests. Contact the service administrator.
b. The workspace you are trying to run the tests on is in a different Azure tenant than what you are signed into. Check the tenant id from Azure portal and login using the command 'az login --tenant <TENANT_ID>'.",
Pattern = new Regex(@"(?=.*Microsoft\.Playwright\.PlaywrightException)(?=.*403 Forbidden)(?=[\s\S]*CheckAccess API call with non successful response)", RegexOptions.IgnoreCase),
Type = TestErrorType.Scalable
},
new TestResultError
{
Key = "InvalidWorkspace_Scalable",
Message = "The specified workspace does not exist. Please verify your workspace settings.",
Pattern = new Regex(@"(?=.*Microsoft\.Playwright\.PlaywrightException)(?=.*403 Forbidden)(?=.*InvalidAccountOrSubscriptionState)", RegexOptions.IgnoreCase),
Type = TestErrorType.Scalable
},
new TestResultError
{
Key = "AccessKeyBasedAuthNotSupported_Scalable",
Message = "Authentication through service access token is disabled for this workspace. Please use Entra ID to authenticate.",
Pattern = new Regex(@"(?=.*Microsoft\.Playwright\.PlaywrightException)(?=.*403 Forbidden)(?=.*AccessKeyBasedAuthNotSupported)", RegexOptions.IgnoreCase),
Type = TestErrorType.Scalable
},
new TestResultError
{
Key = "ServiceUnavailable_Scalable",
Message = "The service is currently unavailable. Please check the service status and try again.",
Pattern = new Regex(@"(?=.*Microsoft\.Playwright\.PlaywrightException)(?=.*503 Service Unavailable)", RegexOptions.IgnoreCase),
Type = TestErrorType.Scalable
},
new TestResultError
{
Key = "GatewayTimeout_Scalable",
Message = "The request to the service timed out. Please try again later.",
Pattern = new Regex(@"(?=.*Microsoft\.Playwright\.PlaywrightException)(?=.*504 Gateway Timeout)", RegexOptions.IgnoreCase),
Type = TestErrorType.Scalable
},
new TestResultError
{
Key = "QuotaLimitError_Scalable",
Message = "It is possible that the maximum number of concurrent sessions allowed for your workspace has been exceeded.",
Pattern = new Regex(@"(Timeout .* exceeded)(?=[\s\S]*ws connecting)", RegexOptions.IgnoreCase),
Type = TestErrorType.Scalable
},
new TestResultError
{
Key = "BrowserConnectionError_Scalable",
Message = "The service is currently unavailable. Please try again after some time.",
Pattern = new Regex(@"Target page, context or browser has been closed", RegexOptions.IgnoreCase),
Type = TestErrorType.Scalable
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation
{
internal class CloudRunErrorParser : ICloudRunErrorParser
{
private List<string> InformationalMessages { get; set; } = new();
private List<string> ProcessedErrorMessageKeys { get; set; } = new();
private readonly ILogger _logger;
private readonly IConsoleWriter _consoleWriter;
public CloudRunErrorParser(ILogger? logger = null, IConsoleWriter? consoleWriter = null)
{
_logger = logger ?? new Logger();
_consoleWriter = consoleWriter ?? new ConsoleWriter();
}

public bool TryPushMessageAndKey(string? message, string? key)
{
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(message))
{
return false;
}
if (ProcessedErrorMessageKeys.Contains(key!))
{
return false;
}
_logger.Info($"Adding message with key: {key}");

ProcessedErrorMessageKeys.Add(key!);
InformationalMessages.Add(message!);
return true;
}

public void PushMessage(string message)
{
InformationalMessages.Add(message);
}

public void DisplayMessages()
{
if (InformationalMessages.Count > 0)
_consoleWriter.WriteLine();
int index = 1;
foreach (string message in InformationalMessages)
{
_consoleWriter.WriteLine($"{index++}) {message}");
}
}

public void HandleScalableRunErrorMessage(string? message)
{
if (string.IsNullOrEmpty(message))
{
return;
}
foreach (TestResultError testResultErrorObj in TestResultErrorConstants.ErrorConstants)
{
if (testResultErrorObj.Pattern.IsMatch(message))
{
TryPushMessageAndKey(testResultErrorObj.Message, testResultErrorObj.Key);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation
{
internal class ConsoleWriter : IConsoleWriter
{
public void WriteLine(string? message = null)
{
if (message == null)
{
Console.WriteLine();
}
else
{
Console.WriteLine(message);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation;

internal enum LogLevel
{
Debug,
Info,
Warning,
Error
}

internal class Logger : ILogger
{
internal static bool EnableDebug { get { return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(Constants.s_pLAYWRIGHT_SERVICE_DEBUG)); } set { } }

#pragma warning disable CA1822 // Mark members as static
private void Log(LogLevel level, string message)
#pragma warning restore CA1822 // Mark members as static
{
if (EnableDebug)
{
Console.WriteLine($"{DateTime.Now} [{level}]: {message}");
}
}

public void Debug(string message)
{
Log(LogLevel.Debug, message);
}

public void Error(string message)
{
Log(LogLevel.Error, message);
}

public void Info(string message)
{
Log(LogLevel.Info, message);
}

public void Warning(string message)
{
Log(LogLevel.Warning, message);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using Azure.Core.Serialization;
using Azure.Core;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Client;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Model;
using System.Text.Json;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation
{
internal class ServiceClient : IServiceClient
{
private readonly ReportingTestResultsClient _reportingTestResultsClient;
private readonly ReportingTestRunsClient _reportingTestRunsClient;
private readonly CloudRunMetadata _cloudRunMetadata;
private static string AccessToken { get => $"Bearer {Environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken)}"; set { } }
private static string CorrelationId { get => Guid.NewGuid().ToString(); set { } }

public ServiceClient(CloudRunMetadata cloudRunMetadata, ReportingTestResultsClient? reportingTestResultsClient = null, ReportingTestRunsClient? reportingTestRunsClient = null)
{
_cloudRunMetadata = cloudRunMetadata;
_reportingTestResultsClient = reportingTestResultsClient ?? new ReportingTestResultsClient(_cloudRunMetadata.BaseUri);
_reportingTestRunsClient = reportingTestRunsClient ?? new ReportingTestRunsClient(_cloudRunMetadata.BaseUri);
}

public TestRunDtoV2? PatchTestRunInfo(TestRunDtoV2 run)
{
Response apiResponse = _reportingTestRunsClient.PatchTestRunInfo(_cloudRunMetadata.WorkspaceId!, _cloudRunMetadata.RunId!, RequestContent.Create(run), ReporterConstants.s_aPPLICATION_JSON, AccessToken, CorrelationId);
if (apiResponse.Content != null)
{
return apiResponse.Content!.ToObject<TestRunDtoV2>(new JsonObjectSerializer());
}
return null;
}

public TestRunShardDto? PatchTestRunShardInfo(int shardId, TestRunShardDto runShard)
{
Response apiResponse = _reportingTestRunsClient.PatchTestRunShardInfo(_cloudRunMetadata.WorkspaceId!, _cloudRunMetadata.RunId!, shardId.ToString(), RequestContent.Create(runShard), ReporterConstants.s_aPPLICATION_JSON, AccessToken, CorrelationId);
if (apiResponse.Content != null)
{
return apiResponse.Content!.ToObject<TestRunShardDto>(new JsonObjectSerializer());
}
return null;
}

public void UploadBatchTestResults(UploadTestResultsRequest uploadTestResultsRequest)
{
_reportingTestResultsClient.UploadBatchTestResults(_cloudRunMetadata.WorkspaceId!, RequestContent.Create(JsonSerializer.Serialize(uploadTestResultsRequest)), AccessToken, CorrelationId, null);
}

public TestResultsUri? GetTestRunResultsUri()
{
Response response = _reportingTestRunsClient.GetTestRunResultsUri(_cloudRunMetadata.WorkspaceId!, _cloudRunMetadata.RunId!, AccessToken, CorrelationId, null);
if (response.Content != null)
{
return response.Content!.ToObject<TestResultsUri>(new JsonObjectSerializer());
}
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
{
internal interface ICloudRunErrorParser
{
void HandleScalableRunErrorMessage(string? message);
bool TryPushMessageAndKey(string? message, string? key);
void PushMessage(string message);
void DisplayMessages();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
{
internal interface IConsoleWriter
{
void WriteLine(string? message = null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Model;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
{
internal interface IDataProcessor
{
TestRunDtoV2 GetTestRun();
TestRunShardDto GetTestRunShard();
TestResults GetTestCaseResultData(TestResult testResultSource);
}
}
Loading

0 comments on commit 7345746

Please sign in to comment.