test(playwright): add ExternalSystem CRUD + validation coverage (Wave 3)
This commit is contained in:
+104
@@ -0,0 +1,104 @@
|
||||
using Microsoft.Playwright;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Design;
|
||||
|
||||
[Collection("Playwright")]
|
||||
public class ExternalSystemCrudTests
|
||||
{
|
||||
private readonly PlaywrightFixture _pw;
|
||||
|
||||
public ExternalSystemCrudTests(PlaywrightFixture pw)
|
||||
{
|
||||
_pw = pw;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full create → card → delete round-trip for an external system via the Central UI.
|
||||
/// Uses CliRunner for best-effort teardown so no zztest-extsys-* definition leaks on
|
||||
/// failure (the happy path already deletes via the UI).
|
||||
/// </summary>
|
||||
[SkippableFact]
|
||||
public async Task Create_Delete_RoundTrips()
|
||||
{
|
||||
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
|
||||
|
||||
var name = CliRunner.UniqueName("extsys");
|
||||
|
||||
var page = await _pw.NewAuthenticatedPageAsync();
|
||||
|
||||
try
|
||||
{
|
||||
// ── CREATE ────────────────────────────────────────────────────────────────
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/external-systems/create");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
await Assertions.Expect(page.Locator("h4:has-text('Add External System')")).ToBeVisibleAsync();
|
||||
|
||||
// Name and Endpoint URL are both input[type=text].form-control. Per
|
||||
// ExternalSystemForm.razor, Name is the first text input and Endpoint URL
|
||||
// is the second — select by order.
|
||||
var textInputs = page.Locator("input[type=text].form-control");
|
||||
await textInputs.Nth(0).FillAsync(name);
|
||||
await textInputs.Nth(1).FillAsync("https://example.invalid/api");
|
||||
|
||||
await page.Locator("button.btn-success:has-text('Save')").ClickAsync();
|
||||
|
||||
// Save redirects back to the list (no toast).
|
||||
await PlaywrightFixture.WaitForPathAsync(page, "/design/external-systems", excludePath: "/create");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
// The new card must be present (and unique) before we act on it.
|
||||
var card = page.Locator("div.card").Filter(new() { HasText = name });
|
||||
await Assertions.Expect(card).ToHaveCountAsync(1, new() { Timeout = 10_000 });
|
||||
|
||||
// ── DELETE ────────────────────────────────────────────────────────────────
|
||||
// Scope the kebab + delete item to the card's .dropdown for strict-mode safety.
|
||||
var cardDropdown = card.Locator(".dropdown");
|
||||
await cardDropdown.Locator("button[aria-label^='More actions']").ClickAsync();
|
||||
|
||||
await cardDropdown.Locator(".dropdown-menu button.dropdown-item.text-danger").ClickAsync();
|
||||
|
||||
// Confirm the delete dialog.
|
||||
await Assertions.Expect(page.Locator(".modal-title:has-text('Delete External System')")).ToBeVisibleAsync();
|
||||
await page.Locator(".modal-footer .btn-danger").ClickAsync();
|
||||
|
||||
// Single web-first assertion on the success toast — toasts auto-dismiss at 5s,
|
||||
// so we do NOT chase .toast-body in a second sequential check.
|
||||
await Assertions.Expect(page.Locator(".toast", new() { HasText = "Deleted." }))
|
||||
.ToHaveCountAsync(1, new() { Timeout = 15_000 });
|
||||
|
||||
// The card must be gone.
|
||||
await Assertions.Expect(card).ToHaveCountAsync(0, new() { Timeout = 10_000 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Best-effort teardown: the happy path deletes via the UI, so this finds
|
||||
// nothing; it only fires if the UI path failed mid-way.
|
||||
foreach (var id in await CliRunner.ListExternalSystemIdsByNamePrefixAsync(name))
|
||||
await CliRunner.DeleteExternalSystemAsync(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saving the create form with both fields blank surfaces the inline validation
|
||||
/// error and keeps the user on the create page. Mutates nothing.
|
||||
/// </summary>
|
||||
[SkippableFact]
|
||||
public async Task CreateForm_EmptyFields_ShowsInlineError()
|
||||
{
|
||||
Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason);
|
||||
|
||||
var page = await _pw.NewAuthenticatedPageAsync();
|
||||
|
||||
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/external-systems/create");
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||
|
||||
await page.Locator("button.btn-success:has-text('Save')").ClickAsync();
|
||||
|
||||
await Assertions.Expect(page.Locator("div.text-danger.small:has-text('Name and URL required.')"))
|
||||
.ToBeVisibleAsync();
|
||||
await Assertions.Expect(page)
|
||||
.ToHaveURLAsync(new System.Text.RegularExpressions.Regex("/create"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user