Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(playwrighttesting): separate api, test data processing and utility layers #46681

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,12 @@ internal class ReporterConstants
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 static readonly string s_cONFLICT_409_ERROR_MESSAGE = "Test run with id {runId} already exists. Provide a unique run id.";
internal static readonly string s_cONFLICT_409_ERROR_MESSAGE_KEY = "DuplicateRunId";

internal static readonly string s_fORBIDDEN_403_ERROR_MESSAGE = "Reporting is not enabled for your workspace {workspaceId}. Enable the Reporting feature under Feature management settings using the Playwright portal: https://playwright.microsoft.com/workspaces/{workspaceId}/settings/general";
internal static readonly string s_fORBIDDEN_403_ERROR_MESSAGE_KEY = "ReportingNotEnabled";
internal static readonly string s_uNKNOWN_ERROR_MESSAGE = "Unknown error occured.";
}

internal class CIConstants
Expand Down Expand Up @@ -345,3 +351,54 @@ internal static class TestResultErrorConstants
}
};
}

internal static class ApiErrorConstants
{
private static Dictionary<int, string> PatchTestRun { get; set; } = new Dictionary<int, string>() {
{ 400, "The request made to the server is invalid. Please check the request parameters and try again." },
{ 401, "The authentication token provided is invalid. Please check the token and try again." },
{ 500, "An unexpected error occurred on our server. Our team is working to resolve the issue. Please try again later, or contact support if the problem continues." },
{ 429, "You have exceeded the rate limit for the API. Please wait and try again later." },
{ 504, "The request to the service timed out. Please try again later." },
{ 503, "The service is currently unavailable. Please check the service status and try again." }
};

private static Dictionary<int, string> GetTestRun { get; set; } = new Dictionary<int, string>()
{
{ 400, "The request made to the server is invalid. Please check the request parameters and try again." },
{ 401, "The authentication token provided is invalid. Please check the token and try again." },
{ 403, "You do not have the required permissions to run tests. Please contact your workspace administrator." },
{ 500, "An unexpected error occurred on our server. Our team is working to resolve the issue. Please try again later, or contact support if the problem continues." },
{ 429, "You have exceeded the rate limit for the API. Please wait and try again later." },
{ 504, "The request to the service timed out. Please try again later." },
{ 503, "The service is currently unavailable. Please check the service status and try again." }
};
private static Dictionary<int, string> PatchTestRunShard { get; set; } = new Dictionary<int, string>()
{
{ 400, "The request made to the server is invalid. Please check the request parameters and try again." },
{ 401, "The authentication token provided is invalid. Please check the token and try again." },
{ 403, "You do not have the required permissions to run tests. Please contact your workspace administrator." },
{ 500, "An unexpected error occurred on our server. Our team is working to resolve the issue. Please try again later, or contact support if the problem continues." },
{ 429, "You have exceeded the rate limit for the API. Please wait and try again later." },
{ 504, "The request to the service timed out. Please try again later." },
{ 503, "The service is currently unavailable. Please check the service status and try again." }
};
private static Dictionary<int, string> GetStorageUri { get; set; } = new Dictionary<int, string>()
{
{ 400, "The request made to the server is invalid. Please check the request parameters and try again." },
{ 401, "The authentication token provided is invalid. Please check the token and try again." },
{ 403, "You do not have the required permissions to run tests. Please contact your workspace administrator." },
{ 500, "An unexpected error occurred on our server. Our team is working to resolve the issue. Please try again later, or contact support if the problem continues." },
{ 429, "You have exceeded the rate limit for the API. Please wait and try again later." },
{ 504, "The request to the service timed out. Please try again later." },
{ 503, "The service is currently unavailable. Please check the service status and try again." }
};

internal static readonly Dictionary<string, Dictionary<int, string>> s_errorOperationPair = new()
{
{ "PatchTestRun", PatchTestRun },
{ "GetTestRun", GetTestRun },
{ "PatchTestRunShard", PatchTestRunShard },
{ "GetStorageUri", GetStorageUri }
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public void DisplayMessages()
}
}

public void PrintErrorToConsole(string message)
{
_consoleWriter.WriteError(message);
}

public void HandleScalableRunErrorMessage(string? message)
{
if (string.IsNullOrEmpty(message))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,17 @@ public void WriteLine(string? message = null)
Console.WriteLine(message);
}
}

public void WriteError(string? message = null)
Sid200026 marked this conversation as resolved.
Show resolved Hide resolved
{
if (message == null)
{
Console.Error.WriteLine();
}
else
{
Console.Error.WriteLine(message);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Text.Json;
using Azure.Core.Diagnostics;
using System.Diagnostics.Tracing;
using System.Net;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation
{
Expand All @@ -17,20 +18,23 @@ internal class ServiceClient : IServiceClient
private readonly ReportingTestResultsClient _reportingTestResultsClient;
private readonly ReportingTestRunsClient _reportingTestRunsClient;
private readonly CloudRunMetadata _cloudRunMetadata;
private readonly ICloudRunErrorParser _cloudRunErrorParser;
private readonly ILogger _logger;
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, ILogger? logger = null)
public ServiceClient(CloudRunMetadata cloudRunMetadata, ICloudRunErrorParser cloudRunErrorParser, ReportingTestResultsClient? reportingTestResultsClient = null, ReportingTestRunsClient? reportingTestRunsClient = null, ILogger? logger = null)
{
_cloudRunMetadata = cloudRunMetadata;
_cloudRunErrorParser = cloudRunErrorParser;
_logger = logger ?? new Logger();
AzureEventSourceListener listener = new(delegate (EventWrittenEventArgs eventData, string text)
{
_logger.Info($"[{eventData.Level}] {eventData.EventSource.Name}: {text}");
}, EventLevel.Informational);
var clientOptions = new TestReportingClientOptions();
clientOptions.Diagnostics.IsLoggingEnabled = true;
clientOptions.Diagnostics.IsLoggingContentEnabled = false;
clientOptions.Diagnostics.IsTelemetryEnabled = true;
clientOptions.Retry.MaxRetries = ServiceClientConstants.s_mAX_RETRIES;
clientOptions.Retry.MaxDelay = TimeSpan.FromSeconds(ServiceClientConstants.s_mAX_RETRY_DELAY_IN_SECONDS);
Expand All @@ -40,37 +44,108 @@ public ServiceClient(CloudRunMetadata cloudRunMetadata, ReportingTestResultsClie

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)
int statusCode;
try
{
return apiResponse.Content!.ToObject<TestRunDtoV2>(new JsonObjectSerializer());
Response? apiResponse = _reportingTestRunsClient.PatchTestRunInfo(_cloudRunMetadata.WorkspaceId!, _cloudRunMetadata.RunId!, RequestContent.Create(run), ReporterConstants.s_aPPLICATION_JSON, AccessToken, CorrelationId);
if (apiResponse.Status == (int)HttpStatusCode.OK)
{
return apiResponse.Content!.ToObject<TestRunDtoV2>(new JsonObjectSerializer());
}
statusCode = apiResponse.Status;
}
catch (RequestFailedException ex)
{
if (ex.Status == (int)HttpStatusCode.Conflict)
{
var errorMessage = ReporterConstants.s_cONFLICT_409_ERROR_MESSAGE.Replace("{runId}", _cloudRunMetadata.RunId!);
_cloudRunErrorParser.PrintErrorToConsole(errorMessage);
_cloudRunErrorParser.TryPushMessageAndKey(errorMessage, ReporterConstants.s_cONFLICT_409_ERROR_MESSAGE_KEY);
throw new Exception(errorMessage);
}
else if (ex.Status == (int)HttpStatusCode.Forbidden)
{
var errorMessage = ReporterConstants.s_fORBIDDEN_403_ERROR_MESSAGE.Replace("{workspaceId}", _cloudRunMetadata.WorkspaceId!);
_cloudRunErrorParser.PrintErrorToConsole(errorMessage);
_cloudRunErrorParser.TryPushMessageAndKey(errorMessage, ReporterConstants.s_fORBIDDEN_403_ERROR_MESSAGE_KEY);
throw new Exception(errorMessage);
}
statusCode = ex.Status;
}
HandleAPIFailure(statusCode, "PatchTestRun");
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)
int statusCode;
try
{
Response apiResponse = _reportingTestRunsClient.PatchTestRunShardInfo(_cloudRunMetadata.WorkspaceId!, _cloudRunMetadata.RunId!, shardId.ToString(), RequestContent.Create(runShard), ReporterConstants.s_aPPLICATION_JSON, AccessToken, CorrelationId);
if (apiResponse.Status == (int)HttpStatusCode.OK)
{
return apiResponse.Content!.ToObject<TestRunShardDto>(new JsonObjectSerializer());
}
statusCode = apiResponse.Status;
}
catch (RequestFailedException ex)
{
return apiResponse.Content!.ToObject<TestRunShardDto>(new JsonObjectSerializer());
statusCode = ex.Status;
}
HandleAPIFailure(statusCode, "PatchTestRunShard");
return null;
}

public void UploadBatchTestResults(UploadTestResultsRequest uploadTestResultsRequest)
{
_reportingTestResultsClient.UploadBatchTestResults(_cloudRunMetadata.WorkspaceId!, RequestContent.Create(JsonSerializer.Serialize(uploadTestResultsRequest)), AccessToken, CorrelationId, null);
int statusCode;
try
{
Response apiResponse = _reportingTestResultsClient.UploadBatchTestResults(_cloudRunMetadata.WorkspaceId!, RequestContent.Create(JsonSerializer.Serialize(uploadTestResultsRequest)), AccessToken, CorrelationId, null);
if (apiResponse.Status == (int)HttpStatusCode.OK)
{
return;
}
statusCode = apiResponse.Status;
}
catch (RequestFailedException ex)
{
statusCode = ex.Status;
}
HandleAPIFailure(statusCode, "PostTestResults");
}

public TestResultsUri? GetTestRunResultsUri()
{
Response response = _reportingTestRunsClient.GetTestRunResultsUri(_cloudRunMetadata.WorkspaceId!, _cloudRunMetadata.RunId!, AccessToken, CorrelationId, null);
if (response.Content != null)
int statusCode;
try
{
Response response = _reportingTestRunsClient.GetTestRunResultsUri(_cloudRunMetadata.WorkspaceId!, _cloudRunMetadata.RunId!, AccessToken, CorrelationId, null);
if (response.Status == (int)HttpStatusCode.OK)
{
return response.Content!.ToObject<TestResultsUri>(new JsonObjectSerializer());
}
statusCode = response.Status;
}
catch (RequestFailedException ex)
{
return response.Content!.ToObject<TestResultsUri>(new JsonObjectSerializer());
statusCode = ex.Status;
}
HandleAPIFailure(statusCode, "GetStorageUri");
return null;
}

private void HandleAPIFailure(int? statusCode, string operationName)
{
if (statusCode == null)
return;
ApiErrorConstants.s_errorOperationPair.TryGetValue(operationName, out System.Collections.Generic.Dictionary<int, string>? errorObject);
if (errorObject == null)
return;
errorObject.TryGetValue((int)statusCode, out string? errorMessage);
if (errorMessage == null)
errorMessage = ReporterConstants.s_uNKNOWN_ERROR_MESSAGE;
_cloudRunErrorParser.TryPushMessageAndKey(errorMessage, operationName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ internal interface ICloudRunErrorParser
bool TryPushMessageAndKey(string? message, string? key);
void PushMessage(string message);
void DisplayMessages();
void PrintErrorToConsole(string message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
internal interface IConsoleWriter
{
void WriteLine(string? message = null);
void WriteError(string? message = null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public TestProcessor(CloudRunMetadata cloudRunMetadata, CIInfo cIInfo, ILogger?
_logger = logger ?? new Logger();
_dataProcessor = dataProcessor ?? new DataProcessor(_cloudRunMetadata, _cIInfo, _logger);
_cloudRunErrorParser = cloudRunErrorParser ?? new CloudRunErrorParser(_logger);
_serviceClient = serviceClient ?? new ServiceClient(_cloudRunMetadata);
_serviceClient = serviceClient ?? new ServiceClient(_cloudRunMetadata, _cloudRunErrorParser);
_consoleWriter = consoleWriter ?? new ConsoleWriter();
}

Expand Down Expand Up @@ -89,10 +89,6 @@ public void TestCaseResultHandler(object? sender, TestResultEventArgs e)
{
try
{
if (FatalTestExecution)
{
return;
}
TestResult testResultSource = e.Result;
TestResults? testResult = _dataProcessor.GetTestCaseResultData(testResultSource);
RawTestResult rawResult = DataProcessor.GetRawResultObject(testResultSource);
Expand Down