From 19c4412fd12280d247c455df386aad96a907a58d Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 5 Jun 2026 10:28:53 -0400 Subject: [PATCH] test(e2e): cover Template create/add-attribute/delete round-trip --- .../Design/TemplateCrudTests.cs | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Design/TemplateCrudTests.cs diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Design/TemplateCrudTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Design/TemplateCrudTests.cs new file mode 100644 index 00000000..41c2a532 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Design/TemplateCrudTests.cs @@ -0,0 +1,120 @@ +using Microsoft.Playwright; +using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster; + +namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Design; + +/// +/// End-to-end round-trip for the Template design pages: +/// create → add attribute → delete, all via the Central UI against the +/// running dev cluster. +/// +[Collection("Playwright")] +public class TemplateCrudTests +{ + private readonly PlaywrightFixture _fixture; + + public TemplateCrudTests(PlaywrightFixture fixture) + { + _fixture = fixture; + } + + [SkippableFact] + public async Task CreateAddAttributeDelete_Template_RoundTrips() + { + Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); + + var name = $"zztest-tmpl-{Guid.NewGuid().ToString("N")[..8]}"; + var page = await _fixture.NewAuthenticatedPageAsync(); + + try + { + // ── CREATE ──────────────────────────────────────────────────────────────── + await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/templates/create"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Name input is label-anchored: + // followed by . + // Use the label's sibling input scoped to the containing div to avoid any + // strict-mode violation from the Description input (also form-control). + await page.Locator("div.mb-3:has(label:has-text('Name')) input.form-control").FillAsync(name); + + // Leave Parent Template at the default "(None - root template)". + // Click the green Create button. + await page.ClickAsync("button.btn.btn-success:has-text('Create')"); + + // After a successful create, Blazor navigates to /design/templates/{id}. + // Poll window.location until the path matches /design/templates/ + digits + // and does not still say /create. + await PlaywrightFixture.WaitForPathAsync(page, "/design/templates/", excludePath: "/create"); + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + + // Sanity: we must be on a numeric template detail URL. + Assert.Matches(@"/design/templates/\d+$", page.Url); + + // ── ADD ATTRIBUTE ───────────────────────────────────────────────────────── + // The Attributes tab is the default-active tab (_activeTab = "attributes"), + // so we don't need to click it, but we do wait for the tab panel to render. + await Assertions.Expect( + page.Locator("button.nav-link:has-text('Attributes')")) + .ToBeVisibleAsync(); + + // Click Add Attribute. + await page.ClickAsync("button.btn.btn-primary.btn-sm:has-text('Add Attribute')"); + + // The modal is a page-local .modal.show.d-block — NOT the global DialogHost. + var modal = page.Locator(".modal.show.d-block"); + await Assertions.Expect(modal).ToBeVisibleAsync(); + await Assertions.Expect(modal.Locator(".modal-title")).ToHaveTextAsync("Add Attribute"); + + // Fill Name field inside the modal. + await modal.Locator("div.col-12:has(label:has-text('Name')) input.form-control").FillAsync("Val"); + + // Select Data Type = Double. + // The select is label-anchored: +