diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/api/Azure.Developer.MicrosoftPlaywrightTesting.NUnit.netstandard2.0.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/api/Azure.Developer.MicrosoftPlaywrightTesting.NUnit.netstandard2.0.cs
index 52f0189d991e8..64cd0a6c655dc 100644
--- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/api/Azure.Developer.MicrosoftPlaywrightTesting.NUnit.netstandard2.0.cs
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/api/Azure.Developer.MicrosoftPlaywrightTesting.NUnit.netstandard2.0.cs
@@ -3,7 +3,7 @@ namespace Azure.Developer.MicrosoftPlaywrightTesting.NUnit
[NUnit.Framework.SetUpFixtureAttribute]
public partial class PlaywrightServiceNUnit : Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.PlaywrightService
{
- public PlaywrightServiceNUnit(Azure.Core.TokenCredential? credential = null) : base (default(Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.PlaywrightServiceOptions), default(Azure.Core.TokenCredential)) { }
+ public PlaywrightServiceNUnit(Azure.Core.TokenCredential? credential = null) : base (default(Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.PlaywrightServiceOptions), default(Azure.Core.TokenCredential), default(Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface.IFrameworkLogger)) { }
public static Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.PlaywrightServiceOptions playwrightServiceOptions { get { throw null; } }
[NUnit.Framework.OneTimeSetUpAttribute]
public System.Threading.Tasks.Task SetupAsync() { throw null; }
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/src/NUnitFrameworkLogger.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/src/NUnitFrameworkLogger.cs
new file mode 100644
index 0000000000000..806da17197119
--- /dev/null
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/src/NUnitFrameworkLogger.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
+using NUnit.Framework;
+
+namespace Azure.Developer.MicrosoftPlaywrightTesting.NUnit
+{
+ internal class NUnitFrameworkLogger : IFrameworkLogger
+ {
+ public void Debug(string message)
+ {
+ TestContext.WriteLine($"[MPT-NUnit]: {message}");
+ }
+
+ public void Error(string message)
+ {
+ TestContext.Error.WriteLine($"[MPT-NUnit]: {message}");
+ }
+
+ public void Info(string message)
+ {
+ TestContext.Progress.WriteLine($"[MPT-NUnit]: {message}");
+ }
+
+ public void Warning(string message)
+ {
+ TestContext.Progress.WriteLine($"[MPT-NUnit]: {message}");
+ }
+ }
+}
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/src/PlaywrightServiceNUnit.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/src/PlaywrightServiceNUnit.cs
index ddcee9697806b..547be80ea7777 100644
--- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/src/PlaywrightServiceNUnit.cs
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.NUnit/src/PlaywrightServiceNUnit.cs
@@ -7,6 +7,7 @@
using System.Runtime.InteropServices;
using System;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger;
+using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
namespace Azure.Developer.MicrosoftPlaywrightTesting.NUnit;
@@ -16,12 +17,13 @@ namespace Azure.Developer.MicrosoftPlaywrightTesting.NUnit;
[SetUpFixture]
public class PlaywrightServiceNUnit : PlaywrightService
{
+ private static NUnitFrameworkLogger nunitFrameworkLogger { get; } = new();
///
/// Initializes a new instance of the class.
///
/// The azure token credential to use for authentication.
public PlaywrightServiceNUnit(TokenCredential? credential = null)
- : base(playwrightServiceOptions, credential: credential)
+ : base(playwrightServiceOptions, credential: credential, frameworkLogger: nunitFrameworkLogger)
{
}
@@ -47,7 +49,7 @@ public async Task SetupAsync()
{
if (!UseCloudHostedBrowsers)
return;
- TestContext.Progress.WriteLine("\nRunning tests using Microsoft Playwright Testing service.\n");
+ nunitFrameworkLogger.Info("\nRunning tests using Microsoft Playwright Testing service.\n");
await InitializeAsync().ConfigureAwait(false);
}
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/api/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.netstandard2.0.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/api/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.netstandard2.0.cs
index 92f645d91b4cf..e9db853b9e0d4 100644
--- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/api/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.netstandard2.0.cs
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/api/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.netstandard2.0.cs
@@ -8,8 +8,8 @@ public ConnectOptions() { }
}
public partial class PlaywrightService
{
- public PlaywrightService(Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.PlaywrightServiceOptions playwrightServiceOptions, Azure.Core.TokenCredential? credential = null) { }
- public PlaywrightService(System.Runtime.InteropServices.OSPlatform? os = default(System.Runtime.InteropServices.OSPlatform?), string? runId = null, string? exposeNetwork = null, string? serviceAuth = null, bool? useCloudHostedBrowsers = default(bool?), Azure.Core.TokenCredential? credential = null) { }
+ public PlaywrightService(Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.PlaywrightServiceOptions playwrightServiceOptions, Azure.Core.TokenCredential? credential = null, Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface.IFrameworkLogger? frameworkLogger = null) { }
+ public PlaywrightService(System.Runtime.InteropServices.OSPlatform? os = default(System.Runtime.InteropServices.OSPlatform?), string? runId = null, string? exposeNetwork = null, string? serviceAuth = null, bool? useCloudHostedBrowsers = default(bool?), Azure.Core.TokenCredential? credential = null, Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface.IFrameworkLogger? frameworkLogger = null) { }
public string? ExposeNetwork { get { throw null; } set { } }
public System.Runtime.InteropServices.OSPlatform? Os { get { throw null; } set { } }
public System.Threading.Timer? RotationTimer { get { throw null; } set { } }
@@ -67,3 +67,13 @@ public enum ServiceVersion
}
}
}
+namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
+{
+ public partial interface IFrameworkLogger
+ {
+ void Debug(string message);
+ void Error(string message);
+ void Info(string message);
+ void Warning(string message);
+ }
+}
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/EntraLifecycle.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/EntraLifecycle.cs
index a4f33add343be..f32c73990e2fa 100644
--- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/EntraLifecycle.cs
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/EntraLifecycle.cs
@@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
+using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
using Azure.Identity;
using Microsoft.IdentityModel.JsonWebTokens;
@@ -16,9 +17,11 @@ internal class EntraLifecycle
internal long? _entraIdAccessTokenExpiry;
private readonly TokenCredential _tokenCredential;
private readonly JsonWebTokenHandler _jsonWebTokenHandler;
+ private readonly IFrameworkLogger? _frameworkLogger;
- public EntraLifecycle(TokenCredential? tokenCredential = null, JsonWebTokenHandler? jsonWebTokenHandler = null)
+ public EntraLifecycle(TokenCredential? tokenCredential = null, JsonWebTokenHandler? jsonWebTokenHandler = null, IFrameworkLogger? frameworkLogger = null)
{
+ _frameworkLogger = frameworkLogger;
_tokenCredential = tokenCredential ?? new DefaultAzureCredential();
_jsonWebTokenHandler = jsonWebTokenHandler ?? new JsonWebTokenHandler();
SetEntraIdAccessTokenFromEnvironment();
@@ -37,7 +40,7 @@ internal async Task FetchEntraIdAccessTokenAsync(CancellationToken cancellationT
}
catch (Exception ex)
{
- Console.Error.WriteLine(ex);
+ _frameworkLogger?.Error(ex.ToString());
throw new Exception(Constants.s_no_auth_error);
}
}
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Implementation/VSTestFrameworkLogger.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Implementation/VSTestFrameworkLogger.cs
new file mode 100644
index 0000000000000..4a0d364643288
--- /dev/null
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Implementation/VSTestFrameworkLogger.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
+
+namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Implementation
+{
+ internal class VSTestFrameworkLogger : IFrameworkLogger
+ {
+ private readonly ILogger _logger;
+ public VSTestFrameworkLogger(ILogger? logger = null)
+ {
+ _logger = logger ?? new Logger();
+ }
+
+ public void Debug(string message)
+ {
+ _logger.Debug(message);
+ }
+
+ public void Error(string message)
+ {
+ _logger.Error(message);
+ }
+
+ public void Info(string message)
+ {
+ _logger.Info(message);
+ }
+
+ public void Warning(string message)
+ {
+ _logger.Warning(message);
+ }
+ }
+}
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Interface/IFrameworkLogger.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Interface/IFrameworkLogger.cs
new file mode 100644
index 0000000000000..63c41c5d3b753
--- /dev/null
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/Interface/IFrameworkLogger.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface
+{
+ ///
+ /// Sets up logging for the TestLogger package.
+ ///
+ public interface IFrameworkLogger
+ {
+ ///
+ /// Log informational message.
+ ///
+ ///
+ void Info(string message);
+ ///
+ /// Log debug messages.
+ ///
+ ///
+ void Debug(string message);
+ ///
+ /// Log warnming messages.
+ ///
+ ///
+ void Warning(string message);
+ ///
+ /// Log error messages.
+ ///
+ ///
+ void Error(string message);
+ }
+}
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightReporter.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightReporter.cs
index ac187e9e32a51..63c6168a382cb 100644
--- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightReporter.cs
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightReporter.cs
@@ -121,10 +121,19 @@ internal void InitializePlaywrightReporter(string xmlSettings)
return;
}
// setup entra rotation handlers
- _playwrightService = new PlaywrightService(null, playwrightServiceSettings!.RunId, null, playwrightServiceSettings.ServiceAuth, null, entraLifecycle: null, jsonWebTokenHandler: _jsonWebTokenHandler, credential: playwrightServiceSettings.AzureTokenCredential);
+ IFrameworkLogger frameworkLogger = new VSTestFrameworkLogger(_logger);
+ try
+ {
+ _playwrightService = new PlaywrightService(null, playwrightServiceSettings!.RunId, null, playwrightServiceSettings.ServiceAuth, null, entraLifecycle: null, jsonWebTokenHandler: _jsonWebTokenHandler, credential: playwrightServiceSettings.AzureTokenCredential, frameworkLogger: frameworkLogger);
#pragma warning disable AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.
- _playwrightService.InitializeAsync().GetAwaiter().GetResult();
+ _playwrightService.InitializeAsync().GetAwaiter().GetResult();
#pragma warning restore AZC0102 // Do not use GetAwaiter().GetResult(). Use the TaskExtensions.EnsureCompleted() extension method instead.
+ }
+ catch (Exception ex)
+ {
+ // We have checks for access token and base url in the next block, so we can ignore the exception here.
+ _logger.Error("Failed to initialize PlaywrightService: " + ex);
+ }
var cloudRunId = _environment.GetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceRunId);
string baseUrl = _environment.GetEnvironmentVariable(ReporterConstants.s_pLAYWRIGHT_SERVICE_REPORTING_URL);
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightService.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightService.cs
index d87082998823e..ed65c0b531aa3 100644
--- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightService.cs
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/src/PlaywrightService.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using Azure.Core;
+using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Interface;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Model;
using Azure.Developer.MicrosoftPlaywrightTesting.TestLogger.Utility;
using Microsoft.IdentityModel.JsonWebTokens;
@@ -136,19 +137,22 @@ public string? ExposeNetwork
private readonly EntraLifecycle? _entraLifecycle;
private readonly JsonWebTokenHandler? _jsonWebTokenHandler;
+ private IFrameworkLogger? _frameworkLogger;
///
/// Initializes a new instance of the class.
///
///
///
- public PlaywrightService(PlaywrightServiceOptions playwrightServiceOptions, TokenCredential? credential = null) : this(
+ ///
+ public PlaywrightService(PlaywrightServiceOptions playwrightServiceOptions, TokenCredential? credential = null, IFrameworkLogger? frameworkLogger = null) : this(
os: playwrightServiceOptions.Os,
runId: playwrightServiceOptions.RunId,
exposeNetwork: playwrightServiceOptions.ExposeNetwork,
serviceAuth: playwrightServiceOptions.ServiceAuth,
useCloudHostedBrowsers: playwrightServiceOptions.UseCloudHostedBrowsers,
- credential: credential ?? playwrightServiceOptions.AzureTokenCredential
+ credential: credential ?? playwrightServiceOptions.AzureTokenCredential,
+ frameworkLogger: frameworkLogger
)
{
// No-op
@@ -163,21 +167,25 @@ public PlaywrightService(PlaywrightServiceOptions playwrightServiceOptions, Toke
/// The service authentication mechanism.
/// Whether to use cloud-hosted browsers.
/// The token credential.
- public PlaywrightService(OSPlatform? os = null, string? runId = null, string? exposeNetwork = null, string? serviceAuth = null, bool? useCloudHostedBrowsers = null, TokenCredential? credential = null)
+ /// Logger
+ public PlaywrightService(OSPlatform? os = null, string? runId = null, string? exposeNetwork = null, string? serviceAuth = null, bool? useCloudHostedBrowsers = null, TokenCredential? credential = null, IFrameworkLogger? frameworkLogger = null)
{
if (string.IsNullOrEmpty(ServiceEndpoint))
return;
- _entraLifecycle = new EntraLifecycle(tokenCredential: credential);
+ _frameworkLogger = frameworkLogger;
+ _entraLifecycle = new EntraLifecycle(tokenCredential: credential, frameworkLogger: _frameworkLogger);
_jsonWebTokenHandler = new JsonWebTokenHandler();
InitializePlaywrightServiceEnvironmentVariables(GetServiceCompatibleOs(os), runId, exposeNetwork, serviceAuth, useCloudHostedBrowsers);
}
- internal PlaywrightService(OSPlatform? os = null, string? runId = null, string? exposeNetwork = null, string? serviceAuth = null, bool? useCloudHostedBrowsers = null, EntraLifecycle? entraLifecycle = null, JsonWebTokenHandler? jsonWebTokenHandler = null, TokenCredential? credential = null)
+ internal PlaywrightService(OSPlatform? os = null, string? runId = null, string? exposeNetwork = null, string? serviceAuth = null, bool? useCloudHostedBrowsers = null, EntraLifecycle? entraLifecycle = null, JsonWebTokenHandler? jsonWebTokenHandler = null, TokenCredential? credential = null, IFrameworkLogger? frameworkLogger = null)
{
if (string.IsNullOrEmpty(ServiceEndpoint))
return;
+ _frameworkLogger = frameworkLogger;
_jsonWebTokenHandler = jsonWebTokenHandler ?? new JsonWebTokenHandler();
- _entraLifecycle = entraLifecycle ?? new EntraLifecycle(credential, _jsonWebTokenHandler);
+ _entraLifecycle = entraLifecycle ?? new EntraLifecycle(credential, _jsonWebTokenHandler, _frameworkLogger);
+ _frameworkLogger = frameworkLogger;
InitializePlaywrightServiceEnvironmentVariables(GetServiceCompatibleOs(os), runId, exposeNetwork, serviceAuth, useCloudHostedBrowsers);
}
@@ -211,6 +219,7 @@ internal PlaywrightService(OSPlatform? os = null, string? runId = null, string?
}
if (string.IsNullOrEmpty(GetAuthToken()))
{
+ _frameworkLogger?.Error("Access token not found when trying to call GetConnectOptionsAsync.");
throw new Exception(Constants.s_no_auth_error);
}
@@ -236,20 +245,26 @@ internal PlaywrightService(OSPlatform? os = null, string? runId = null, string?
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(ServiceEndpoint))
+ {
+ _frameworkLogger?.Info("Exiting initialization as service endpoint is not set.");
return;
+ }
if (!UseCloudHostedBrowsers)
{
// Since playwright-dotnet checks PLAYWRIGHT_SERVICE_ACCESS_TOKEN and PLAYWRIGHT_SERVICE_URL to be set, remove PLAYWRIGHT_SERVICE_URL so that tests are run locally.
// If customers use GetConnectOptionsAsync, after setting disableScalableExecution, an error will be thrown.
+ _frameworkLogger?.Info("Disabling scalable execution since UseCloudHostedBrowsers is set to false.");
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceUri, null);
return;
}
// If default auth mechanism is Access token and token is available in the environment variable, no need to setup rotation handler
if (ServiceAuth == ServiceAuthType.AccessToken)
{
+ _frameworkLogger?.Info("Auth mechanism is Access Token.");
ValidateMptPAT();
return;
}
+ _frameworkLogger?.Info("Auth mechanism is Entra Id.");
await _entraLifecycle!.FetchEntraIdAccessTokenAsync(cancellationToken).ConfigureAwait(false);
RotationTimer = new Timer(RotationHandlerAsync, null, TimeSpan.FromMinutes(Constants.s_entra_access_token_rotation_interval_period_in_minutes), TimeSpan.FromMinutes(Constants.s_entra_access_token_rotation_interval_period_in_minutes));
}
@@ -259,6 +274,7 @@ public async Task InitializeAsync(CancellationToken cancellationToken = default)
///
public void Cleanup()
{
+ _frameworkLogger?.Info("Cleaning up Playwright service resources.");
RotationTimer?.Dispose();
}
@@ -266,6 +282,7 @@ internal async void RotationHandlerAsync(object? _)
{
if (_entraLifecycle!.DoesEntraIdAccessTokenRequireRotation())
{
+ _frameworkLogger?.Info("Rotating Entra Id access token.");
await _entraLifecycle.FetchEntraIdAccessTokenAsync().ConfigureAwait(false);
}
}
@@ -360,20 +377,28 @@ internal static void SetReportingUrlAndWorkspaceId()
private void ValidateMptPAT()
{
- string authToken = GetAuthToken()!;
- 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 tokenWorkspaceId = jsonWebToken.Claims.FirstOrDefault(c => c.Type == "aid")?.Value;
- Match match = Regex.Match(ServiceEndpoint, @"wss://(?[\w-]+)\.api\.(?playwright(?:-test|-int)?\.io|playwright\.microsoft\.com)/accounts/(?[\w-]+)/");
- if (!match.Success)
- throw new Exception(Constants.s_invalid_service_endpoint_error_message);
- var serviceEndpointWorkspaceId = match.Groups["workspaceId"].Value;
- 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())
- throw new Exception(Constants.s_expired_mpt_pat_error);
+ try
+ {
+ string authToken = GetAuthToken()!;
+ 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 tokenWorkspaceId = jsonWebToken.Claims.FirstOrDefault(c => c.Type == "aid")?.Value;
+ Match match = Regex.Match(ServiceEndpoint, @"wss://(?[\w-]+)\.api\.(?playwright(?:-test|-int)?\.io|playwright\.microsoft\.com)/accounts/(?[\w-]+)/");
+ if (!match.Success)
+ throw new Exception(Constants.s_invalid_service_endpoint_error_message);
+ var serviceEndpointWorkspaceId = match.Groups["workspaceId"].Value;
+ 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())
+ throw new Exception(Constants.s_expired_mpt_pat_error);
+ }
+ catch (Exception ex)
+ {
+ _frameworkLogger?.Error(ex.ToString());
+ throw;
+ }
}
internal static string? GetServiceCompatibleOs(OSPlatform? oSPlatform)
diff --git a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/tests/PlaywrightServiceTests.cs b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/tests/PlaywrightServiceTests.cs
index 6f6cfbf88916e..0e610789b60a9 100644
--- a/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/tests/PlaywrightServiceTests.cs
+++ b/sdk/playwrighttesting/Azure.Developer.MicrosoftPlaywrightTesting.TestLogger/tests/PlaywrightServiceTests.cs
@@ -198,7 +198,7 @@ public void Initialize_WhenServiceEnpointIsNotSet_NoOP()
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceUri, null);
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object);
service.InitializeAsync().Wait();
defaultAzureCredentialMock.Verify(x => x.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Never);
@@ -213,7 +213,7 @@ public void Initialize_WhenDefaultAuthIsEntraIdAccessTokenAndAccessTokenEnvironm
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(new AccessToken(token, DateTimeOffset.UtcNow.AddMinutes(10)));
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "access_token");
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object);
service.InitializeAsync().Wait();
@@ -252,7 +252,7 @@ public void Initialize_WhenDefaultAuthIsEntraIdAccessTokenAndAccessTokenEnvironm
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(new AccessToken(token, DateTimeOffset.UtcNow.AddMinutes(10)));
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "access_token");
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object, useCloudHostedBrowsers: false);
@@ -275,7 +275,7 @@ public void Initialize_WhenDefaultAuthIsEntraIdAccessTokenAndAccessTokenEnvironm
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(new AccessToken(token, DateTimeOffset.UtcNow.AddMinutes(10)));
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object);
service.InitializeAsync().Wait();
defaultAzureCredentialMock.Verify(x => x.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Once);
@@ -292,7 +292,7 @@ public void Initialize_WhenFetchesEntraIdAccessToken_SetsUpRotationHandler()
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(new AccessToken(token, DateTimeOffset.UtcNow.AddMinutes(10)));
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object);
service.InitializeAsync().Wait();
defaultAzureCredentialMock.Verify(x => x.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Once);
@@ -309,7 +309,7 @@ public void Initialize_WhenFailsToFetchEntraIdAccessToken_ThrowsException()
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ThrowsAsync(new Exception());
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object);
Exception? ex = Assert.ThrowsAsync(async () => await service.InitializeAsync());
Assert.That(ex!.Message, Is.EqualTo(Constants.s_no_auth_error));
@@ -327,7 +327,7 @@ public void Initialize_WhenEntraIdAccessTokenFailsAndMptPatIsSet_ThrowsException
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ThrowsAsync(new Exception());
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler());
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler(), null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object, jsonWebTokenHandler: new JsonWebTokenHandler());
Exception? ex = Assert.ThrowsAsync(async () => await service.InitializeAsync());
Assert.That(ex!.Message, Is.EqualTo(Constants.s_no_auth_error));
@@ -344,7 +344,7 @@ public void Initialize_WhenEntraIdAccessTokenFailsAndMptPatIsNotSet_ThrowsExcept
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ThrowsAsync(new Exception());
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler());
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler(), null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object, jsonWebTokenHandler: new JsonWebTokenHandler());
Exception? ex = Assert.ThrowsAsync(async () => await service.InitializeAsync());
Assert.That(ex!.Message, Is.EqualTo(Constants.s_no_auth_error));
@@ -359,7 +359,7 @@ public void Initialize_WhenEntraIdAccessTokenFailsAndMptPatIsNotValid_ThrowsErro
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ThrowsAsync(new Exception());
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler());
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler(), null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object, jsonWebTokenHandler: new JsonWebTokenHandler());
Assert.That(() => service.InitializeAsync().Wait(), Throws.Exception);
}
@@ -379,7 +379,7 @@ public void Initialize_WhenEntraIdAccessTokenFailsAndMptPatTokenParsingReturnsNu
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ThrowsAsync(new Exception());
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler());
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler(), null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object, jsonWebTokenHandler: jsonWebTokenHandlerMock.Object);
Assert.That(() => service.InitializeAsync().Wait(), Throws.Exception);
}
@@ -396,7 +396,7 @@ public void Initialize_WhenEntraIdAccessTokenFailsAndMptPatIsExpired_ThrowsError
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ThrowsAsync(new Exception());
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler());
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler(), null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object, jsonWebTokenHandler: new JsonWebTokenHandler());
Assert.That(() => service.InitializeAsync().Wait(), Throws.Exception);
@@ -419,7 +419,7 @@ public void Initialize_WhenDefaultAuthIsMptPATAndPATIsSet_DoesNotSetUpRotationHa
};
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceUri, $"{testRubric["url"]}");
var defaultAzureCredentialMock = new Mock();
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler());
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, new JsonWebTokenHandler(), null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object, jsonWebTokenHandler: new JsonWebTokenHandler(), serviceAuth: ServiceAuthType.AccessToken);
service.InitializeAsync().Wait();
Assert.That(service.RotationTimer, Is.Null);
@@ -434,7 +434,7 @@ public void RotationHandler_WhenEntraIdAccessTokenRequiresRotation_FetchesEntraI
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(new AccessToken(token, DateTimeOffset.UtcNow.AddMinutes(5)));
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object);
service.RotationHandlerAsync(null);
@@ -446,7 +446,7 @@ public void RotationHandler_WhenEntraIdAccessTokenDoesNotRequireRotation_NoOp()
{
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object._entraIdAccessTokenExpiry = (int)DateTimeOffset.UtcNow.AddMinutes(22).ToUnixTimeSeconds();
PlaywrightService service = new(entraLifecycle: entraLifecycleMock.Object);
@@ -482,7 +482,7 @@ public async Task GetConnectOptionsAsync_WhenServiceEndpointIsSet_ReturnsConnect
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "valid_token");
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object._entraIdAccessTokenExpiry = (int)DateTimeOffset.UtcNow.AddMinutes(22).ToUnixTimeSeconds();
var runId = "run-id";
@@ -507,7 +507,7 @@ public async Task GetConnectOptionsAsync_WhenTokenRequiresRotation_RotatesEntraT
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(new AccessToken("valid_token", DateTimeOffset.UtcNow.AddMinutes(5)));
var jsonWebTokenHandlerMock = new Mock();
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object._entraIdAccessTokenExpiry = (int)DateTimeOffset.UtcNow.AddMinutes(-1).ToUnixTimeSeconds();
@@ -525,7 +525,7 @@ public async Task GetConnectOptionsAsync_WhenTokenDoesNotRequireRotation_DoesNot
.Setup(x => x.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(new AccessToken("valid_token", DateTimeOffset.UtcNow.AddMinutes(5)));
var jsonWebTokenHandlerMock = new Mock();
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object._entraIdAccessTokenExpiry = (int)DateTimeOffset.UtcNow.AddMinutes(22).ToUnixTimeSeconds();
@@ -540,7 +540,7 @@ public async Task GetConnectOptionsAsync_WhenDefaultParametersAreProvided_SetsSe
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "valid_token");
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object
._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object
@@ -563,7 +563,7 @@ public async Task GetConnectOptionsAsync_WhenDefaultParametersAreNotProvided_Set
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "valid_token");
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object
._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object
@@ -586,7 +586,7 @@ public async Task GetConnectOptionsAsync_WhenParametersAreSetInTheObject_UsesObj
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "valid_token");
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object
._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object
@@ -612,7 +612,7 @@ public async Task GetConnectOptionsAsync_WhenParametersAreSetInTheObjectButAlsoP
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "valid_token");
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object
._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object
@@ -635,7 +635,7 @@ public async Task GetConnectOptionsAsync_WhenParametersAreSetViaEnvironmentButAl
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "valid_token");
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object
._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object
@@ -661,7 +661,7 @@ public async Task GetConnectOptionsAsync_WhenServiceParametersAreSetViaEnvironme
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, "valid_token");
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
entraLifecycleMock.Object
._entraIdAccessToken = "valid_token";
entraLifecycleMock.Object
@@ -686,7 +686,7 @@ public void GetConnectOptionsAsync_WhenNoAuthTokenIsSet_ThrowsException()
{
var defaultAzureCredentialMock = new Mock();
var jsonWebTokenHandlerMock = new Mock();
- var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object);
+ var entraLifecycleMock = new Mock(defaultAzureCredentialMock.Object, jsonWebTokenHandlerMock.Object, null);
var service = new PlaywrightService(entraLifecycle: entraLifecycleMock.Object);
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, null);