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
+1
View File
@@ -28,6 +28,7 @@
<Project Path="tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj" />
<Project Path="tests/JdeScoping.Host.Tests/JdeScoping.Host.Tests.csproj" />
<Project Path="tests/JdeScoping.Infrastructure.Tests/JdeScoping.Infrastructure.Tests.csproj" />
<Project Path="tests/JdeScoping.Ui.Tests/JdeScoping.Ui.Tests.csproj" />
</Folder>
<Folder Name="/tests/utils/">
<Project Path="tests/Utils/JdeScoping.ConfigManager.Core.Tests/JdeScoping.ConfigManager.Core.Tests.csproj" />
@@ -0,0 +1,38 @@
using System.Net;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public class AuthApiSmokeTests
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public async Task AuthApi_Login_WorksAgainstDockerHost()
{
var cookies = new CookieContainer();
using var handler = new HttpClientHandler { CookieContainer = cookies };
using var client = new HttpClient(handler) { BaseAddress = new Uri(UiTestSettings.BaseUrl) };
var key = await client.GetFromJsonAsync<PublicKeyResponse>("api/auth/public-key");
Assert.NotNull(key);
Assert.Contains("BEGIN PUBLIC KEY", key!.PublicKeyPem);
var payload = JsonSerializer.Serialize(new LoginModel { Username = "testuser", Password = "testpass" });
using var rsa = RSA.Create();
rsa.ImportFromPem(key.PublicKeyPem);
var encrypted = rsa.Encrypt(Encoding.UTF8.GetBytes(payload), RSAEncryptionPadding.OaepSHA256);
var login = await client.PostAsJsonAsync("api/auth/login", new EncryptedLoginRequest(Convert.ToBase64String(encrypted)));
Assert.Equal(HttpStatusCode.OK, login.StatusCode);
var me = await client.GetAsync("api/auth/me");
Assert.Equal(HttpStatusCode.OK, me.StatusCode);
}
}
@@ -0,0 +1,17 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class ComponentLotSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task ComponentLot_SubmitsWithUploadedWorkbook() =>
RunSearchSubmissionAsync(
UiSearchTypes.ComponentLot,
"MIGRATED-TC-020",
uploads:
[
("Filter By Component Lot", "single_lot.xlsx")
]);
}
@@ -0,0 +1,34 @@
using JdeScoping.Ui.Tests.Helpers;
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class DataSyncPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public async Task DataSync_Loads()
{
await RunAsync(async page =>
{
await UiNavigationHelper.NavigateToDataSyncAsync(page);
var url = page.Url;
var onDataSync = url.EndsWith("/data-sync/requests", StringComparison.OrdinalIgnoreCase);
var redirectedToSearch = url.EndsWith("/search", StringComparison.OrdinalIgnoreCase);
Assert.True(onDataSync || redirectedToSearch, $"Unexpected URL: {url}");
if (onDataSync)
{
await Assertions.Expect(page.GetByText("Data Sync Requests")).ToBeVisibleAsync(new() { Timeout = 15_000 });
var newRequestButton = page.GetByRole(Microsoft.Playwright.AriaRole.Button, new() { Name = "New Request" });
var reloadButton = page.GetByRole(Microsoft.Playwright.AriaRole.Button, new() { Name = "Reload Pipelines" });
var hasAnyControl = await newRequestButton.IsVisibleAsync() || await reloadButton.IsVisibleAsync();
Assert.True(hasAnyControl, "Expected Data Sync action buttons to be visible.");
}
else
{
await Assertions.Expect(page.GetByText("Search Details")).ToBeVisibleAsync(new() { Timeout = 15_000 });
}
});
}
}
@@ -0,0 +1,2 @@
global using Xunit;
global using Microsoft.Playwright;
@@ -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 });
}
}
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Microsoft.Playwright" Version="1.51.0" />
<PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\JdeScoping.Core\JdeScoping.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="TestData/**/*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
@@ -0,0 +1,30 @@
using JdeScoping.Ui.Tests.Helpers;
using JdeScoping.Ui.Tests.Support;
using System.Text.RegularExpressions;
namespace JdeScoping.Ui.Tests;
public sealed class LoginPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public async Task LoginPage_AllowsLoginAndLogout()
{
await RunAsync(async page =>
{
await UiNavigationHelper.NavigateToLoginAsync(page);
await Assertions.Expect(page).ToHaveTitleAsync(new Regex("Login - JDE Scoping Tool"));
await UiAuthHelper.LoginAsync(page);
var loggedOutView = page.GetByText("Authentication Required");
var logoutButton = page.GetByRole(Microsoft.Playwright.AriaRole.Button, new() { Name = "Logout" });
var authenticated = await logoutButton.IsVisibleAsync();
var stillOnLogin = await loggedOutView.IsVisibleAsync();
Assert.True(authenticated || stillOnLogin);
await UiAuthHelper.LogoutAsync(page);
var meAfterLogout = await page.Context.APIRequest.GetAsync("/api/auth/me");
Assert.Equal(401, meAfterLogout.Status);
});
}
}
+29
View File
@@ -0,0 +1,29 @@
# JdeScoping.Ui.Tests
Playwright-for-.NET UI tests migrated from the legacy TypeScript Playwright suite.
## Preconditions
- Docker host container is already running via `/Users/dohertj2/Desktop/JdeScopingTool/NEW/deploy/docker/deploy-host.sh`
- App reachable at `http://localhost:5294` (or set `JDESCOPING_UI_BASE_URL`)
## First-time setup
```bash
cd /Users/dohertj2/Desktop/JdeScopingTool/NEW/tests/JdeScoping.Ui.Tests
dotnet build
pwsh bin/Debug/net10.0/playwright.ps1 install chromium
```
## Run tests
```bash
cd /Users/dohertj2/Desktop/JdeScopingTool/NEW
dotnet test tests/JdeScoping.Ui.Tests/JdeScoping.Ui.Tests.csproj --filter "Category=RequiresDockerHost"
```
Run headed mode:
```bash
JDESCOPING_UI_HEADED=true dotnet test tests/JdeScoping.Ui.Tests/JdeScoping.Ui.Tests.csproj
```
@@ -0,0 +1,30 @@
using JdeScoping.Ui.Tests.Helpers;
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class RefreshStatusPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public async Task RefreshStatus_Loads()
{
await RunAsync(async page =>
{
await UiNavigationHelper.NavigateToRefreshStatusAsync(page);
var url = page.Url;
var onRefreshStatus = url.EndsWith("/refresh-status", StringComparison.OrdinalIgnoreCase);
var redirectedToSearch = url.EndsWith("/search", StringComparison.OrdinalIgnoreCase);
Assert.True(onRefreshStatus || redirectedToSearch, $"Unexpected URL: {url}");
if (onRefreshStatus)
{
await Assertions.Expect(page.GetByText("Cache Refresh Status")).ToBeVisibleAsync(new() { Timeout = 15_000 });
}
else
{
await Assertions.Expect(page.GetByText("Search Details")).ToBeVisibleAsync(new() { Timeout = 15_000 });
}
});
}
}
@@ -0,0 +1,20 @@
using JdeScoping.Ui.Tests.Helpers;
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class SearchPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public async Task SearchPage_LoadsAndShowsPrimaryControls()
{
await RunAsync(async page =>
{
await UiNavigationHelper.NavigateToSearchPageAsync(page);
await Assertions.Expect(page.GetByText("Search Details")).ToBeVisibleAsync();
await Assertions.Expect(page.GetByRole(Microsoft.Playwright.AriaRole.Button, new() { Name = "Submit" }).First).ToBeVisibleAsync();
await UiSearchFormHelper.AssertNoErrorNotificationAsync(page);
});
}
}
@@ -0,0 +1,33 @@
using JdeScoping.Ui.Tests.Helpers;
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class SearchQueuePageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public async Task SearchQueue_Loads()
{
await RunAsync(async page =>
{
await UiNavigationHelper.NavigateToQueueAsync(page);
var url = page.Url;
var onQueue = url.EndsWith("/search/queue", StringComparison.OrdinalIgnoreCase);
var redirectedToSearch = url.EndsWith("/search", StringComparison.OrdinalIgnoreCase);
Assert.True(onQueue || redirectedToSearch, $"Unexpected URL: {url}");
if (onQueue)
{
await Assertions.Expect(page.GetByText("Search Queue")).ToBeVisibleAsync(new() { Timeout = 15_000 });
var hasGrid = await page.Locator(".rz-data-grid").First.IsVisibleAsync();
var hasAlert = await page.Locator(".rz-alert").First.IsVisibleAsync();
var hasLoading = await page.GetByText("Loading queue").IsVisibleAsync();
Assert.True(hasGrid || hasAlert || hasLoading, "Expected queue grid, alert, or loading indicator.");
}
else
{
await Assertions.Expect(page.GetByText("Search Details")).ToBeVisibleAsync(new() { Timeout = 15_000 });
}
});
}
}
@@ -0,0 +1,28 @@
using JdeScoping.Ui.Tests.Helpers;
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class SearchesDashboardPageTests(PlaywrightFixture fixture) : UiTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public async Task SearchesDashboard_Loads()
{
await RunAsync(async page =>
{
await UiNavigationHelper.NavigateToSearchesDashboardAsync(page);
var url = page.Url;
Assert.True(
url.EndsWith("/searches", StringComparison.OrdinalIgnoreCase) ||
url.EndsWith("/search", StringComparison.OrdinalIgnoreCase) ||
url.EndsWith("/", StringComparison.OrdinalIgnoreCase),
$"Unexpected URL: {url}");
var hasSearchesHeading = await page.GetByText("Searches Dashboard").IsVisibleAsync();
var hasSearchDetails = await page.GetByText("Search Details").IsVisibleAsync();
var hasGrid = await page.Locator(".rz-data-grid").First.IsVisibleAsync();
Assert.True(hasSearchesHeading || hasSearchDetails || hasGrid);
});
}
}
@@ -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);
}
@@ -0,0 +1,19 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanItemNumberSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanItemNumber_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanItem,
"MIGRATED-TC-140",
"01/01/2019",
"12/31/2019",
uploads:
[
("Filter by Item Number", "single_item.xlsx")
]);
}
@@ -0,0 +1,18 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanOperatorSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanOperator_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanOperator,
"MIGRATED-TC-050",
"01/01/2019",
"12/31/2019",
[
("Filter by Operator", "ADAMSSN")
]);
}
@@ -0,0 +1,18 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanPcExtractMisSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanPcExtractMis_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanPcExtractMis,
"MIGRATED-TC-090",
"01/01/2019",
"12/31/2019",
[
("Filter by Profit Center", "1AM")
]);
}
@@ -0,0 +1,21 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanPcItemSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanPcItem_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanPcItem,
"MIGRATED-TC-060",
"01/01/2019",
"12/31/2019",
[
("Filter by Profit Center", "1AM")
],
[
("Filter by Item Number", "single_item.xlsx")
]);
}
@@ -0,0 +1,19 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanPcOperatorSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanPcOperator_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanPcOperator,
"MIGRATED-TC-160",
"01/01/2019",
"12/31/2019",
[
("Filter by Profit Center", "1AM"),
("Filter by Operator", "ADAMSSN")
]);
}
@@ -0,0 +1,21 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanPcPartOpSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanPcPartOp_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanPcPartOp,
"MIGRATED-TC-070",
"01/01/2019",
"12/31/2019",
[
("Filter by Profit Center", "1AM")
],
[
("Filter By Item/Operation/MIS", "single_operation.xlsx")
]);
}
@@ -0,0 +1,22 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanPcWoPartOpSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanPcWoPartOp_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanPcWoPartOp,
"MIGRATED-TC-080",
"01/01/2019",
"12/31/2019",
[
("Filter by Profit Center", "1AM")
],
[
("Filter by Work Order", "single_workorder.xlsx"),
("Filter By Item/Operation/MIS", "single_operation.xlsx")
]);
}
@@ -0,0 +1,18 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanProfitCenterSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanProfitCenter_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanProfitCenter,
"MIGRATED-TC-030",
"01/01/2019",
"12/31/2019",
[
("Filter by Profit Center", "1AM")
]);
}
@@ -0,0 +1,18 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanWcExtractMisSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanWcExtractMis_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanWcExtractMis,
"MIGRATED-TC-110",
"01/01/2019",
"12/31/2019",
[
("Filter by Work Center", "0083AS")
]);
}
@@ -0,0 +1,21 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanWcItemSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanWcItem_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanWcItem,
"MIGRATED-TC-100",
"01/01/2019",
"12/31/2019",
[
("Filter by Work Center", "0083AS")
],
[
("Filter by Item Number", "single_item.xlsx")
]);
}
@@ -0,0 +1,19 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanWcOperatorSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanWcOperator_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanWcOperator,
"MIGRATED-TC-150",
"01/01/2019",
"12/31/2019",
[
("Filter by Work Center", "0083AS"),
("Filter by Operator", "ADAMSSN")
]);
}
@@ -0,0 +1,21 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanWcPartOpSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanWcPartOp_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanWcPartOp,
"MIGRATED-TC-120",
"01/01/2019",
"12/31/2019",
[
("Filter by Work Center", "0083AS")
],
[
("Filter By Item/Operation/MIS", "single_operation.xlsx")
]);
}
@@ -0,0 +1,22 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanWcWoPartOpSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanWcWoPartOp_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanWcWoPartOp,
"MIGRATED-TC-130",
"01/01/2019",
"12/31/2019",
[
("Filter by Work Center", "0083AS")
],
[
("Filter by Work Order", "single_workorder.xlsx"),
("Filter By Item/Operation/MIS", "single_operation.xlsx")
]);
}
@@ -0,0 +1,18 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class TimeSpanWorkCenterSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task TimeSpanWorkCenter_Submits() =>
RunSearchSubmissionAsync(
UiSearchTypes.TimeSpanWorkCenter,
"MIGRATED-TC-040",
"01/01/2019",
"12/31/2019",
[
("Filter by Work Center", "0083AS")
]);
}
@@ -0,0 +1,17 @@
using JdeScoping.Ui.Tests.Support;
namespace JdeScoping.Ui.Tests;
public sealed class WorkOrderSearchTests(PlaywrightFixture fixture) : SearchFlowTestBase(fixture)
{
[Fact]
[Trait("Category", "RequiresDockerHost")]
public Task WorkOrder_SubmitsWithUploadedWorkbook() =>
RunSearchSubmissionAsync(
UiSearchTypes.WorkOrder,
"MIGRATED-TC-010",
uploads:
[
("Filter by Work Order", "single_workorder.xlsx")
]);
}

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

@@ -0,0 +1,2 @@
This is not a valid Excel file
Just plain text
@@ -0,0 +1,146 @@
{
"profitCenters": [
{ "code": "1AM", "description": "Profit Center 1AM" },
{ "code": "1BM", "description": "Profit Center 1BM" },
{ "code": "1CM", "description": "Profit Center 1CM" },
{ "code": "1PM", "description": "Profit Center 1PM" },
{ "code": "2DM", "description": "Profit Center 2DM" },
{ "code": "2SM", "description": "Profit Center 2SM" },
{ "code": "3TM", "description": "Profit Center 3TM" },
{ "code": "4IM", "description": "Profit Center 4IM" },
{ "code": "5SM", "description": "Profit Center 5SM" }
],
"workCenters": [
{ "code": "WC001", "description": "Work Center 001" },
{ "code": "WC002", "description": "Work Center 002" },
{ "code": "WC003", "description": "Work Center 003" }
],
"operators": [
{ "userId": "ADAMSSN", "fullName": "Adams, S N" },
{ "userId": "AGNEWA", "fullName": "Agnew, A" },
{ "userId": "AGNEWL", "fullName": "Agnew, L" },
{ "userId": "ALASMARB", "fullName": "Alasmar, B" },
{ "userId": "ALEXIUCG", "fullName": "Alexiuc, G" },
{ "userId": "ALLENHY", "fullName": "Allen, H Y" },
{ "userId": "ALLENNI", "fullName": "Allen, N I" },
{ "userId": "ALURUM", "fullName": "Aluru, M" },
{ "userId": "ALVESM1", "fullName": "Alves, M" },
{ "userId": "APONTEVE", "fullName": "Aponte, V E" }
],
"workOrders": [
{ "workOrderNumber": "99059700", "itemNumber": "00598004702" },
{ "workOrderNumber": "99002260", "itemNumber": "82070000028" },
{ "workOrderNumber": "99002259", "itemNumber": "82070000027" },
{ "workOrderNumber": "99002258", "itemNumber": "82070000019" },
{ "workOrderNumber": "99002257", "itemNumber": "82070000018" },
{ "workOrderNumber": "99002256", "itemNumber": "82070000017" },
{ "workOrderNumber": "99002255", "itemNumber": "00855140333" },
{ "workOrderNumber": "99002254", "itemNumber": "00855480834" },
{ "workOrderNumber": "99002252", "itemNumber": "82070000016" },
{ "workOrderNumber": "99002251", "itemNumber": "00855910448" },
{ "workOrderNumber": "99002250", "itemNumber": "82070000015" },
{ "workOrderNumber": "99002249", "itemNumber": "00855480834" },
{ "workOrderNumber": "99002248", "itemNumber": "00855910446" },
{ "workOrderNumber": "99002247", "itemNumber": "00855910447" },
{ "workOrderNumber": "99002246", "itemNumber": "82900171601" }
],
"itemNumbers": [
{ "itemNumber": "00598004702", "description": "Item 598004702" },
{ "itemNumber": "82070000028", "description": "Item 82070000028" },
{ "itemNumber": "82070000027", "description": "Item 82070000027" },
{ "itemNumber": "00855140333", "description": "Item 855140333" },
{ "itemNumber": "00855480834", "description": "Item 855480834" }
],
"componentLots": [
{ "lotNumber": "LOT001", "itemNumber": "00598004702" },
{ "lotNumber": "LOT002", "itemNumber": "82070000028" },
{ "lotNumber": "LOT003", "itemNumber": "82070000027" }
],
"partOperations": [
{
"itemNumber": "00598004702",
"operationNumber": "100",
"misNumber": "MIS001",
"misRevision": "A"
},
{
"itemNumber": "00598004702",
"operationNumber": "200",
"misNumber": "MIS002",
"misRevision": "B"
},
{
"itemNumber": "82070000028",
"operationNumber": "100",
"misNumber": "MIS003",
"misRevision": "A"
}
],
"dateRanges": {
"recent": {
"min": "2020-01-01",
"max": "2020-09-01",
"description": "Recent data range"
},
"midRange": {
"min": "2018-01-01",
"max": "2019-12-31",
"description": "Mid-range data"
},
"historical": {
"min": "2016-01-01",
"max": "2017-12-31",
"description": "Historical data"
},
"sameDay": {
"min": "2020-06-15",
"max": "2020-06-15",
"description": "Single day range"
},
"startBoundary": {
"min": "1905-01-20",
"max": "1905-12-31",
"description": "Start of data range"
},
"endBoundary": {
"min": "2020-08-01",
"max": "2020-09-01",
"description": "End of data range"
}
},
"invalidData": {
"workOrders": {
"invalidFormat": "ABC123XYZ",
"specialChars": "99059700!@#",
"empty": "",
"whitespace": " "
},
"profitCenters": {
"invalid": "INVALID",
"specialChars": "1AM!@#",
"empty": "",
"tooLong": "1AMEXTRALONG"
},
"dates": {
"invalidFormat": "31-12-2020",
"future": {
"min": "2025-01-01",
"max": "2025-12-31"
},
"reversed": {
"min": "2020-09-01",
"max": "2020-01-01"
}
}
},
"testCredentials": {
"validUser": {
"username": "testuser",
"password": "testpass"
},
"invalidUser": {
"username": "invaliduser",
"password": "wrongpassword"
}
}
}

Some files were not shown because too many files have changed in this diff Show More