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

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -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
Sid200026 marked this conversation as resolved.
Show resolved Hide resolved
};

_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
Sid200026 marked this conversation as resolved.
Show resolved Hide resolved
};
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