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>
This commit is contained in:
parent
77b0f04e4d
commit
9b5195f1d6
18 changed files with 109 additions and 122 deletions
|
|
@ -11,8 +11,8 @@ 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 a class.
|
||||
/// Used via IClassFixture<ConfiguredServerFixture>.
|
||||
/// 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,11 +44,14 @@ public class MetadataPage
|
|||
await _page.WaitForSelectorAsync(".ant-modal", new() { State = WaitForSelectorState.Hidden, Timeout = 5000 });
|
||||
}
|
||||
|
||||
public async Task<bool> IsItemVisibleAsync(string name)
|
||||
/// <summary>
|
||||
/// Locator for an item row's name cell within the table body.
|
||||
/// Use with web-first assertions, e.g. Expect(page.Item("Action")).ToBeVisibleAsync().
|
||||
/// </summary>
|
||||
public ILocator Item(string name)
|
||||
{
|
||||
// Look for the name text within the table body
|
||||
var cell = _page.Locator(".ant-table-tbody").GetByText(name, new() { Exact = true });
|
||||
return await cell.IsVisibleAsync();
|
||||
return _page.Locator(".ant-table-tbody").GetByText(name, new() { Exact = true });
|
||||
}
|
||||
|
||||
public async Task EditItemAsync(string oldName, string newName)
|
||||
|
|
|
|||
|
|
@ -28,11 +28,14 @@ public class ProfilePage
|
|||
return await input.InputValueAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> HasFieldAsync(string label)
|
||||
/// <summary>
|
||||
/// Locator for a form field (text or password) with the given label.
|
||||
/// Use with web-first assertions, e.g. Expect(page.Field("Username")).ToBeVisibleAsync().
|
||||
/// </summary>
|
||||
public ILocator Field(string label)
|
||||
{
|
||||
return await _page.Locator(".ant-form-item")
|
||||
.Filter(new() { HasText = label })
|
||||
.IsVisibleAsync();
|
||||
return _page.Locator(".ant-form-item")
|
||||
.Filter(new() { HasText = label });
|
||||
}
|
||||
|
||||
public async Task SetAliasAsync(string alias)
|
||||
|
|
@ -69,13 +72,6 @@ public class ProfilePage
|
|||
await _page.WaitForSelectorAsync("text=Change Password", new() { Timeout = 10000 });
|
||||
}
|
||||
|
||||
public async Task<bool> HasPasswordFieldAsync(string label)
|
||||
{
|
||||
return await _page.Locator(".ant-form-item")
|
||||
.Filter(new() { HasText = label })
|
||||
.IsVisibleAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the change password form and submits it.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -47,12 +47,12 @@ public class RolesPage
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a role with the given name appears in the table.
|
||||
/// Locator for a role row with the given name.
|
||||
/// Use with web-first assertions, e.g. Expect(page.Role("Administrator")).ToBeVisibleAsync().
|
||||
/// </summary>
|
||||
public async Task<bool> IsRoleVisibleAsync(string name)
|
||||
public ILocator Role(string name)
|
||||
{
|
||||
var row = _page.Locator("table tbody tr").Filter(new() { HasText = name });
|
||||
return await row.CountAsync() > 0;
|
||||
return _page.Locator("table tbody tr").Filter(new() { HasText = name });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -49,11 +49,12 @@ public class UsersPage
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a given username appears in a table row.
|
||||
/// Locator for a table row containing the given username.
|
||||
/// Use with web-first assertions, e.g. Expect(page.User("admin")).ToBeVisibleAsync().
|
||||
/// </summary>
|
||||
public async Task<bool> IsUserVisibleAsync(string username)
|
||||
public ILocator User(string username)
|
||||
{
|
||||
return await _page.Locator("tr.ant-table-row", new() { HasTextString = username }).CountAsync() > 0;
|
||||
return _page.Locator("tr.ant-table-row", new() { HasTextString = username });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ public class PlaywrightFixture : IAsyncLifetime
|
|||
Playwright.Dispose();
|
||||
}
|
||||
|
||||
public async Task<IBrowserContext> NewContextAsync(string? baseUrl = null)
|
||||
public async Task<IBrowserContext> NewContextAsync(string baseUrl)
|
||||
{
|
||||
return await Browser.NewContextAsync(new BrowserNewContextOptions
|
||||
{
|
||||
IgnoreHTTPSErrors = true,
|
||||
BaseURL = baseUrl ?? TestConstants.BaseUrl,
|
||||
BaseURL = baseUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ using Xunit.Abstractions;
|
|||
namespace LANCommander.Server.UI.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Captures a full-page screenshot when a test fails.
|
||||
/// Screenshots are saved to a "Screenshots" directory that CI uploads as an artifact.
|
||||
/// Captures a full-page screenshot of the final page state at the end of each test.
|
||||
/// Screenshots are saved to a "Screenshots" directory that CI uploads as an artifact,
|
||||
/// making failures easy to diagnose. (xUnit v2 does not expose the test outcome to
|
||||
/// DisposeAsync, so we capture unconditionally and name each file after the test.)
|
||||
/// </summary>
|
||||
public static class ScreenshotHelper
|
||||
{
|
||||
|
|
@ -16,17 +18,14 @@ public static class ScreenshotHelper
|
|||
string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Captures a screenshot of the current page state.
|
||||
/// Call this in DisposeAsync — it extracts the test name from ITestOutputHelper.
|
||||
/// Captures a screenshot of the current page state, named after the running test.
|
||||
/// Call this from DisposeAsync — it extracts the test name from ITestOutputHelper.
|
||||
/// </summary>
|
||||
public static async Task CaptureIfFailedAsync(IPage? page, ITestOutputHelper? output)
|
||||
public static async Task CaptureAsync(IPage? page, ITestOutputHelper? output)
|
||||
{
|
||||
if (page == null || output == null)
|
||||
return;
|
||||
|
||||
// xUnit only writes to ITestOutputHelper when a test fails or produces output.
|
||||
// We always capture since DisposeAsync doesn't know the test result,
|
||||
// but name the file so it's easy to correlate.
|
||||
var testName = GetTestDisplayName(output) ?? $"Unknown_{Guid.NewGuid():N}";
|
||||
|
||||
try
|
||||
|
|
|
|||
|
|
@ -4,6 +4,4 @@ public static class TestConstants
|
|||
{
|
||||
public const string AdminUserName = "admin";
|
||||
public const string AdminPassword = "Password1234!";
|
||||
public const int ServerPort = 1337;
|
||||
public static string BaseUrl => $"http://localhost:{ServerPort}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace LANCommander.Server.UI.Tests.Tests;
|
|||
/// Tests for navigating around key parts of the admin application.
|
||||
/// These tests verify that the main admin pages are accessible and render correctly
|
||||
/// after logging in as an administrator.
|
||||
/// Uses IClassFixture to start the server once for all tests in this class.
|
||||
/// Uses the shared "Server" collection fixture so the server starts once for the whole collection.
|
||||
/// </summary>
|
||||
[Collection("Server")]
|
||||
public class AdminNavigationTests : IAsyncLifetime
|
||||
|
|
@ -31,7 +31,7 @@ public class AdminNavigationTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -43,9 +43,9 @@ public class AdminNavigationTests : IAsyncLifetime
|
|||
Assert.True(await dashboard.IsDisplayedAsync());
|
||||
|
||||
// Dashboard should show playtime charts
|
||||
Assert.True(await _page.GetByText("Top 10 Total Playtime (By Player)").IsVisibleAsync());
|
||||
Assert.True(await _page.GetByText("Top 10 Total Playtime (By Game)").IsVisibleAsync());
|
||||
Assert.True(await _page.GetByText("Top Average Session Length (By Game)").IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Top 10 Total Playtime (By Player)")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByText("Top 10 Total Playtime (By Game)")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByText("Top Average Session Length (By Game)")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -71,11 +71,11 @@ public class AdminNavigationTests : IAsyncLifetime
|
|||
await dashboard.NavigateToGamesAsync();
|
||||
|
||||
Assert.Contains("/Games", _page.Url);
|
||||
Assert.True(await _page.GetByText("Games").First.IsVisibleAsync());
|
||||
Assert.True(await _page.GetByRole(AriaRole.Button, new() { Name = "Add Game" }).IsVisibleAsync());
|
||||
Assert.True(await _page.GetByRole(AriaRole.Button, new() { Name = "Import" }).IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Games").First).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Add Game" })).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Import" })).ToBeVisibleAsync();
|
||||
// Empty table should show "No data"
|
||||
Assert.True(await _page.GetByText("No data").IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("No data")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -85,7 +85,7 @@ public class AdminNavigationTests : IAsyncLifetime
|
|||
await dashboard.NavigateToRedistributablesAsync();
|
||||
|
||||
Assert.Contains("/Redistributables", _page.Url);
|
||||
Assert.True(await _page.GetByText("Redistributables").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Redistributables").First).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -95,7 +95,7 @@ public class AdminNavigationTests : IAsyncLifetime
|
|||
await dashboard.NavigateToToolsAsync();
|
||||
|
||||
Assert.Contains("/Tools", _page.Url);
|
||||
Assert.True(await _page.GetByText("Tools").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Tools").First).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -105,7 +105,7 @@ public class AdminNavigationTests : IAsyncLifetime
|
|||
await dashboard.NavigateToServersAsync();
|
||||
|
||||
Assert.Contains("/Servers", _page.Url);
|
||||
Assert.True(await _page.GetByText("Servers").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Servers").First).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -134,7 +134,7 @@ public class AdminNavigationTests : IAsyncLifetime
|
|||
|
||||
Assert.Contains("/Settings/General", _page.Url);
|
||||
// Verify settings-specific content is visible (Database Provider is unique to General settings)
|
||||
Assert.True(await _page.GetByText("Database Provider").IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Database Provider")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -151,9 +151,7 @@ public class AdminNavigationTests : IAsyncLifetime
|
|||
|
||||
foreach (var setting in expectedSettings)
|
||||
{
|
||||
Assert.True(
|
||||
await _page.GetByRole(AriaRole.Link, new() { Name = setting, Exact = true }).IsVisibleAsync(),
|
||||
$"Settings menu should contain '{setting}'");
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Link, new() { Name = setting, Exact = true })).ToBeVisibleAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public class FirstTimeSetupTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -54,11 +54,11 @@ public class FirstTimeSetupTests : IAsyncLifetime
|
|||
await setupPage.NavigateAsync();
|
||||
|
||||
// Verify all 4 steps are visible in the wizard
|
||||
Assert.True(await _page.GetByText("Database").First.IsVisibleAsync());
|
||||
Assert.True(await _page.GetByText("Paths").IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Database").First).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByText("Paths")).ToBeVisibleAsync();
|
||||
// "Metadata" may be truncated in UI to "Metad" but the text node still exists
|
||||
Assert.True(await _page.Locator("text=/Metad/").First.IsVisibleAsync());
|
||||
Assert.True(await _page.GetByText("Administrator").IsVisibleAsync());
|
||||
await Assertions.Expect(_page.Locator("text=/Metad/").First).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByText("Administrator")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -74,9 +74,9 @@ public class FirstTimeSetupTests : IAsyncLifetime
|
|||
await _page.WaitForSelectorAsync("[role='listbox']", new() { Timeout = 5000 });
|
||||
|
||||
// Verify all expected providers are shown
|
||||
Assert.True(await _page.GetByRole(AriaRole.Option, new() { Name = "SQLite" }).IsVisibleAsync());
|
||||
Assert.True(await _page.GetByRole(AriaRole.Option, new() { Name = "MySQL" }).IsVisibleAsync());
|
||||
Assert.True(await _page.GetByRole(AriaRole.Option, new() { Name = "PostgreSQL" }).IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Option, new() { Name = "SQLite" })).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Option, new() { Name = "MySQL" })).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Option, new() { Name = "PostgreSQL" })).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact(Skip = "Requires real database and file I/O - not supported with in-memory WebApplicationFactory")]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class GameEditTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class GameImportTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -44,8 +44,8 @@ public class GameImportTests : IAsyncLifetime
|
|||
await gamesPage.NavigateAsync();
|
||||
|
||||
// Verify the page structure is correct (table area and buttons are present)
|
||||
Assert.True(await _page.GetByRole(AriaRole.Button, new() { Name = "Add Game" }).IsVisibleAsync());
|
||||
Assert.True(await _page.GetByRole(AriaRole.Button, new() { Name = "Import" }).IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Add Game" })).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Import" })).ToBeVisibleAsync();
|
||||
|
||||
// The table should render (with "No data" if empty, or rows if a prior test imported)
|
||||
var count = await gamesPage.GetGameCountAsync();
|
||||
|
|
@ -59,7 +59,7 @@ public class GameImportTests : IAsyncLifetime
|
|||
await gamesPage.NavigateAsync();
|
||||
|
||||
var importButton = _page.GetByRole(AriaRole.Button, new() { Name = "Import" });
|
||||
Assert.True(await importButton.IsVisibleAsync());
|
||||
await Assertions.Expect(importButton).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace LANCommander.Server.UI.Tests.Tests;
|
|||
/// <summary>
|
||||
/// Tests for the login flow against a server that has already been configured.
|
||||
/// These tests assume the server is running with a known admin user.
|
||||
/// Uses IClassFixture to start the server once for all tests in this class.
|
||||
/// Uses the shared "Server" collection fixture so the server starts once for the whole collection.
|
||||
/// </summary>
|
||||
[Collection("Server")]
|
||||
public class LoginTests : IAsyncLifetime
|
||||
|
|
@ -30,7 +30,7 @@ public class LoginTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ public class LoginTests : IAsyncLifetime
|
|||
|
||||
Assert.True(await loginPage.IsDisplayedAsync());
|
||||
Assert.True(await loginPage.HasRegisterLinkAsync());
|
||||
Assert.True(await _page.GetByRole(AriaRole.Button, new() { Name = "Login" }).IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Login" })).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class MetadataTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ public class MetadataTests : IAsyncLifetime
|
|||
await tagsPage.NavigateAsync();
|
||||
|
||||
Assert.Equal(0, await tagsPage.GetItemCountAsync());
|
||||
Assert.True(await _page.GetByText("No data").IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("No data")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -65,7 +65,7 @@ public class MetadataTests : IAsyncLifetime
|
|||
|
||||
await tagsPage.AddItemAsync("Action");
|
||||
|
||||
Assert.True(await tagsPage.IsItemVisibleAsync("Action"));
|
||||
await Assertions.Expect(tagsPage.Item("Action")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -75,10 +75,10 @@ public class MetadataTests : IAsyncLifetime
|
|||
await tagsPage.NavigateAsync();
|
||||
|
||||
await tagsPage.AddItemAsync("Puzzle");
|
||||
Assert.True(await tagsPage.IsItemVisibleAsync("Puzzle"));
|
||||
await Assertions.Expect(tagsPage.Item("Puzzle")).ToBeVisibleAsync();
|
||||
|
||||
await tagsPage.EditItemAsync("Puzzle", "Puzzle Games");
|
||||
Assert.True(await tagsPage.IsItemVisibleAsync("Puzzle Games"));
|
||||
await Assertions.Expect(tagsPage.Item("Puzzle Games")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -88,10 +88,10 @@ public class MetadataTests : IAsyncLifetime
|
|||
await tagsPage.NavigateAsync();
|
||||
|
||||
await tagsPage.AddItemAsync("Temporary");
|
||||
Assert.True(await tagsPage.IsItemVisibleAsync("Temporary"));
|
||||
await Assertions.Expect(tagsPage.Item("Temporary")).ToBeVisibleAsync();
|
||||
|
||||
await tagsPage.DeleteItemAsync("Temporary");
|
||||
Assert.False(await tagsPage.IsItemVisibleAsync("Temporary"));
|
||||
await Assertions.Expect(tagsPage.Item("Temporary")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
// --- Genres ---
|
||||
|
|
@ -104,7 +104,7 @@ public class MetadataTests : IAsyncLifetime
|
|||
|
||||
await genresPage.AddItemAsync("RPG");
|
||||
|
||||
Assert.True(await genresPage.IsItemVisibleAsync("RPG"));
|
||||
await Assertions.Expect(genresPage.Item("RPG")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -114,10 +114,10 @@ public class MetadataTests : IAsyncLifetime
|
|||
await genresPage.NavigateAsync();
|
||||
|
||||
await genresPage.AddItemAsync("Strategy");
|
||||
Assert.True(await genresPage.IsItemVisibleAsync("Strategy"));
|
||||
await Assertions.Expect(genresPage.Item("Strategy")).ToBeVisibleAsync();
|
||||
|
||||
await genresPage.DeleteItemAsync("Strategy");
|
||||
Assert.False(await genresPage.IsItemVisibleAsync("Strategy"));
|
||||
await Assertions.Expect(genresPage.Item("Strategy")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
// --- Platforms ---
|
||||
|
|
@ -130,7 +130,7 @@ public class MetadataTests : IAsyncLifetime
|
|||
|
||||
await platformsPage.AddItemAsync("Windows");
|
||||
|
||||
Assert.True(await platformsPage.IsItemVisibleAsync("Windows"));
|
||||
await Assertions.Expect(platformsPage.Item("Windows")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -140,9 +140,9 @@ public class MetadataTests : IAsyncLifetime
|
|||
await platformsPage.NavigateAsync();
|
||||
|
||||
await platformsPage.AddItemAsync("Linux");
|
||||
Assert.True(await platformsPage.IsItemVisibleAsync("Linux"));
|
||||
await Assertions.Expect(platformsPage.Item("Linux")).ToBeVisibleAsync();
|
||||
|
||||
await platformsPage.DeleteItemAsync("Linux");
|
||||
Assert.False(await platformsPage.IsItemVisibleAsync("Linux"));
|
||||
await Assertions.Expect(platformsPage.Item("Linux")).ToBeHiddenAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace LANCommander.Server.UI.Tests.Tests;
|
|||
|
||||
/// <summary>
|
||||
/// Tests for the user profile and change password pages.
|
||||
/// Uses IClassFixture to share the server instance across all tests in this class.
|
||||
/// Uses the shared "Server" collection fixture so the server instance is shared across the collection.
|
||||
/// </summary>
|
||||
[Collection("Server")]
|
||||
public class ProfileTests : IAsyncLifetime
|
||||
|
|
@ -29,7 +29,7 @@ public class ProfileTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -51,12 +51,10 @@ public class ProfileTests : IAsyncLifetime
|
|||
var profilePage = new ProfilePage(_page);
|
||||
await profilePage.NavigateAsync();
|
||||
|
||||
Assert.True(await profilePage.HasFieldAsync("Username"), "Username field should be visible");
|
||||
Assert.True(await profilePage.HasFieldAsync("Alias"), "Alias field should be visible");
|
||||
Assert.True(await profilePage.HasFieldAsync("Email Address"), "Email Address field should be visible");
|
||||
Assert.True(
|
||||
await _page.GetByRole(AriaRole.Button, new() { Name = "Save" }).IsVisibleAsync(),
|
||||
"Save button should be visible");
|
||||
await Assertions.Expect(profilePage.Field("Username")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(profilePage.Field("Alias")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(profilePage.Field("Email Address")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Save" })).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -88,12 +86,10 @@ public class ProfileTests : IAsyncLifetime
|
|||
var profilePage = new ProfilePage(_page);
|
||||
await profilePage.NavigateToChangePasswordAsync();
|
||||
|
||||
Assert.True(await profilePage.HasPasswordFieldAsync("Current Password"), "Current Password field should be visible");
|
||||
Assert.True(await profilePage.HasPasswordFieldAsync("New Password"), "New Password field should be visible");
|
||||
Assert.True(await profilePage.HasPasswordFieldAsync("Confirm Password"), "Confirm Password field should be visible");
|
||||
Assert.True(
|
||||
await _page.GetByRole(AriaRole.Button, new() { Name = "Change" }).IsVisibleAsync(),
|
||||
"Change button should be visible");
|
||||
await Assertions.Expect(profilePage.Field("Current Password")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(profilePage.Field("New Password")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(profilePage.Field("Confirm Password")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Change" })).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public class RoleManagementTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -41,8 +41,7 @@ public class RoleManagementTests : IAsyncLifetime
|
|||
var rolesPage = new RolesPage(_page);
|
||||
await rolesPage.NavigateAsync();
|
||||
|
||||
Assert.True(await rolesPage.IsRoleVisibleAsync("Administrator"),
|
||||
"Administrator role should be visible in the roles table");
|
||||
await Assertions.Expect(rolesPage.Role("Administrator")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -53,8 +52,7 @@ public class RoleManagementTests : IAsyncLifetime
|
|||
|
||||
await rolesPage.AddRoleAsync("TestRole");
|
||||
|
||||
Assert.True(await rolesPage.IsRoleVisibleAsync("TestRole"),
|
||||
"Newly added TestRole should appear in the roles table");
|
||||
await Assertions.Expect(rolesPage.Role("TestRole")).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -65,14 +63,12 @@ public class RoleManagementTests : IAsyncLifetime
|
|||
|
||||
// Add a role to delete
|
||||
await rolesPage.AddRoleAsync("RoleToDelete");
|
||||
Assert.True(await rolesPage.IsRoleVisibleAsync("RoleToDelete"),
|
||||
"RoleToDelete should be visible before deletion");
|
||||
await Assertions.Expect(rolesPage.Role("RoleToDelete")).ToBeVisibleAsync();
|
||||
|
||||
// Delete the role
|
||||
await rolesPage.DeleteRoleAsync("RoleToDelete");
|
||||
|
||||
Assert.False(await rolesPage.IsRoleVisibleAsync("RoleToDelete"),
|
||||
"RoleToDelete should no longer appear after deletion");
|
||||
await Assertions.Expect(rolesPage.Role("RoleToDelete")).ToBeHiddenAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class SettingsTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -41,9 +41,9 @@ public class SettingsTests : IAsyncLifetime
|
|||
await settings.NavigateToGeneralAsync();
|
||||
|
||||
Assert.Contains("/Settings/General", _page.Url);
|
||||
Assert.True(await _page.GetByText("Database Provider").IsVisibleAsync());
|
||||
Assert.True(await _page.GetByText("Port").First.IsVisibleAsync());
|
||||
Assert.True(await _page.GetByRole(AriaRole.Button, new() { Name = "Save" }).IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Database Provider")).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByText("Port").First).ToBeVisibleAsync();
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Save" })).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -58,7 +58,7 @@ public class SettingsTests : IAsyncLifetime
|
|||
// The admin user created during fixture setup should appear in the table
|
||||
var adminCell = _page.Locator("table").GetByText(TestConstants.AdminUserName).First;
|
||||
await adminCell.WaitForAsync(new() { Timeout = 15000 });
|
||||
Assert.True(await adminCell.IsVisibleAsync());
|
||||
await Assertions.Expect(adminCell).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -68,12 +68,12 @@ public class SettingsTests : IAsyncLifetime
|
|||
await settings.NavigateToRolesAsync();
|
||||
|
||||
Assert.Contains("/Settings/Roles", _page.Url);
|
||||
Assert.True(await _page.GetByRole(AriaRole.Button, new() { Name = "Add Role" }).IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByRole(AriaRole.Button, new() { Name = "Add Role" })).ToBeVisibleAsync();
|
||||
// Wait for the data table to render then check for the Administrator role
|
||||
await _page.Locator("table").First.WaitForAsync(new() { Timeout = 15000 });
|
||||
var adminRole = _page.Locator("table").GetByText("Administrator");
|
||||
await adminRole.WaitForAsync(new() { Timeout = 15000 });
|
||||
Assert.True(await adminRole.IsVisibleAsync());
|
||||
await Assertions.Expect(adminRole).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -83,7 +83,7 @@ public class SettingsTests : IAsyncLifetime
|
|||
await settings.NavigateToAuthenticationAsync();
|
||||
|
||||
Assert.Contains("/Settings/Authentication", _page.Url);
|
||||
Assert.True(await _page.GetByText("Authentication").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Authentication").First).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -93,7 +93,7 @@ public class SettingsTests : IAsyncLifetime
|
|||
await settings.NavigateToArchivesAsync();
|
||||
|
||||
Assert.Contains("/Settings/Archives", _page.Url);
|
||||
Assert.True(await _page.GetByText("Archives").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Archives").First).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -103,7 +103,7 @@ public class SettingsTests : IAsyncLifetime
|
|||
await settings.NavigateToMediaAsync();
|
||||
|
||||
Assert.Contains("/Settings/Media", _page.Url);
|
||||
Assert.True(await _page.GetByText("Media").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Media").First).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -114,7 +114,7 @@ public class SettingsTests : IAsyncLifetime
|
|||
await settings.NavigateToBeaconAsync();
|
||||
|
||||
Assert.Contains("/Settings/Beacon", _page.Url);
|
||||
Assert.True(await _page.GetByText("Beacon").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Beacon").First).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -124,7 +124,7 @@ public class SettingsTests : IAsyncLifetime
|
|||
await settings.NavigateToUpdatesAsync();
|
||||
|
||||
Assert.Contains("/Settings/Updates", _page.Url);
|
||||
Assert.True(await _page.GetByText("Updates").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Updates").First).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -134,6 +134,6 @@ public class SettingsTests : IAsyncLifetime
|
|||
await settings.NavigateToAppearanceAsync();
|
||||
|
||||
Assert.Contains("/Settings/Appearance", _page.Url);
|
||||
Assert.True(await _page.GetByText("Appearance").First.IsVisibleAsync());
|
||||
await Assertions.Expect(_page.GetByText("Appearance").First).ToBeVisibleAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class UserManagementTests : IAsyncLifetime
|
|||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await ScreenshotHelper.CaptureIfFailedAsync(_page, _output);
|
||||
await ScreenshotHelper.CaptureAsync(_page, _output);
|
||||
if (_page != null) await _page.CloseAsync();
|
||||
if (_context != null) await _context.DisposeAsync();
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ public class UserManagementTests : IAsyncLifetime
|
|||
var usersPage = new UsersPage(_page);
|
||||
await usersPage.NavigateAsync();
|
||||
|
||||
Assert.True(await usersPage.IsUserVisibleAsync(TestConstants.AdminUserName));
|
||||
await Assertions.Expect(usersPage.User(TestConstants.AdminUserName)).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -50,7 +50,7 @@ public class UserManagementTests : IAsyncLifetime
|
|||
|
||||
await usersPage.SearchUsersAsync(TestConstants.AdminUserName);
|
||||
|
||||
Assert.True(await usersPage.IsUserVisibleAsync(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}");
|
||||
|
|
@ -96,7 +96,7 @@ public class UserManagementTests : IAsyncLifetime
|
|||
var usersPage = new UsersPage(_page);
|
||||
await usersPage.NavigateAsync();
|
||||
|
||||
Assert.True(await usersPage.IsUserVisibleAsync(testUserName));
|
||||
await Assertions.Expect(usersPage.User(testUserName)).ToBeVisibleAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -129,12 +129,12 @@ public class UserManagementTests : IAsyncLifetime
|
|||
await usersPage.NavigateAsync();
|
||||
|
||||
// Verify user exists before deletion
|
||||
Assert.True(await usersPage.IsUserVisibleAsync(testUserName));
|
||||
await Assertions.Expect(usersPage.User(testUserName)).ToBeVisibleAsync();
|
||||
|
||||
// Delete the user
|
||||
await usersPage.DeleteUserAsync(testUserName);
|
||||
|
||||
// Verify user is gone
|
||||
Assert.False(await usersPage.IsUserVisibleAsync(testUserName));
|
||||
await Assertions.Expect(usersPage.User(testUserName)).ToBeHiddenAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue