Skip to content

Commit 2a85f03

Browse files
authored
chore(playwrighttesting): populate NumberOfTestWorkers from runsettings and refactor PlaywrightReporter (#47066)
* chore(playwrighttesting): populate NumberOfTestWorkers from runsettings and refactor PlaywrightReporter * chore(): update api spec --------- Co-authored-by: Siddharth Singha Roy <[email protected]>
1 parent e666aab commit 2a85f03

File tree

16 files changed

+542
-87
lines changed

16 files changed

+542
-87
lines changed

sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/api/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.netstandard2.0.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public partial class RunSettingKey
2929
public static readonly string EnableResultPublish;
3030
public static readonly string ExposeNetwork;
3131
public static readonly string ManagedIdentityClientId;
32+
public static readonly string NumberOfTestWorkers;
3233
public static readonly string Os;
3334
public static readonly string RunId;
3435
public static readonly string ServiceAuthType;

sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Constants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ public class RunSettingKey
180180
/// Enable Result publish.
181181
/// </summary>
182182
public static readonly string EnableResultPublish = "EnableResultPublish";
183+
184+
/// <summary>
185+
/// Number of NUnit test workers.
186+
/// </summary>
187+
public static readonly string NumberOfTestWorkers = "NumberOfTestWorkers";
183188
}
184189

185190
internal class Constants
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
6+
7+
namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation
8+
{
9+
internal class EnvironmentHandler : IEnvironment
10+
{
11+
public void Exit(int exitCode)
12+
{
13+
Environment.Exit(exitCode);
14+
}
15+
16+
public string GetEnvironmentVariable(string variable)
17+
{
18+
return Environment.GetEnvironmentVariable(variable);
19+
}
20+
21+
public void SetEnvironmentVariable(string variable, string value)
22+
{
23+
Environment.SetEnvironmentVariable(variable, value);
24+
}
25+
}
26+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Xml;
7+
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
8+
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
9+
10+
namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation
11+
{
12+
internal class XmlRunSettings : IXmlRunSettings
13+
{
14+
private static readonly string NUnitNodeName = "NUnit";
15+
public Dictionary<string, object> GetTestRunParameters(string? settingsXml)
16+
{
17+
return XmlRunSettingsUtilities.GetTestRunParameters(settingsXml);
18+
}
19+
20+
public Dictionary<string, object> GetNUnitParameters(string? settingsXml)
21+
{
22+
try
23+
{
24+
var parameters = new Dictionary<string, object>();
25+
XmlDocument xmlDocument = ParseXmlSettings(settingsXml);
26+
XmlNodeList nUnitNodes = xmlDocument.GetElementsByTagName(NUnitNodeName);
27+
foreach (XmlNode nUnitNode in nUnitNodes)
28+
{
29+
foreach (XmlNode childNode in nUnitNode.ChildNodes)
30+
{
31+
parameters.Add(childNode.Name, childNode.InnerText);
32+
}
33+
}
34+
return parameters;
35+
}
36+
catch (Exception)
37+
{
38+
return new Dictionary<string, object>();
39+
}
40+
}
41+
42+
private static XmlDocument ParseXmlSettings(string? settingsXml)
43+
{
44+
XmlDocument xmlDocument = new();
45+
xmlDocument.LoadXml(settingsXml);
46+
return xmlDocument;
47+
}
48+
}
49+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
5+
{
6+
internal interface IEnvironment
7+
{
8+
void Exit(int exitCode);
9+
string GetEnvironmentVariable(string variable);
10+
void SetEnvironmentVariable(string variable, string value);
11+
}
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Generic;
5+
6+
namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
7+
{
8+
internal interface IXmlRunSettings
9+
{
10+
Dictionary<string, object> GetTestRunParameters(string? settingsXml);
11+
Dictionary<string, object> GetNUnitParameters(string? settingsXml);
12+
}
13+
}

sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Model/CloudRunMetadata.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ internal string? PortalUrl
1818
internal bool EnableGithubSummary { get; set; } = true;
1919
internal DateTime TestRunStartTime { get; set; }
2020
internal TokenDetails? AccessTokenDetails { get; set; }
21+
internal int NumberOfTestWorkers { get; set; } = 1;
2122
}
2223
}

sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightReporter.cs

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,32 @@
1212
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
1313
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation;
1414
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Processor;
15+
using Microsoft.IdentityModel.JsonWebTokens;
16+
using Microsoft.IdentityModel.Tokens;
1517

1618
namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger;
1719

1820
[FriendlyName("microsoft-playwright-testing")]
1921
[ExtensionUri("logger://MicrosoftPlaywrightTesting/Logger/v1")]
2022
internal class PlaywrightReporter : ITestLoggerWithParameters
2123
{
22-
private Dictionary<string, string?>? _parametersDictionary;
23-
private PlaywrightService? _playwrightService;
24-
private readonly ILogger _logger;
25-
private TestProcessor? _testProcessor;
26-
27-
public PlaywrightReporter() : this(null) { } // no-op
28-
public PlaywrightReporter(ILogger? logger)
24+
internal Dictionary<string, string?>? _parametersDictionary;
25+
internal PlaywrightService? _playwrightService;
26+
internal TestProcessor? _testProcessor;
27+
internal readonly ILogger _logger;
28+
internal IEnvironment _environment;
29+
internal IXmlRunSettings _xmlRunSettings;
30+
internal IConsoleWriter _consoleWriter;
31+
internal JsonWebTokenHandler _jsonWebTokenHandler;
32+
33+
public PlaywrightReporter() : this(null, null, null, null, null) { } // no-op
34+
public PlaywrightReporter(ILogger? logger, IEnvironment? environment, IXmlRunSettings? xmlRunSettings, IConsoleWriter? consoleWriter, JsonWebTokenHandler? jsonWebTokenHandler)
2935
{
3036
_logger = logger ?? new Logger();
37+
_environment = environment ?? new EnvironmentHandler();
38+
_xmlRunSettings = xmlRunSettings ?? new XmlRunSettings();
39+
_consoleWriter = consoleWriter ?? new ConsoleWriter();
40+
_jsonWebTokenHandler = jsonWebTokenHandler ?? new JsonWebTokenHandler();
3141
}
3242

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

69-
private void InitializePlaywrightReporter(string xmlSettings)
79+
internal void InitializePlaywrightReporter(string xmlSettings)
7080
{
71-
Dictionary<string, object> runParameters = XmlRunSettingsUtilities.GetTestRunParameters(xmlSettings);
81+
Dictionary<string, object> runParameters = _xmlRunSettings.GetTestRunParameters(xmlSettings);
82+
Dictionary<string, object> nunitParameters = _xmlRunSettings.GetNUnitParameters(xmlSettings);
7283
runParameters.TryGetValue(RunSettingKey.RunId, out var runId);
7384
// If run id is not provided and not set via env, try fetching it from CI info.
74-
CIInfo cIInfo = CiInfoProvider.GetCIInfo();
75-
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId)))
85+
CIInfo cIInfo = CiInfoProvider.GetCIInfo(_environment);
86+
if (string.IsNullOrEmpty(_environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId)))
7687
{
7788
if (string.IsNullOrEmpty(runId?.ToString()))
78-
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId, ReporterUtils.GetRunId(cIInfo));
89+
_environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId, ReporterUtils.GetRunId(cIInfo));
7990
else
80-
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId, runId!.ToString());
91+
_environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId, runId!.ToString());
8192
}
8293
else
8394
{
@@ -89,46 +100,49 @@ private void InitializePlaywrightReporter(string xmlSettings)
89100
runParameters.TryGetValue(RunSettingKey.ManagedIdentityClientId, out var managedIdentityClientId);
90101
runParameters.TryGetValue(RunSettingKey.EnableGitHubSummary, out var enableGithubSummary);
91102
runParameters.TryGetValue(RunSettingKey.EnableResultPublish, out var enableResultPublish);
103+
nunitParameters.TryGetValue(RunSettingKey.NumberOfTestWorkers, out var numberOfTestWorkers);
92104
string? enableGithubSummaryString = enableGithubSummary?.ToString();
93105
string? enableResultPublishString = enableResultPublish?.ToString();
94106

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

98-
PlaywrightServiceOptions? playwrightServiceSettings = null;
110+
PlaywrightServiceOptions? playwrightServiceSettings;
99111
try
100112
{
101113
playwrightServiceSettings = new(runId: runId?.ToString(), serviceAuth: serviceAuth?.ToString(), azureTokenCredentialType: azureTokenCredential?.ToString(), managedIdentityClientId: managedIdentityClientId?.ToString());
102114
}
103115
catch (Exception ex)
104116
{
105-
Console.Error.WriteLine("Failed to initialize PlaywrightServiceSettings: " + ex);
106-
Environment.Exit(1);
117+
_consoleWriter.WriteError("Failed to initialize PlaywrightServiceSettings: " + ex);
118+
_environment.Exit(1);
119+
return;
107120
}
108-
109121
// setup entra rotation handlers
110-
_playwrightService = new PlaywrightService(null, playwrightServiceSettings!.RunId, null, playwrightServiceSettings.ServiceAuth, null, playwrightServiceSettings.AzureTokenCredential);
122+
_playwrightService = new PlaywrightService(null, playwrightServiceSettings!.RunId, null, playwrightServiceSettings.ServiceAuth, null, entraLifecycle: null, jsonWebTokenHandler: _jsonWebTokenHandler, credential: playwrightServiceSettings.AzureTokenCredential);
111123
#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.
112124
_playwrightService.InitializeAsync().GetAwaiter().GetResult();
113125
#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.
114126

115-
var cloudRunId = Environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId);
116-
string baseUrl = Environment.GetEnvironmentVariable(ReporterConstants.s_pLAYWRIGHT_SERVICE_REPORTING_URL);
117-
string accessToken = Environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken);
127+
var cloudRunId = _environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId);
128+
string baseUrl = _environment.GetEnvironmentVariable(ReporterConstants.s_pLAYWRIGHT_SERVICE_REPORTING_URL);
129+
string accessToken = _environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken);
118130
if (string.IsNullOrEmpty(baseUrl))
119131
{
120-
Console.Error.WriteLine(Constants.s_no_service_endpoint_error_message);
121-
Environment.Exit(1);
132+
_consoleWriter.WriteError(Constants.s_no_service_endpoint_error_message);
133+
_environment.Exit(1);
134+
return;
122135
}
123136
if (string.IsNullOrEmpty(accessToken))
124137
{
125-
Console.Error.WriteLine(Constants.s_no_auth_error);
126-
Environment.Exit(1);
138+
_consoleWriter.WriteError(Constants.s_no_auth_error);
139+
_environment.Exit(1);
140+
return;
127141
}
128142

129143
var baseUri = new Uri(baseUrl);
130144
var reporterUtils = new ReporterUtils();
131-
TokenDetails tokenDetails = reporterUtils.ParseWorkspaceIdFromAccessToken(jsonWebTokenHandler: null, accessToken: accessToken);
145+
TokenDetails tokenDetails = reporterUtils.ParseWorkspaceIdFromAccessToken(jsonWebTokenHandler: _jsonWebTokenHandler, accessToken: accessToken);
132146
var workspaceId = tokenDetails.aid;
133147

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

145160
_testProcessor = new TestProcessor(cloudRunMetadata, cIInfo);

sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ internal PlaywrightService(OSPlatform? os = null, string? runId = null, string?
7979
{
8080
if (string.IsNullOrEmpty(ServiceEndpoint))
8181
return;
82-
_entraLifecycle = entraLifecycle ?? new EntraLifecycle(credential);
8382
_jsonWebTokenHandler = jsonWebTokenHandler ?? new JsonWebTokenHandler();
83+
_entraLifecycle = entraLifecycle ?? new EntraLifecycle(credential, _jsonWebTokenHandler);
8484
InitializePlaywrightServiceEnvironmentVariables(getServiceCompatibleOs(os), runId, exposeNetwork, serviceAuth, useCloudHostedBrowsers);
8585
}
8686

@@ -248,12 +248,12 @@ private void ValidateMptPAT()
248248
if (string.IsNullOrEmpty(authToken))
249249
throw new Exception(Constants.s_no_auth_error);
250250
JsonWebToken jsonWebToken = _jsonWebTokenHandler!.ReadJsonWebToken(authToken) ?? throw new Exception(Constants.s_invalid_mpt_pat_error);
251-
var tokenaWorkspaceId = jsonWebToken.Claims.FirstOrDefault(c => c.Type == "aid")?.Value;
251+
var tokenWorkspaceId = jsonWebToken.Claims.FirstOrDefault(c => c.Type == "aid")?.Value;
252252
Match match = Regex.Match(ServiceEndpoint, @"wss://(?<region>[\w-]+)\.api\.(?<domain>playwright(?:-test|-int)?\.io|playwright\.microsoft\.com)/accounts/(?<workspaceId>[\w-]+)/");
253253
if (!match.Success)
254254
throw new Exception(Constants.s_invalid_service_endpoint_error_message);
255255
var serviceEndpointWorkspaceId = match.Groups["workspaceId"].Value;
256-
if (tokenaWorkspaceId != serviceEndpointWorkspaceId)
256+
if (tokenWorkspaceId != serviceEndpointWorkspaceId)
257257
throw new Exception(Constants.s_workspace_mismatch_error);
258258
var expiry = (long)(jsonWebToken.ValidTo - new DateTime(1970, 1, 1)).TotalSeconds;
259259
if (expiry <= DateTimeOffset.UtcNow.ToUnixTimeSeconds())

sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightServiceOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ private void Validate()
4545
{
4646
if (Os != null && Os != OSPlatform.Linux && Os != OSPlatform.Windows)
4747
{
48-
throw new System.Exception($"Invalid value for {nameof(Os)}: {Os}. Supported values are {ServiceOs.Linux} and {ServiceOs.Windows}");
48+
throw new Exception($"Invalid value for {nameof(Os)}: {Os}. Supported values are {ServiceOs.Linux} and {ServiceOs.Windows}");
4949
}
5050
if (!string.IsNullOrEmpty(ServiceAuth) && ServiceAuth != ServiceAuthType.EntraId && ServiceAuth != ServiceAuthType.AccessToken)
5151
{
52-
throw new System.Exception($"Invalid value for {nameof(ServiceAuth)}: {ServiceAuth}. Supported values are {ServiceAuthType.EntraId} and {ServiceAuthType.AccessToken}");
52+
throw new Exception($"Invalid value for {nameof(ServiceAuth)}: {ServiceAuth}. Supported values are {ServiceAuthType.EntraId} and {ServiceAuthType.AccessToken}");
5353
}
5454
}
5555

0 commit comments

Comments
 (0)