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>
125 lines
5 KiB
C#
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<ConfiguredServerFixture>.
|
|
/// </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);
|
|
}
|
|
}
|