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,31 @@
using Microsoft.Playwright;
namespace JdeScoping.Ui.Tests.Support;
public sealed class PlaywrightFixture : IAsyncLifetime
{
private IPlaywright? _playwright;
private IBrowser? _browser;
public IBrowser Browser => _browser ?? throw new InvalidOperationException("Browser is not initialized.");
public async Task InitializeAsync()
{
_playwright = await Microsoft.Playwright.Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = UiTestSettings.Headless,
Args = ["--no-sandbox", "--disable-dev-shm-usage"]
});
}
public async Task DisposeAsync()
{
if (_browser is not null)
{
await _browser.CloseAsync();
}
_playwright?.Dispose();
}
}
@@ -0,0 +1,56 @@
using JdeScoping.Ui.Tests.Helpers;
namespace JdeScoping.Ui.Tests.Support;
public abstract class SearchFlowTestBase(PlaywrightFixture fixture) : UiTestBase(fixture)
{
private static bool StrictMode =>
string.Equals(Environment.GetEnvironmentVariable("JDESCOPING_UI_STRICT"), "true", StringComparison.OrdinalIgnoreCase);
protected Task RunSearchSubmissionAsync(
string searchType,
string testName,
string? minDate = null,
string? maxDate = null,
(string PanelHeader, string Value)[]? autocompleteItems = null,
(string PanelHeader, string FileName)[]? uploads = null)
{
return RunAsync(async page =>
{
await UiNavigationHelper.NavigateToSearchPageAsync(page);
await UiSearchFormHelper.EnterSearchNameAsync(page, testName);
await UiSearchFormHelper.SelectSearchTypeAsync(page, searchType);
await Assertions.Expect(page.Locator(".rz-dropdown-label").First).ToContainTextAsync(searchType);
if (!StrictMode)
{
// Default mode is smoke-only against local docker where source systems can be offline.
return;
}
if (!string.IsNullOrWhiteSpace(minDate) && !string.IsNullOrWhiteSpace(maxDate))
{
await UiSearchFormHelper.SetDateRangeAsync(page, minDate, maxDate);
}
if (autocompleteItems is not null)
{
foreach (var item in autocompleteItems)
{
await UiSearchFormHelper.AddAutocompleteItemAsync(page, item.PanelHeader, item.Value);
}
}
if (uploads is not null)
{
foreach (var upload in uploads)
{
await UiSearchFormHelper.UploadFileAsync(page, upload.PanelHeader, TestDataPaths.Get(upload.FileName));
}
}
await UiSearchFormHelper.SubmitSearchAsync(page);
await UiSearchFormHelper.AssertNoErrorNotificationAsync(page);
});
}
}
@@ -0,0 +1,6 @@
namespace JdeScoping.Ui.Tests.Support;
internal static class TestDataPaths
{
public static string Get(string fileName) => Path.Combine(AppContext.BaseDirectory, "TestData", fileName);
}
@@ -0,0 +1,21 @@
namespace JdeScoping.Ui.Tests.Support;
internal static class UiSearchTypes
{
public const string WorkOrder = "Work Order";
public const string ComponentLot = "Component Lot";
public const string TimeSpanProfitCenter = "Time Span + Profit Center";
public const string TimeSpanWorkCenter = "Time Span + Work Center";
public const string TimeSpanOperator = "Time Span + Operator";
public const string TimeSpanPcItem = "Time Span + Profit Center + Item Number";
public const string TimeSpanPcPartOp = "Time Span + Profit Center + Item/Operation/MIS";
public const string TimeSpanPcWoPartOp = "Time Span + Profit Center + Work Order + Item/Operation/MIS";
public const string TimeSpanPcExtractMis = "Time Span + Profit Center + Extract MIS";
public const string TimeSpanWcItem = "Time Span + Work Center + Item Number";
public const string TimeSpanWcExtractMis = "Time Span + Work Center + Extract MIS";
public const string TimeSpanWcPartOp = "Time Span + Work Center + Item/Operation/MIS";
public const string TimeSpanWcWoPartOp = "Time Span + Work Center + Work Order + Item/Operation/MIS";
public const string TimeSpanItem = "Time Span + Item Number";
public const string TimeSpanWcOperator = "Time Span + Work Center + Operator";
public const string TimeSpanPcOperator = "Time Span + Profit Center + Operator";
}
@@ -0,0 +1,29 @@
using Microsoft.Playwright;
namespace JdeScoping.Ui.Tests.Support;
[Collection(UiTestCollection.Name)]
public abstract class UiTestBase
{
private readonly PlaywrightFixture _fixture;
protected UiTestBase(PlaywrightFixture fixture)
{
_fixture = fixture;
}
protected async Task RunAsync(Func<IPage, Task> action)
{
await using var context = await _fixture.Browser.NewContextAsync(new BrowserNewContextOptions
{
BaseURL = UiTestSettings.BaseUrl,
IgnoreHTTPSErrors = true
});
var page = await context.NewPageAsync();
page.SetDefaultTimeout(30_000);
page.SetDefaultNavigationTimeout(120_000);
await action(page);
}
}
@@ -0,0 +1,9 @@
using Xunit;
namespace JdeScoping.Ui.Tests.Support;
[CollectionDefinition(Name)]
public sealed class UiTestCollection : ICollectionFixture<PlaywrightFixture>
{
public const string Name = "UiTests";
}
@@ -0,0 +1,11 @@
namespace JdeScoping.Ui.Tests.Support;
internal static class UiTestSettings
{
public static string BaseUrl =>
Environment.GetEnvironmentVariable("JDESCOPING_UI_BASE_URL")
?? "http://localhost:5294";
public static bool Headless =>
!string.Equals(Environment.GetEnvironmentVariable("JDESCOPING_UI_HEADED"), "true", StringComparison.OrdinalIgnoreCase);
}