using Microsoft.Playwright; using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster; using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment; using Xunit; 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 : IClassFixture { private readonly PlaywrightFixture _fixture; private readonly DeploymentFixture _cluster; public TemplateCrudTests(PlaywrightFixture fixture, DeploymentFixture cluster) { _fixture = fixture; _cluster = cluster; } [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')"); // TemplateEdit's four page-embedded modals were migrated onto the global // DialogService host, so the Add-Attribute modal IS the DialogHost modal now // (`modal fade show d-block`, title in an h5). The old `:not(.fade)` guard // against the page-local variant would exclude it — TemplateEdit renders no // page-local modal of its own any more, so `.modal.show.d-block` is // unambiguous on this page. 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: +