Skip to content

Commit

Permalink
chore(playwrighttesting): populate NumberOfTestWorkers from runsettin…
Browse files Browse the repository at this point in the history
…gs and refactor PlaywrightReporter (#47066)

* chore(playwrighttesting): populate NumberOfTestWorkers from runsettings and refactor PlaywrightReporter

* chore(): update api spec

---------

Co-authored-by: Siddharth Singha Roy <ssingharoy@microsoft.com>
  • Loading branch information
Sid200026 and Siddharth Singha Roy authored Nov 12, 2024
1 parent e666aab commit 2a85f03
Show file tree
Hide file tree
Showing 16 changed files with 542 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public partial class RunSettingKey
public static readonly string EnableResultPublish;
public static readonly string ExposeNetwork;
public static readonly string ManagedIdentityClientId;
public static readonly string NumberOfTestWorkers;
public static readonly string Os;
public static readonly string RunId;
public static readonly string ServiceAuthType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ public class RunSettingKey
/// Enable Result publish.
/// </summary>
public static readonly string EnableResultPublish = "EnableResultPublish";

/// <summary>
/// Number of NUnit test workers.
/// </summary>
public static readonly string NumberOfTestWorkers = "NumberOfTestWorkers";
}

internal class Constants
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 EnvironmentHandler : IEnvironment
{
public void Exit(int exitCode)
{
Environment.Exit(exitCode);
}

public string GetEnvironmentVariable(string variable)
{
return Environment.GetEnvironmentVariable(variable);
}

public void SetEnvironmentVariable(string variable, string value)
{
Environment.SetEnvironmentVariable(variable, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Xml;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation
{
internal class XmlRunSettings : IXmlRunSettings
{
private static readonly string NUnitNodeName = "NUnit";
public Dictionary<string, object> GetTestRunParameters(string? settingsXml)
{
return XmlRunSettingsUtilities.GetTestRunParameters(settingsXml);
}

public Dictionary<string, object> GetNUnitParameters(string? settingsXml)
{
try
{
var parameters = new Dictionary<string, object>();
XmlDocument xmlDocument = ParseXmlSettings(settingsXml);
XmlNodeList nUnitNodes = xmlDocument.GetElementsByTagName(NUnitNodeName);
foreach (XmlNode nUnitNode in nUnitNodes)
{
foreach (XmlNode childNode in nUnitNode.ChildNodes)
{
parameters.Add(childNode.Name, childNode.InnerText);
}
}
return parameters;
}
catch (Exception)
{
return new Dictionary<string, object>();
}
}

private static XmlDocument ParseXmlSettings(string? settingsXml)
{
XmlDocument xmlDocument = new();
xmlDocument.LoadXml(settingsXml);
return xmlDocument;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
{
internal interface IEnvironment
{
void Exit(int exitCode);
string GetEnvironmentVariable(string variable);
void SetEnvironmentVariable(string variable, string value);
}
}
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.

using System.Collections.Generic;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
{
internal interface IXmlRunSettings
{
Dictionary<string, object> GetTestRunParameters(string? settingsXml);
Dictionary<string, object> GetNUnitParameters(string? settingsXml);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ internal string? PortalUrl
internal bool EnableGithubSummary { get; set; } = true;
internal DateTime TestRunStartTime { get; set; }
internal TokenDetails? AccessTokenDetails { get; set; }
internal int NumberOfTestWorkers { get; set; } = 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,32 @@
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Processor;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger;

[FriendlyName("microsoft-playwright-testing")]
[ExtensionUri("logger://MicrosoftPlaywrightTesting/Logger/v1")]
internal class PlaywrightReporter : ITestLoggerWithParameters
{
private Dictionary<string, string?>? _parametersDictionary;
private PlaywrightService? _playwrightService;
private readonly ILogger _logger;
private TestProcessor? _testProcessor;

public PlaywrightReporter() : this(null) { } // no-op
public PlaywrightReporter(ILogger? logger)
internal Dictionary<string, string?>? _parametersDictionary;
internal PlaywrightService? _playwrightService;
internal TestProcessor? _testProcessor;
internal readonly ILogger _logger;
internal IEnvironment _environment;
internal IXmlRunSettings _xmlRunSettings;
internal IConsoleWriter _consoleWriter;
internal JsonWebTokenHandler _jsonWebTokenHandler;

public PlaywrightReporter() : this(null, null, null, null, null) { } // no-op
public PlaywrightReporter(ILogger? logger, IEnvironment? environment, IXmlRunSettings? xmlRunSettings, IConsoleWriter? consoleWriter, JsonWebTokenHandler? jsonWebTokenHandler)
{
_logger = logger ?? new Logger();
_environment = environment ?? new EnvironmentHandler();
_xmlRunSettings = xmlRunSettings ?? new XmlRunSettings();
_consoleWriter = consoleWriter ?? new ConsoleWriter();
_jsonWebTokenHandler = jsonWebTokenHandler ?? new JsonWebTokenHandler();
}

public void Initialize(TestLoggerEvents events, Dictionary<string, string?> parameters)
Expand Down Expand Up @@ -66,18 +76,19 @@ internal void TestRunCompleteHandler(object? sender, TestRunCompleteEventArgs e)
}
#endregion

private void InitializePlaywrightReporter(string xmlSettings)
internal void InitializePlaywrightReporter(string xmlSettings)
{
Dictionary<string, object> runParameters = XmlRunSettingsUtilities.GetTestRunParameters(xmlSettings);
Dictionary<string, object> runParameters = _xmlRunSettings.GetTestRunParameters(xmlSettings);
Dictionary<string, object> nunitParameters = _xmlRunSettings.GetNUnitParameters(xmlSettings);
runParameters.TryGetValue(RunSettingKey.RunId, out var runId);
// If run id is not provided and not set via env, try fetching it from CI info.
CIInfo cIInfo = CiInfoProvider.GetCIInfo();
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId)))
CIInfo cIInfo = CiInfoProvider.GetCIInfo(_environment);
if (string.IsNullOrEmpty(_environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId)))
{
if (string.IsNullOrEmpty(runId?.ToString()))
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId, ReporterUtils.GetRunId(cIInfo));
_environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId, ReporterUtils.GetRunId(cIInfo));
else
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId, runId!.ToString());
_environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId, runId!.ToString());
}
else
{
Expand All @@ -89,46 +100,49 @@ private void InitializePlaywrightReporter(string xmlSettings)
runParameters.TryGetValue(RunSettingKey.ManagedIdentityClientId, out var managedIdentityClientId);
runParameters.TryGetValue(RunSettingKey.EnableGitHubSummary, out var enableGithubSummary);
runParameters.TryGetValue(RunSettingKey.EnableResultPublish, out var enableResultPublish);
nunitParameters.TryGetValue(RunSettingKey.NumberOfTestWorkers, out var numberOfTestWorkers);
string? enableGithubSummaryString = enableGithubSummary?.ToString();
string? enableResultPublishString = enableResultPublish?.ToString();

bool _enableGitHubSummary = string.IsNullOrEmpty(enableGithubSummaryString) || bool.Parse(enableGithubSummaryString!);
bool _enableResultPublish = string.IsNullOrEmpty(enableResultPublishString) || bool.Parse(enableResultPublishString!);

PlaywrightServiceOptions? playwrightServiceSettings = null;
PlaywrightServiceOptions? playwrightServiceSettings;
try
{
playwrightServiceSettings = new(runId: runId?.ToString(), serviceAuth: serviceAuth?.ToString(), azureTokenCredentialType: azureTokenCredential?.ToString(), managedIdentityClientId: managedIdentityClientId?.ToString());
}
catch (Exception ex)
{
Console.Error.WriteLine("Failed to initialize PlaywrightServiceSettings: " + ex);
Environment.Exit(1);
_consoleWriter.WriteError("Failed to initialize PlaywrightServiceSettings: " + ex);
_environment.Exit(1);
return;
}

// setup entra rotation handlers
_playwrightService = new PlaywrightService(null, playwrightServiceSettings!.RunId, null, playwrightServiceSettings.ServiceAuth, null, playwrightServiceSettings.AzureTokenCredential);
_playwrightService = new PlaywrightService(null, playwrightServiceSettings!.RunId, null, playwrightServiceSettings.ServiceAuth, null, entraLifecycle: null, jsonWebTokenHandler: _jsonWebTokenHandler, credential: playwrightServiceSettings.AzureTokenCredential);
#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.
_playwrightService.InitializeAsync().GetAwaiter().GetResult();
#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.

var cloudRunId = Environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId);
string baseUrl = Environment.GetEnvironmentVariable(ReporterConstants.s_pLAYWRIGHT_SERVICE_REPORTING_URL);
string accessToken = Environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken);
var cloudRunId = _environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId);
string baseUrl = _environment.GetEnvironmentVariable(ReporterConstants.s_pLAYWRIGHT_SERVICE_REPORTING_URL);
string accessToken = _environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken);
if (string.IsNullOrEmpty(baseUrl))
{
Console.Error.WriteLine(Constants.s_no_service_endpoint_error_message);
Environment.Exit(1);
_consoleWriter.WriteError(Constants.s_no_service_endpoint_error_message);
_environment.Exit(1);
return;
}
if (string.IsNullOrEmpty(accessToken))
{
Console.Error.WriteLine(Constants.s_no_auth_error);
Environment.Exit(1);
_consoleWriter.WriteError(Constants.s_no_auth_error);
_environment.Exit(1);
return;
}

var baseUri = new Uri(baseUrl);
var reporterUtils = new ReporterUtils();
TokenDetails tokenDetails = reporterUtils.ParseWorkspaceIdFromAccessToken(jsonWebTokenHandler: null, accessToken: accessToken);
TokenDetails tokenDetails = reporterUtils.ParseWorkspaceIdFromAccessToken(jsonWebTokenHandler: _jsonWebTokenHandler, accessToken: accessToken);
var workspaceId = tokenDetails.aid;

var cloudRunMetadata = new CloudRunMetadata
Expand All @@ -140,6 +154,7 @@ private void InitializePlaywrightReporter(string xmlSettings)
EnableGithubSummary = _enableGitHubSummary,
TestRunStartTime = DateTime.UtcNow,
AccessTokenDetails = tokenDetails,
NumberOfTestWorkers = numberOfTestWorkers != null ? Convert.ToInt32(numberOfTestWorkers) : 1
};

_testProcessor = new TestProcessor(cloudRunMetadata, cIInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ internal PlaywrightService(OSPlatform? os = null, string? runId = null, string?
{
if (string.IsNullOrEmpty(ServiceEndpoint))
return;
_entraLifecycle = entraLifecycle ?? new EntraLifecycle(credential);
_jsonWebTokenHandler = jsonWebTokenHandler ?? new JsonWebTokenHandler();
_entraLifecycle = entraLifecycle ?? new EntraLifecycle(credential, _jsonWebTokenHandler);
InitializePlaywrightServiceEnvironmentVariables(getServiceCompatibleOs(os), runId, exposeNetwork, serviceAuth, useCloudHostedBrowsers);
}

Expand Down Expand Up @@ -248,12 +248,12 @@ private void ValidateMptPAT()
if (string.IsNullOrEmpty(authToken))
throw new Exception(Constants.s_no_auth_error);
JsonWebToken jsonWebToken = _jsonWebTokenHandler!.ReadJsonWebToken(authToken) ?? throw new Exception(Constants.s_invalid_mpt_pat_error);
var tokenaWorkspaceId = jsonWebToken.Claims.FirstOrDefault(c => c.Type == "aid")?.Value;
var tokenWorkspaceId = jsonWebToken.Claims.FirstOrDefault(c => c.Type == "aid")?.Value;
Match match = Regex.Match(ServiceEndpoint, @"wss://(?<region>[\w-]+)\.api\.(?<domain>playwright(?:-test|-int)?\.io|playwright\.microsoft\.com)/accounts/(?<workspaceId>[\w-]+)/");
if (!match.Success)
throw new Exception(Constants.s_invalid_service_endpoint_error_message);
var serviceEndpointWorkspaceId = match.Groups["workspaceId"].Value;
if (tokenaWorkspaceId != serviceEndpointWorkspaceId)
if (tokenWorkspaceId != serviceEndpointWorkspaceId)
throw new Exception(Constants.s_workspace_mismatch_error);
var expiry = (long)(jsonWebToken.ValidTo - new DateTime(1970, 1, 1)).TotalSeconds;
if (expiry <= DateTimeOffset.UtcNow.ToUnixTimeSeconds())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ private void Validate()
{
if (Os != null && Os != OSPlatform.Linux && Os != OSPlatform.Windows)
{
throw new System.Exception($"Invalid value for {nameof(Os)}: {Os}. Supported values are {ServiceOs.Linux} and {ServiceOs.Windows}");
throw new Exception($"Invalid value for {nameof(Os)}: {Os}. Supported values are {ServiceOs.Linux} and {ServiceOs.Windows}");
}
if (!string.IsNullOrEmpty(ServiceAuth) && ServiceAuth != ServiceAuthType.EntraId && ServiceAuth != ServiceAuthType.AccessToken)
{
throw new System.Exception($"Invalid value for {nameof(ServiceAuth)}: {ServiceAuth}. Supported values are {ServiceAuthType.EntraId} and {ServiceAuthType.AccessToken}");
throw new Exception($"Invalid value for {nameof(ServiceAuth)}: {ServiceAuth}. Supported values are {ServiceAuthType.EntraId} and {ServiceAuthType.AccessToken}");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public TestRunDto GetTestRun()
},
TestRunConfig = new ClientConfig // TODO fetch some of these dynamically
{
Workers = 1,
Workers = _cloudRunMetadata.NumberOfTestWorkers,
PwVersion = "1.40",
Timeout = 60000,
TestType = "WebTest",
Expand All @@ -74,7 +74,7 @@ public TestRunShardDto GetTestRunShard()
Status = "RUNNING",
StartTime = startTime,
},
Workers = 1
Workers = _cloudRunMetadata.NumberOfTestWorkers
};
return shard;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ internal class TestProcessor : ITestProcessor
private readonly ICloudRunErrorParser _cloudRunErrorParser;
private readonly IServiceClient _serviceClient;
private readonly IConsoleWriter _consoleWriter;
private readonly CIInfo _cIInfo;
private readonly CloudRunMetadata _cloudRunMetadata;
internal readonly CIInfo _cIInfo;
internal readonly CloudRunMetadata _cloudRunMetadata;
private readonly IBlobService _blobService;

// Test Metadata
Expand Down
Loading

0 comments on commit 2a85f03

Please sign in to comment.