| File: Integration\Playwright\BrowserTokenAuthenticationTests.cs | Web Access |
| Project: src\tests\Aspire.Dashboard.Tests\Aspire.Dashboard.Tests.csproj (Aspire.Dashboard.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.TestUtilities; using Aspire.Dashboard.Configuration; using Aspire.Dashboard.Resources; using Aspire.Dashboard.Tests.Integration.Playwright.Infrastructure; using Aspire.Hosting; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Playwright; using Xunit; namespace Aspire.Dashboard.Tests.Integration.Playwright; [RequiresFeature(TestFeature.Playwright)] public class BrowserTokenAuthenticationTests : PlaywrightTestsBase<BrowserTokenAuthenticationTests.BrowserTokenDashboardServerFixture> { public class BrowserTokenDashboardServerFixture : DashboardServerFixture { public BrowserTokenDashboardServerFixture() { Configuration[DashboardConfigNames.DashboardFrontendAuthModeName.ConfigKey] = nameof(FrontendAuthMode.BrowserToken); Configuration[DashboardConfigNames.DashboardFrontendBrowserTokenName.ConfigKey] = "VALID_TOKEN"; } } public sealed class BrowserTokenDashboardServerWithHttpAndHttpsFixture : DashboardServerFixture { public BrowserTokenDashboardServerWithHttpAndHttpsFixture() { // Bind to 127.0.0.1 rather than the "localhost" hostname: Kestrel rejects dynamic-port // binding (":0") on "localhost" with "Dynamic port binding is not supported when binding // to localhost. You must either bind to 127.0.0.1:0 or [::1]:0, or both." The WebKit test // navigates to the resolved address with IgnoreHTTPSErrors, so the loopback IP is fine. Configuration[DashboardConfigNames.DashboardFrontendUrlName.ConfigKey] = "https://127.0.0.1:0;http://127.0.0.1:0"; Configuration[DashboardConfigNames.DashboardFrontendAuthModeName.ConfigKey] = nameof(FrontendAuthMode.BrowserToken); Configuration[DashboardConfigNames.DashboardFrontendBrowserTokenName.ConfigKey] = "VALID_TOKEN"; } } public BrowserTokenAuthenticationTests(BrowserTokenDashboardServerFixture dashboardServerFixture) : base(dashboardServerFixture) { } [Fact] [OuterloopTest("Resource-intensive Playwright browser test")] public async Task BrowserToken_LoginPage_Success_RedirectToResources() { // Arrange await RunTestAsync(async page => { // Act var response = await page.GotoAsync("/").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); var uri = new Uri(response!.Url); Assert.Equal("/login?returnUrl=%2F", uri.PathAndQuery); var tokenTextBox = page.GetByRole(AriaRole.Textbox); await tokenTextBox.FillAsync("VALID_TOKEN").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); var submitButton = page.GetByRole(AriaRole.Button); await submitButton.ClickAsync().DefaultTimeout(TestConstants.LongTimeoutTimeSpan); // Wait for navigation to complete after successful login. // The page redirects from /login to / (resources page). await page.WaitForURLAsync(url => new Uri(url).AbsolutePath == "/").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); // Assert await Assertions .Expect(page.GetByText(MockDashboardClient.TestResource1.DisplayName)) .ToBeVisibleAsync() .DefaultTimeout(TestConstants.LongTimeoutTimeSpan); }); } [Fact] [OuterloopTest("Resource-intensive Playwright browser test")] public async Task BrowserToken_LoginPage_SuccessWithWhitespaceToken_RedirectToResources() { // Arrange await RunTestAsync(async page => { // Act var response = await page.GotoAsync("/").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); var uri = new Uri(response!.Url); Assert.Equal("/login?returnUrl=%2F", uri.PathAndQuery); var tokenTextBox = page.GetByRole(AriaRole.Textbox); await tokenTextBox.FillAsync(" VALID_TOKEN ").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); var submitButton = page.GetByRole(AriaRole.Button); await submitButton.ClickAsync().DefaultTimeout(TestConstants.LongTimeoutTimeSpan); await page.WaitForURLAsync(url => new Uri(url).AbsolutePath == "/").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); // Assert await Assertions .Expect(page.GetByText(MockDashboardClient.TestResource1.DisplayName)) .ToBeVisibleAsync() .DefaultTimeout(TestConstants.LongTimeoutTimeSpan); }); } [Fact] [OuterloopTest("Resource-intensive Playwright browser test")] public async Task BrowserToken_LoginPage_Failure_DisplayFailureMessage() { // Arrange await RunTestAsync(async page => { // Act var response = await page.GotoAsync("/").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); var uri = new Uri(response!.Url); Assert.Equal("/login?returnUrl=%2F", uri.PathAndQuery); var tokenTextBox = page.GetByRole(AriaRole.Textbox); await tokenTextBox.FillAsync("INVALID_TOKEN").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); var submitButton = page.GetByRole(AriaRole.Button); await submitButton.ClickAsync().DefaultTimeout(TestConstants.LongTimeoutTimeSpan); // Assert await Assertions .Expect(page.GetByText(Login.InvalidTokenErrorMessage)) .ToBeVisibleAsync() .DefaultTimeout(TestConstants.LongTimeoutTimeSpan); }); } [Fact] [OuterloopTest("Resource-intensive Playwright browser test")] public async Task BrowserToken_QueryStringToken_Success_RestrictToResources() { // Arrange await RunTestAsync(async page => { // Act await page.GotoAsync("/login?t=VALID_TOKEN").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); // Assert await Assertions .Expect(page.GetByText(MockDashboardClient.TestResource1.DisplayName)) .ToBeVisibleAsync() .DefaultTimeout(TestConstants.LongTimeoutTimeSpan); }); } [Fact] [OuterloopTest("Resource-intensive Playwright browser test")] public async Task BrowserToken_QueryStringToken_Failure_DisplayLoginPage() { // Arrange await RunTestAsync(async page => { // Act await page.GotoAsync("/login?t=INVALID_TOKEN").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); var submitButton = page.GetByRole(AriaRole.Button); var name = await submitButton.GetAttributeAsync("name").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); // Assert Assert.Equal("submit-token", name); }); } } [RequiresFeature(TestFeature.Playwright)] public class BrowserTokenAuthenticationHttpAndHttpsTests : PlaywrightTestsBase<BrowserTokenAuthenticationTests.BrowserTokenDashboardServerWithHttpAndHttpsFixture> { public BrowserTokenAuthenticationHttpAndHttpsTests(BrowserTokenAuthenticationTests.BrowserTokenDashboardServerWithHttpAndHttpsFixture dashboardServerFixture) : base(dashboardServerFixture) { } [Fact] [OuterloopTest("Resource-intensive Playwright browser test")] public async Task BrowserToken_QueryStringToken_HttpsThenHttp_WebKit_Success() { using var playwright = await Microsoft.Playwright.Playwright.CreateAsync(); await using var browser = await LaunchWebKitAsync(playwright); await RunHttpsThenHttpAsync(browser); } private static async Task<IBrowser> LaunchWebKitAsync(IPlaywright playwright) { try { return await playwright.Webkit.LaunchAsync(new BrowserTypeLaunchOptions { Headless = true }); } catch (PlaywrightException ex) when (IsWebKitBrowserUnavailable(ex)) { Assert.Skip("Playwright WebKit is not available in this environment."); throw; } } private static bool IsWebKitBrowserUnavailable(PlaywrightException ex) { return ex.Message.Contains("Executable doesn't exist", StringComparison.Ordinal) || ex.Message.Contains("Host system is missing dependencies", StringComparison.Ordinal); } private async Task RunHttpsThenHttpAsync(IBrowser browser) { var endpoints = DashboardServerFixture.DashboardApp.FrontendEndPointsAccessor .Select(accessor => accessor()) .ToList(); var httpsEndpoint = endpoints.Single(e => e.IsHttps); var httpEndpoint = endpoints.Single(e => !e.IsHttps); var httpsBaseUrl = httpsEndpoint.GetResolvedAddress(replaceIPAnyWithLocalhost: true); var httpBaseUrl = httpEndpoint.GetResolvedAddress(replaceIPAnyWithLocalhost: true); var context = await browser.NewContextAsync(new BrowserNewContextOptions { IgnoreHTTPSErrors = true }); try { var page = await context.NewPageAsync(); try { await page.GotoAsync($"{httpsBaseUrl}/login?t=VALID_TOKEN").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); await Assertions .Expect(page.GetByText(MockDashboardClient.TestResource1.DisplayName)) .ToBeVisibleAsync() .DefaultTimeout(TestConstants.LongTimeoutTimeSpan); await page.GotoAsync($"{httpBaseUrl}/login?t=VALID_TOKEN").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); await Assertions .Expect(page.GetByText(MockDashboardClient.TestResource1.DisplayName)) .ToBeVisibleAsync() .DefaultTimeout(TestConstants.LongTimeoutTimeSpan); await page.GotoAsync($"{httpBaseUrl}/structuredlogs").DefaultTimeout(TestConstants.LongTimeoutTimeSpan); Assert.Equal("/structuredlogs", new Uri(page.Url).AbsolutePath); await Assertions .Expect(page.GetByRole(AriaRole.Button, new() { Name = "submit-token" })) .Not .ToBeVisibleAsync() .DefaultTimeout(TestConstants.LongTimeoutTimeSpan); } finally { await page.CloseAsync(); } } finally { await context.DisposeAsync(); } } }