Migrate Playwright suite to .NET UI tests and deprecate TS project

This commit is contained in:
Joseph Doherty
2026-02-06 18:44:40 -05:00
parent 4e56ea3435
commit 562f7e9e37
105 changed files with 1119 additions and 0 deletions
@@ -0,0 +1,53 @@
using Microsoft.Playwright;
namespace JdeScoping.Ui.Tests.Helpers;
internal static class UiAuthHelper
{
public static async Task LoginAsync(IPage page, string username = "testuser", string password = "testpass")
{
var loginForm = page.GetByText("Authentication Required");
var formVisible = await loginForm.IsVisibleAsync();
if (!formVisible)
{
return;
}
await page.Locator("input[name='Username']").FillAsync(username);
await page.Locator("input[name='Password']").FillAsync(password);
await page.Locator("button[type='submit']:has-text('LOGIN')").ClickAsync();
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await page.WaitForTimeoutAsync(3_000);
await page.GotoAsync("/search");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
if (await loginForm.IsVisibleAsync())
{
var notifications = page.Locator(".rz-notification");
var count = await notifications.CountAsync();
var details = new List<string>();
for (var i = 0; i < count; i++)
{
var text = (await notifications.Nth(i).InnerTextAsync()).Trim();
if (!string.IsNullOrWhiteSpace(text))
{
details.Add(text);
}
}
throw new InvalidOperationException(
$"Login did not complete. URL={page.Url}. Notifications={string.Join(" | ", details)}");
}
}
public static async Task LogoutAsync(IPage page)
{
var logoutButton = page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "Logout" });
if (await logoutButton.IsVisibleAsync())
{
await logoutButton.ClickAsync();
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
}
}
}
@@ -0,0 +1,106 @@
using Microsoft.Playwright;
namespace JdeScoping.Ui.Tests.Helpers;
internal static class UiNavigationHelper
{
public static async Task NavigateToSearchPageAsync(IPage page)
{
await page.GotoAsync("/search");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await UiAuthHelper.LoginAsync(page);
await EnsureAuthenticatedOrThrowAsync(page);
await WaitForBlazorReadyAsync(page);
}
public static async Task NavigateToSearchesDashboardAsync(IPage page)
{
await page.GotoAsync("/searches");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await UiAuthHelper.LoginAsync(page);
await EnsureAuthenticatedOrThrowAsync(page);
await WaitForBlazorReadyAsync(page);
}
public static async Task NavigateToQueueAsync(IPage page)
{
await page.GotoAsync("/search/queue");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await UiAuthHelper.LoginAsync(page);
await EnsureAuthenticatedOrThrowAsync(page);
await WaitForBlazorReadyAsync(page);
}
public static async Task NavigateToRefreshStatusAsync(IPage page)
{
await page.GotoAsync("/refresh-status");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await UiAuthHelper.LoginAsync(page);
await EnsureAuthenticatedOrThrowAsync(page);
await WaitForBlazorReadyAsync(page);
}
public static async Task NavigateToDataSyncAsync(IPage page)
{
await page.GotoAsync("/data-sync/requests");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
await UiAuthHelper.LoginAsync(page);
await EnsureAuthenticatedOrThrowAsync(page);
await WaitForBlazorReadyAsync(page);
}
public static async Task NavigateToLoginAsync(IPage page)
{
await page.GotoAsync("/login");
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
}
private static async Task WaitForBlazorReadyAsync(IPage page)
{
var timeoutMs = 15_000;
try
{
await page.Locator(".rz-dropdown").First.WaitForAsync(new LocatorWaitForOptions
{
State = WaitForSelectorState.Visible,
Timeout = timeoutMs
});
return;
}
catch (PlaywrightException)
{
// Try additional readiness markers.
}
try
{
await page.Locator(".rz-data-grid").First.WaitForAsync(new LocatorWaitForOptions
{
State = WaitForSelectorState.Visible,
Timeout = timeoutMs
});
return;
}
catch (PlaywrightException)
{
// Try text marker as final fallback.
}
await page.GetByText("Search Details").First.WaitForAsync(new LocatorWaitForOptions
{
State = WaitForSelectorState.Visible,
Timeout = timeoutMs
});
await page.WaitForTimeoutAsync(1_000);
}
private static async Task EnsureAuthenticatedOrThrowAsync(IPage page)
{
var meResponse = await page.Context.APIRequest.GetAsync("/api/auth/me");
if (meResponse.Status != 200)
{
throw new InvalidOperationException(
$"UI test host did not establish authenticated session after login. /api/auth/me status={meResponse.Status}.");
}
}
}
@@ -0,0 +1,63 @@
using Microsoft.Playwright;
namespace JdeScoping.Ui.Tests.Helpers;
internal static class UiSearchFormHelper
{
public static async Task SelectSearchTypeAsync(IPage page, string searchType)
{
await page.Locator(".rz-dropdown").First.ClickAsync();
await page.GetByRole(AriaRole.Option, new PageGetByRoleOptions { Name = searchType, Exact = true }).ClickAsync();
await page.WaitForTimeoutAsync(500);
}
public static Task EnterSearchNameAsync(IPage page, string name) =>
page.Locator("input[placeholder=' ']").First.FillAsync(name);
public static async Task SetDateRangeAsync(IPage page, string minimumMmDdYyyy, string maximumMmDdYyyy)
{
await page.Locator("input[name='MinimumDt']").FillAsync(minimumMmDdYyyy);
await page.Locator("input[name='MaximumDt']").FillAsync(maximumMmDdYyyy);
}
public static async Task AddAutocompleteItemAsync(IPage page, string panelHeader, string value)
{
var panel = page.Locator($".rz-card:has-text('{panelHeader}')");
await panel.Locator(".rz-autocomplete input").First.FillAsync(value);
await page.WaitForTimeoutAsync(500);
var listItem = page.Locator(".rz-autocomplete-list .rz-autocomplete-list-item").First;
if (await listItem.IsVisibleAsync())
{
await listItem.ClickAsync();
}
await panel.GetByRole(AriaRole.Button, new LocatorGetByRoleOptions { Name = "Add" }).ClickAsync();
await page.WaitForTimeoutAsync(250);
}
public static async Task UploadFileAsync(IPage page, string panelHeader, string filePath)
{
var panel = page.Locator($".rz-card:has-text('{panelHeader}')");
await panel.Locator("input[type='file']").First.SetInputFilesAsync(filePath);
await page.WaitForTimeoutAsync(1_500);
}
public static async Task SubmitSearchAsync(IPage page)
{
await page.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name = "Submit" }).First.ClickAsync();
await page.GetByText("Confirm Submit").WaitForAsync(new LocatorWaitForOptions
{
State = WaitForSelectorState.Visible,
Timeout = 10_000
});
await page.Locator(".rz-dialog-wrapper button").GetByText("Submit", new LocatorGetByTextOptions { Exact = true }).ClickAsync();
await page.WaitForTimeoutAsync(1_000);
}
public static async Task AssertNoErrorNotificationAsync(IPage page)
{
var error = page.Locator(".rz-notification-error");
await Assertions.Expect(error).Not.ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 5_000 });
}
}