LANCommander/LANCommander.Server.UI.Tests/Pages/GameEditPage.cs
Aaron Powell 952af65b2c Fix CI: decouple GameEditTests from import, add test-reporter
- GameEditTests now use a game seeded via the service layer in
  ConfiguredServerFixture instead of importing via the UI.
  This eliminates the Blazor InputFile compatibility issue on Linux CI.
- GamesPage.ImportGameAsync uses SetInputFilesAsync with retry and
  manual change event dispatch for Blazor InputFile compatibility.
- Add dorny/test-reporter to display test results in PR summary.
- Add checks/pull-requests write permissions for test-reporter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-08 12:07:52 +11:00

166 lines
5.2 KiB
C#

using Microsoft.Playwright;
namespace LANCommander.Server.UI.Tests.Pages;
/// <summary>
/// Page object for the game edit page at /Games/{id}/General and related tabs.
/// </summary>
public class GameEditPage
{
private readonly IPage _page;
private const int DefaultTimeout = 15000;
public GameEditPage(IPage page)
{
_page = page;
}
/// <summary>
/// Navigates directly to a game's edit page by its ID.
/// </summary>
public async Task NavigateToGameByIdAsync(Guid gameId)
{
var uri = new Uri(_page.Url);
var baseUrl = $"{uri.Scheme}://{uri.Authority}";
await _page.GotoAsync($"{baseUrl}/Games/{gameId}/General");
await WaitForFormLoadedAsync();
}
/// <summary>
/// Ensures a game is imported and navigates to its edit page.
/// If the game already exists, it opens the edit page directly.
/// </summary>
public async Task NavigateAsync(string gameTitle, string lcxFilePath)
{
var gamesPage = new GamesPage(_page);
await gamesPage.NavigateAsync();
if (!await gamesPage.IsGameVisibleAsync(gameTitle, timeoutMs: 3000))
{
await gamesPage.ImportGameAsync(lcxFilePath);
}
await gamesPage.OpenGameEditAsync(gameTitle);
// The edit page may land on /Games/{id} or /Games/{id}/General
await WaitForFormLoadedAsync();
}
/// <summary>
/// Gets the current game title from the form input.
/// </summary>
public async Task<string?> GetTitleAsync()
{
var input = GetTitleInput();
await input.WaitForAsync(new() { Timeout = DefaultTimeout });
return await input.InputValueAsync();
}
/// <summary>
/// Sets the game title in the form input.
/// </summary>
public async Task SetTitleAsync(string title)
{
var input = GetTitleInput();
await input.WaitForAsync(new() { Timeout = DefaultTimeout });
await input.ClickAsync();
await input.PressAsync("Control+a");
await input.TypeAsync(title);
await input.PressAsync("Tab");
}
/// <summary>
/// Clicks the Save button and waits for the success notification.
/// </summary>
public async Task SaveAsync()
{
await _page.GetByRole(AriaRole.Button, new() { Name = "Save", Exact = true }).ClickAsync();
// Wait for the success notification to appear
try
{
await _page.WaitForSelectorAsync(".ant-notification", new() { Timeout = 10000 });
}
catch (TimeoutException)
{
// Notification may not appear in all cases; continue
}
await _page.WaitForTimeoutAsync(500);
}
/// <summary>
/// Navigates to a specific tab by clicking the corresponding menu item in the game edit sidebar.
/// </summary>
public async Task NavigateToTabAsync(string tabName)
{
var menuItem = GetSiderMenu().GetByRole(AriaRole.Menuitem, new() { Name = tabName, Exact = true });
await menuItem.ClickAsync();
await _page.WaitForTimeoutAsync(1000);
}
/// <summary>
/// Checks whether a tab (menu item) with the given name is visible in the game edit sidebar.
/// </summary>
public async Task<bool> IsTabVisibleAsync(string tabName)
{
var menuItem = GetSiderMenu().GetByRole(AriaRole.Menuitem, new() { Name = tabName, Exact = true });
try
{
await menuItem.WaitForAsync(new() { Timeout = 5000 });
return await menuItem.IsVisibleAsync();
}
catch (TimeoutException)
{
return false;
}
}
/// <summary>
/// Checks whether the Export button is visible on the page.
/// </summary>
public async Task<bool> IsExportButtonVisibleAsync()
{
return await _page.GetByRole(AriaRole.Button, new() { Name = "Export", Exact = true })
.IsVisibleAsync();
}
/// <summary>
/// Checks whether the Save button is visible on the page.
/// </summary>
public async Task<bool> IsSaveButtonVisibleAsync()
{
return await _page.GetByRole(AriaRole.Button, new() { Name = "Save", Exact = true })
.IsVisibleAsync();
}
/// <summary>
/// Gets the current page URL.
/// </summary>
public string GetCurrentUrl() => _page.Url;
private ILocator GetTitleInput()
{
return _page.Locator(".ant-form-item")
.Filter(new() { HasText = "Title" })
.First
.Locator("input")
.First;
}
/// <summary>
/// Returns the sidebar menu locator scoped to the game edit panel layout.
/// </summary>
private ILocator GetSiderMenu()
{
return _page.Locator(".panel-layout .ant-layout-sider .ant-menu");
}
private async Task WaitForFormLoadedAsync()
{
// Wait for the form to render by checking for the Title form item
await _page.Locator(".ant-form-item").Filter(new() { HasText = "Title" }).First
.WaitForAsync(new() { Timeout = DefaultTimeout });
}
}