File: tests\Shared\Playwright\DashboardResourceAssertions.cs
Web Access
Project: src\tests\Aspire.Dashboard.Playwright.Tests\Aspire.Dashboard.Playwright.Tests.csproj (Aspire.Dashboard.Playwright.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Playwright;
using Xunit;
 
namespace Aspire.Templates.Tests;
 
public static class DashboardResourceAssertions
{
    public static async Task<ResourceRow[]> CheckResourcesAsync(
        WrapperForIPage dashboardPageWrapper,
        IEnumerable<ResourceRow> expectedResources,
        ITestOutputHelper testOutput,
        int timeoutSecs)
    {
        testOutput.WriteLine("Waiting for resources to appear on the dashboard");
        var page = dashboardPageWrapper.Page;
        var originalUrl = page.Url;
        var expectedRows = expectedResources.ToDictionary(resource => resource.Name);
        var foundRows = new Dictionary<string, ResourceRow>();
        var reloadAttempts = 0;
        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSecs));
 
        while (foundRows.Count < expectedRows.Count && !cts.IsCancellationRequested)
        {
            if (dashboardPageWrapper.HasErrors)
            {
                if (reloadAttempts >= 3)
                {
                    throw new InvalidOperationException($"Failed to load dashboard page after {reloadAttempts} attempts");
                }
 
                testOutput.WriteLine("Reloading dashboard page");
                await dashboardPageWrapper.ReloadAsync(new PageReloadOptions { WaitUntil = WaitUntilState.Load });
                reloadAttempts++;
            }
 
            // Summary and Graph do not list resources. The sidebar retains names and states at every density,
            // but source metadata is only consistently available in the individual resource's status panel.
            foreach (var row in await page.Locator(".resource-row[data-resource-name]").AllAsync())
            {
                var name = (await row.Locator(".resource-row-name").InnerTextAsync()).Trim();
                if (!expectedRows.TryGetValue(name, out var expected))
                {
                    Assert.Fail($"Row with unknown name found: '{name}'. Expected values: {string.Join(", ", expectedRows.Keys)}");
                }
                if (foundRows.ContainsKey(name))
                {
                    continue;
                }
 
                var state = (await row.Locator(".resource-row-sub > .state-dot + span").InnerTextAsync()).Trim();
                if (expected.State != state && state != "Finished" && !state.Contains("failed", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }
                Assert.Equal(expected.State, state);
 
                var resourceName = await row.GetAttributeAsync("data-resource-name");
                Assert.NotNull(resourceName);
                await dashboardPageWrapper.GotoAsync(new Uri(new Uri(originalUrl), $"/resources/{Uri.EscapeDataString(resourceName)}").AbsoluteUri);
                var source = page.Locator(".overview-properties dd.overview-mono");
                await Assertions.Expect(source).ToBeVisibleAsync();
                Assert.Contains(expected.SourceContains, await source.GetAttributeAsync("data-tooltip"));
 
                foundRows.Add(name, expected);
            }
 
            if (foundRows.Count < expectedRows.Count)
            {
                await Task.Delay(500);
            }
        }
 
        if (foundRows.Count != expectedRows.Count)
        {
            Assert.Fail($"Expected rows not found: {string.Join(", ", expectedRows.Keys.Except(foundRows.Keys))}");
        }
 
        if (page.Url != originalUrl)
        {
            await dashboardPageWrapper.GotoAsync(originalUrl);
        }
 
        return foundRows.Values.ToArray();
    }
}
 
public sealed record ResourceRow(string Type, string Name, string State, string SourceContains);