LANCommander/LANCommander.Server.UI.Tests/ConfiguredServerFixture.cs
Pat Hartl 9b5195f1d6 Use web-first Playwright assertions in UI tests
Replace non-waiting Assert.True(await locator.IsVisibleAsync()) checks with
auto-retrying Assertions.Expect(locator).ToBeVisibleAsync()/ToBeHiddenAsync()
to remove flakiness against Blazor's async rendering. Table-row/field page
objects (Metadata/Roles/Users/Profile) now expose ILocator helpers instead of
Task<bool>, so deletion checks wait for the element to disappear.

Also: rename ScreenshotHelper.CaptureIfFailedAsync to CaptureAsync (xUnit v2
cannot expose the test outcome to DisposeAsync, so it always captures the final
page state), drop the unused TestConstants.ServerPort/BaseUrl now that ports
bind dynamically, and fix IClassFixture doc comments to ICollectionFixture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-23 17:51:34 -05:00

125 lines
5 KiB
C#

using LANCommander.SDK.Enums;
using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using LANCommander.Server.Services;
using LANCommander.Server.Settings.Enums;
using LANCommander.Server.UI.Tests.Pages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Playwright;
namespace LANCommander.Server.UI.Tests;
/// <summary>
/// Shared fixture that starts the server via WebApplicationFactory, programmatically creates
/// the admin user, and makes it available for all tests in the collection.
/// Shared across the "Server" collection via ICollectionFixture&lt;ConfiguredServerFixture&gt;.
/// </summary>
public class ConfiguredServerFixture : IAsyncLifetime
{
public PlaywrightFixture Playwright { get; private set; } = null!;
public UITestApplicationFactory Factory { get; private set; } = null!;
/// <summary>
/// ID of a game created via the service layer for edit tests.
/// </summary>
public Guid TestGameId { get; private set; }
public const string TestGameTitle = "Test Game";
public async Task InitializeAsync()
{
Playwright = new PlaywrightFixture();
await Playwright.InitializeAsync();
Factory = new UITestApplicationFactory();
// Trigger the factory to start the Kestrel server
_ = Factory.Services;
// Create the admin user via the service layer (before setting Provider
// so OnConfiguring doesn't try to add a conflicting SQLite provider)
using var scope = Factory.RealServices.CreateScope();
var roleService = scope.ServiceProvider.GetRequiredService<RoleService>();
var userService = scope.ServiceProvider.GetRequiredService<UserService>();
await roleService.AddAsync(new Role { Name = RoleService.AdministratorRoleName });
var user = await userService.AddAsync(new User { UserName = TestConstants.AdminUserName });
await userService.ChangePassword(user.UserName, TestConstants.AdminPassword);
await userService.AddToRoleAsync(user.UserName, RoleService.AdministratorRoleName);
// Seed default storage locations so the import dialog can initialize
var storageLocationService = scope.ServiceProvider.GetRequiredService<StorageLocationService>();
var archivePath = Path.Combine(Path.GetTempPath(), "LANCommander_UITest_Archives");
Directory.CreateDirectory(archivePath);
await storageLocationService.AddAsync(new StorageLocation
{
Path = archivePath,
Type = StorageLocationType.Archive,
Default = true
});
var savePath = Path.Combine(Path.GetTempPath(), "LANCommander_UITest_Saves");
Directory.CreateDirectory(savePath);
await storageLocationService.AddAsync(new StorageLocation
{
Path = savePath,
Type = StorageLocationType.Save,
Default = true
});
var mediaPath = Path.Combine(Path.GetTempPath(), "LANCommander_UITest_Media");
Directory.CreateDirectory(mediaPath);
await storageLocationService.AddAsync(new StorageLocation
{
Path = mediaPath,
Type = StorageLocationType.Media,
Default = true
});
// Seed a test game via the service layer for edit tests
var gameService = scope.ServiceProvider.GetRequiredService<GameService>();
var game = await gameService.AddAsync(new Game
{
Title = TestGameTitle,
Type = GameType.MainGame,
Singleplayer = true
});
TestGameId = game.Id;
// Now set the database provider so the server doesn't redirect to /FirstTimeSetup
DatabaseContext.Provider = DatabaseProvider.SQLite;
}
public async Task DisposeAsync()
{
// Reset the static provider so other tests can use a fresh state
DatabaseContext.Provider = DatabaseProvider.Unknown;
await Factory.DisposeAsync();
await Playwright.DisposeAsync();
}
/// <summary>
/// Creates a new browser context and page, already logged in as admin.
/// </summary>
public async Task<(IBrowserContext Context, IPage Page)> CreateLoggedInPageAsync()
{
var context = await Playwright.NewContextAsync(Factory.BaseAddress);
var page = await context.NewPageAsync();
var loginPage = new LoginPage(page);
await loginPage.NavigateAsync();
await loginPage.LoginAsync(TestConstants.AdminUserName, TestConstants.AdminPassword);
await page.WaitForSelectorAsync("text=Dashboard", new() { Timeout = 15000 });
return (context, page);
}
/// <summary>
/// Creates a new browser context and page (not logged in).
/// </summary>
public async Task<(IBrowserContext Context, IPage Page)> CreateAnonymousPageAsync()
{
var context = await Playwright.NewContextAsync(Factory.BaseAddress);
var page = await context.NewPageAsync();
return (context, page);
}
}