LANCommander/LANCommander.Server.UI.Tests/Tests/UserManagementTests.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

140 lines
5.1 KiB
C#

using LANCommander.Server.UI.Tests.Pages;
using Microsoft.Playwright;
using Xunit.Abstractions;
namespace LANCommander.Server.UI.Tests.Tests;
/// <summary>
/// Tests for the Settings > Users page and user registration flow.
/// </summary>
[Collection("Server")]
public class UserManagementTests : IAsyncLifetime
{
private readonly ConfiguredServerFixture _fixture;
private readonly ITestOutputHelper _output;
private IBrowserContext _context = null!;
private IPage _page = null!;
public UserManagementTests(ConfiguredServerFixture fixture, ITestOutputHelper output)
{
_fixture = fixture;
_output = output;
}
public async Task InitializeAsync()
{
(_context, _page) = await _fixture.CreateLoggedInPageAsync();
}
public async Task DisposeAsync()
{
await ScreenshotHelper.CaptureAsync(_page, _output);
if (_page != null) await _page.CloseAsync();
if (_context != null) await _context.DisposeAsync();
}
[Fact]
public async Task UsersPage_ShowsAdminUser()
{
var usersPage = new UsersPage(_page);
await usersPage.NavigateAsync();
await Assertions.Expect(usersPage.User(TestConstants.AdminUserName)).ToBeVisibleAsync();
}
[Fact]
public async Task UsersPage_CanSearchUsers()
{
var usersPage = new UsersPage(_page);
await usersPage.NavigateAsync();
await usersPage.SearchUsersAsync(TestConstants.AdminUserName);
await Assertions.Expect(usersPage.User(TestConstants.AdminUserName)).ToBeVisibleAsync();
// After searching for "admin", the admin user must be in the results
var count = await usersPage.GetUserCountAsync();
Assert.True(count >= 1, $"Expected at least 1 user matching 'admin', got {count}");
}
[Fact]
public async Task UsersPage_ShowsUserRoles()
{
var usersPage = new UsersPage(_page);
await usersPage.NavigateAsync();
var roles = await usersPage.GetUserRolesTextAsync(TestConstants.AdminUserName);
Assert.Contains("Administrator", roles);
}
[Fact]
public async Task Register_NewUser_AppearsInUserList()
{
var testUserName = $"testuser_{Guid.NewGuid().ToString()[..8]}";
// Register a new user via the public Register page using a separate anonymous context
var (anonContext, anonPage) = await _fixture.CreateAnonymousPageAsync();
try
{
await anonPage.GotoAsync("/Register");
await anonPage.WaitForSelectorAsync("#login-submit", new() { Timeout = 10000 });
await anonPage.Locator("input[name='Model.UserName']").FillAsync(testUserName);
await anonPage.Locator("input[name='Model.Password']").FillAsync(TestConstants.AdminPassword);
await anonPage.Locator("input[name='Model.PasswordConfirmation']").FillAsync(TestConstants.AdminPassword);
await anonPage.GetByRole(AriaRole.Button, new() { Name = "Register" }).ClickAsync();
// Wait for registration to complete (redirects to home)
await anonPage.WaitForURLAsync("**/", new() { Timeout = 10000 });
}
finally
{
await anonPage.CloseAsync();
await anonContext.DisposeAsync();
}
// Navigate to users page as admin and verify the new user appears
var usersPage = new UsersPage(_page);
await usersPage.NavigateAsync();
await Assertions.Expect(usersPage.User(testUserName)).ToBeVisibleAsync();
}
[Fact]
public async Task UsersPage_CanDeleteUser()
{
var testUserName = $"deluser_{Guid.NewGuid().ToString()[..8]}";
// Create a user via the Register page
var (anonContext, anonPage) = await _fixture.CreateAnonymousPageAsync();
try
{
await anonPage.GotoAsync("/Register");
await anonPage.WaitForSelectorAsync("#login-submit", new() { Timeout = 10000 });
await anonPage.Locator("input[name='Model.UserName']").FillAsync(testUserName);
await anonPage.Locator("input[name='Model.Password']").FillAsync(TestConstants.AdminPassword);
await anonPage.Locator("input[name='Model.PasswordConfirmation']").FillAsync(TestConstants.AdminPassword);
await anonPage.GetByRole(AriaRole.Button, new() { Name = "Register" }).ClickAsync();
await anonPage.WaitForURLAsync("**/", new() { Timeout = 10000 });
}
finally
{
await anonPage.CloseAsync();
await anonContext.DisposeAsync();
}
// Navigate to users page as admin
var usersPage = new UsersPage(_page);
await usersPage.NavigateAsync();
// Verify user exists before deletion
await Assertions.Expect(usersPage.User(testUserName)).ToBeVisibleAsync();
// Delete the user
await usersPage.DeleteUserAsync(testUserName);
// Verify user is gone
await Assertions.Expect(usersPage.User(testUserName)).ToBeHiddenAsync();
}
}