Skip to content

Refactor dashboard automation tests and improve configuration handling #4238

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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 @@ -10,15 +10,19 @@ public async Task<DistributedApplication> ConfigureAsync<TEntryPoint>(
string[]? args = null,
Action<IDistributedApplicationTestingBuilder>? configureBuilder = null) where TEntryPoint : class
{
if (App is not null)
{
return App;
}

var builder = await DistributedApplicationTestingBuilder.CreateAsync<TEntryPoint>(
args: args ?? [],
configureBuilder: static (options, _) =>
{
options.DisableDashboard = false;
options.AllowUnsecuredTransport = true;
});

builder.Configuration["ASPIRE_ALLOW_UNSECURED_TRANSPORT"] = "true";

configureBuilder?.Invoke(builder);

App = await builder.BuildAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,23 +186,25 @@ await InteractWithPageAsync(async page =>
// Login to the dashboard
await page.LoginAndWaitForRunningResourcesAsync(DashboardLoginToken);

var apiEllipsisButton = FluentDataGridSelector.Grid.Body.Row(3).Cell(6)
.Descendant("fluent-button:nth-of-type(3)");
await page.ClickAsync(apiEllipsisButton);
await page.ClickAsync(FluentDataGridSelector.Grid.Body.Row(3).Cell(6)
.Descendant("fluent-button:nth-of-type(3)"));

await page.HighlightElementAsync("fluent-anchored-region");

await page.SaveExploreScreenshotAsync("resource-actions.png");

await page.ClickAsync(DashboardSelectors.ResourcePage.ViewDetailsOption);
await page.ClickAsync(DashboardSelectors.ResourcePage.SplitPanel);
//await page.ClickAsync(DashboardSelectors.ResourcePage.SplitPanel);

await page.AdjustSplitPanelsGridTemplateAsync();
await page.ClickAndDragShadowRootElementAsync(
DashboardSelectors.SplitPanels, DashboardSelectors.MedianId, (0, 20));
//await page.AdjustSplitPanelsGridTemplateAsync();
//await page.ClickAndDragShadowRootElementAsync(
// DashboardSelectors.SplitPanels, DashboardSelectors.MedianId, (0, 20));
Copy link
Preview

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple lines of commented-out code (lines 197, 199-201) should be removed rather than left as comments. If this code might be needed later, consider using version control history instead.

Copilot uses AI. Check for mistakes.

await page.RedactElementTextAsync(DashboardSelectors.ResourcePage.ResourceDetailsProjectPath);

await page.ClickAsync(apiEllipsisButton);
var apiEllipsisButton = FluentDataGridSelector.Grid.Body.Row(3).Cell(4)
.Descendant("fluent-button");

await page.ClickAsync(apiEllipsisButton, new() { Force = true });
await page.HoverAsync(DashboardSelectors.ResourcePage.ViewDetailsOption);
await page.HighlightElementAsync(DashboardSelectors.ResourcePage.ViewDetailsOption);

Expand Down Expand Up @@ -258,7 +260,7 @@ await InteractWithPageAsync(async page =>
[Fact, Trait("Capture", "resource-errors")]
public async Task CaptureResourcesWithErrorsImages()
{
await ConfigureAsync<SampleAppHost>([ "API_THROWS_EXCEPTION=true" ]);
await ConfigureAsync<SampleAppHost>(["API_THROWS_EXCEPTION=true"]);

await InteractWithPageAsync(async page =>
{
Expand Down Expand Up @@ -447,7 +449,7 @@ await InteractWithPageAsync(async page =>

// Delay beyond output cache, and invalidate then reload.
await Task.Delay(2_250);
await webPage.ReloadAsync();
await webPage.ReloadAsync(new() { WaitUntil = WaitUntilState.NetworkIdle });

await page.BringToFrontAsync();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ internal static class ResourcePage
public const string StartResource = """fluent-button[title="Start resource"]""";
public const string SplitPanel = """fluent-button[title="Split horizontal"]""";

public const string ResourceDetailsProjectPath = """fluent-accordion-item [title^="C:"]:last-of-type""";
public const string ResourceDetailsProjectPath = """fluent-accordion-item [title^="C:"]:last-of-type, fluent-accordion-item [title^="E:"]:last-of-type""";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ await page.EvaluateAsync($$"""

public static async Task LoginAsync(this IPage page, string token)
{
var response = await page.GotoAsync($"/login?t={token}");
var response = await page.GotoAsync(
$"/login?t={token}", new() { WaitUntil = WaitUntilState.NetworkIdle });

Assert.NotNull(response);
Assert.True(response.Ok, $"Failed to navigate to login page: {response.Status}");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace Aspire.Dashboard.ScreenCapture;

namespace Aspire.Dashboard.ScreenCapture;
Copy link
Preview

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The empty line at the beginning of the file should be removed to maintain consistent file formatting.

Suggested change


Copilot uses AI. Check for mistakes.


[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
public class FluentDataGridSelector(string initialSelector)
Copy link
Preview

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DebuggerDisplay attribute is missing the required using statement for System.Diagnostics. This will cause a compilation error.

Copilot uses AI. Check for mistakes.

{
private readonly StringBuilder _selector = new(initialSelector);
Expand Down Expand Up @@ -46,4 +48,6 @@ public FluentDataGridSelector Descendant(string selector)

public static implicit operator string(FluentDataGridSelector fluentDataGridSelector) =>
fluentDataGridSelector.ToString();

private string GetDebuggerDisplay() => ToString();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class PlaywrightFixture : IAsyncLifetime

public async Task InitializeAsync()
{
Assertions.SetDefaultExpectTimeout(10_000);
Assertions.SetDefaultExpectTimeout(30_000);

_playwright = await Playwright.CreateAsync();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,54 @@
namespace Aspire.Dashboard.ScreenCapture;
using Aspire.Hosting.ApplicationModel;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;

namespace Aspire.Dashboard.ScreenCapture;

public class PlaywrightTestsBase<TDashboardServerFixture>(AppHostTestFixture appHostTestFixture)
: IClassFixture<TDashboardServerFixture>, IAsyncDisposable
where TDashboardServerFixture : AppHostTestFixture
{
public AppHostTestFixture AppHostTestFixture { get; } = appHostTestFixture;
public PlaywrightFixture PlaywrightFixture { get; } = appHostTestFixture.PlaywrightFixture;
public string? DashboardUrl { get; private set; }
public string? DashboardUrl { get; internal set; }
public string DashboardLoginToken { get; private set; } = "";

private IBrowserContext? _context;

public Task<DistributedApplication> ConfigureAsync<TEntryPoint>(
public async Task<DistributedApplication> ConfigureAsync<TEntryPoint>(
string[]? args = null,
Action<IDistributedApplicationTestingBuilder>? configureBuilder = null) where TEntryPoint : class =>
AppHostTestFixture.ConfigureAsync<TEntryPoint>(args, builder =>
Action<IDistributedApplicationTestingBuilder>? configureBuilder = null) where TEntryPoint : class
{
var app = await AppHostTestFixture.ConfigureAsync<TEntryPoint>(args, builder =>
{
var aspNetCoreUrls = builder.Configuration["ASPNETCORE_URLS"];
var urls = aspNetCoreUrls is not null ? aspNetCoreUrls.Split(";") : [];

DashboardUrl = urls.FirstOrDefault();
DashboardLoginToken = builder.Configuration["AppHost:BrowserToken"] ?? "";

builder.Eventing.Subscribe<ResourceEndpointsAllocatedEvent>((@event, _) =>
{
if (@event.Resource.TryGetUrls(out var urls))
{
DashboardUrl = urls.FirstOrDefault()?.ToString() ?? "";
}

return Task.CompletedTask;
});

configureBuilder?.Invoke(builder);
});

var server = app.Services.GetRequiredService<IServer>();
var addresses = server.Features.Get<IServerAddressesFeature>()?.Addresses;
if (addresses is not null)
{

}
var hostUrl = app.GetEndpoint("aspire-dashboard");
Copy link
Preview

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code block from lines 40-45 appears to be unused dead code. The server variable and addresses are retrieved but never used, and the block is empty. This should be removed to improve code clarity.

Suggested change
}

Copilot uses AI. Check for mistakes.

DashboardUrl = hostUrl.ToString();

return app;
}

public async Task InteractWithPageAsync(Func<IPage, Task> test, ViewportSize? size = null)
{
var page = await CreateNewPageAsync(size);
Expand All @@ -33,6 +57,12 @@ public async Task InteractWithPageAsync(Func<IPage, Task> test, ViewportSize? si
{
await test(page);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error during test interaction: {ex.Message}");

throw;
}
finally
{
await page.CloseAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

app.MapGet("/weatherforecast", () =>
{
if (builder.Configuration.GetValue<bool>("THROW_EXCEPTION"))
if (builder.Configuration.GetValue("THROW_EXCEPTION", false))
{
throw new ApplicationException("Error processing request");
}
Expand Down
Binary file modified docs/fundamentals/dashboard/media/explore/dashboard-help.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/fundamentals/dashboard/media/explore/project-graphs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/fundamentals/dashboard/media/explore/projects.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading