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>
103 lines
3.3 KiB
C#
103 lines
3.3 KiB
C#
using Microsoft.Playwright;
|
|
|
|
namespace LANCommander.Server.UI.Tests.Pages;
|
|
|
|
/// <summary>
|
|
/// Page object for the user profile page at /Profile and the change password page at /Profile/ChangePassword.
|
|
/// </summary>
|
|
public class ProfilePage
|
|
{
|
|
private readonly IPage _page;
|
|
|
|
public ProfilePage(IPage page)
|
|
{
|
|
_page = page;
|
|
}
|
|
|
|
public async Task NavigateAsync()
|
|
{
|
|
await _page.GotoAsync("/Profile");
|
|
await _page.WaitForSelectorAsync("text=Profile", new() { Timeout = 10000 });
|
|
}
|
|
|
|
public async Task<string?> GetUsernameAsync()
|
|
{
|
|
var input = _page.Locator(".ant-form-item")
|
|
.Filter(new() { HasText = "Username" })
|
|
.Locator("input");
|
|
return await input.InputValueAsync();
|
|
}
|
|
|
|
/// <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 _page.Locator(".ant-form-item")
|
|
.Filter(new() { HasText = label });
|
|
}
|
|
|
|
public async Task SetAliasAsync(string alias)
|
|
{
|
|
var input = _page.Locator(".ant-form-item")
|
|
.Filter(new() { HasText = "Alias" })
|
|
.Locator("input");
|
|
await input.ClickAsync();
|
|
await input.PressAsync("Control+a");
|
|
await input.TypeAsync(alias);
|
|
// Blur to ensure change event fires
|
|
await input.PressAsync("Tab");
|
|
}
|
|
|
|
public async Task<string?> GetAliasAsync()
|
|
{
|
|
var input = _page.Locator(".ant-form-item")
|
|
.Filter(new() { HasText = "Alias" })
|
|
.Locator("input");
|
|
return await input.InputValueAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clicks the Save button. Note: saving the profile triggers a redirect to /Logout?force=true.
|
|
/// </summary>
|
|
public async Task SaveAsync()
|
|
{
|
|
await _page.GetByRole(AriaRole.Button, new() { Name = "Save" }).ClickAsync();
|
|
}
|
|
|
|
public async Task NavigateToChangePasswordAsync()
|
|
{
|
|
await _page.GotoAsync("/Profile/ChangePassword");
|
|
await _page.WaitForSelectorAsync("text=Change Password", new() { Timeout = 10000 });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fills the change password form and submits it.
|
|
/// </summary>
|
|
public async Task ChangePasswordAsync(string currentPassword, string newPassword)
|
|
{
|
|
// Fill Current Password if visible
|
|
var currentPasswordField = _page.Locator(".ant-form-item")
|
|
.Filter(new() { HasText = "Current Password" })
|
|
.Locator("input");
|
|
|
|
if (await currentPasswordField.IsVisibleAsync())
|
|
await currentPasswordField.FillAsync(currentPassword);
|
|
|
|
// Fill New Password
|
|
await _page.Locator(".ant-form-item")
|
|
.Filter(new() { HasText = "New Password" })
|
|
.Locator("input")
|
|
.FillAsync(newPassword);
|
|
|
|
// Fill Confirm Password
|
|
await _page.Locator(".ant-form-item")
|
|
.Filter(new() { HasText = "Confirm Password" })
|
|
.Locator("input")
|
|
.FillAsync(newPassword);
|
|
|
|
// Click Change button
|
|
await _page.GetByRole(AriaRole.Button, new() { Name = "Change" }).ClickAsync();
|
|
}
|
|
}
|