using Microsoft.Playwright; using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster; namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Transport; /// /// Happy-path end-to-end for the Transport Export wizard (Component #24, Task T21). /// /// /// Drives the full four-step wizard at /design/transport/export against the /// running dev cluster, exporting a single throwaway template the test creates via /// the CLI: /// /// /// Step 1 — Select : filter to the zztest template and tick its checkbox. /// Step 2 — Review : confirm the resolved closure (Next). /// Step 3 — Encrypt: supply a matching passphrase (≥ 8 chars) and Export. /// Step 4 — Download: assert the success summary. /// /// /// /// Step 1's template list is a TemplateFolderTree in checkbox mode. The inner /// TreeView renders an <input type="checkbox" class="tv-checkbox"> /// per node and the name in a sibling span.tv-label; there is no /// <label for> association, and in checkbox mode clicking the label text /// does NOT toggle the box (content-click only selects in Single mode). So the test /// clicks the checkbox input itself, scoped to the row carrying the template's /// label text. /// /// /// /// Step 4's download is a JS-interop blob stream (NOT a DOM <a download>), /// so the test asserts the success DOM ([data-testid='download-summary']) rather /// than waiting for a Playwright download event — which would hang, since no /// browser-level download fires. /// /// [Collection("Playwright")] public sealed class TransportExportTests { private readonly PlaywrightFixture _fixture; public TransportExportTests(PlaywrightFixture fixture) { _fixture = fixture; } [SkippableFact] public async Task ExportTemplate_ReachesDownloadSummary() { Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); var tmplName = CliRunner.UniqueName("exptmpl"); var tmplId = await CliRunner.CreateTemplateAsync(tmplName); await CliRunner.AddAttributeAsync(tmplId, "Value", "Double"); try { var page = await _fixture.NewAuthenticatedPageAsync("multi-role", "password"); await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/design/transport/export"); // Wait for Step-1 to render (OnInitializedAsync makes 8 repo calls over SignalR // before the template tree appears — NetworkIdle is an unreliable proxy for this). await Assertions.Expect(page.Locator("[data-testid='group-templates']")) .ToBeVisibleAsync(new() { Timeout = 15_000 }); // ── STEP 1: Select ──────────────────────────────────────────────────────── // Narrow the tree to just the zztest template, then tick its checkbox. await page.Locator("#export-filter").FillAsync(tmplName); // The template node is a TreeView leaf row: a checkbox input plus a // span.tv-label carrying the name. Clicking the label text is a no-op in // checkbox mode, so we click the input directly, scoped to the row whose // label matches the unique zztest name. var templateCheckbox = page .Locator("[data-testid='group-templates'] li") .Filter(new() { Has = page.Locator($"span.tv-label:text-is('{tmplName}')") }) .Locator("input.tv-checkbox") .First; await Assertions.Expect(templateCheckbox).ToBeVisibleAsync(new() { Timeout = 10_000 }); await templateCheckbox.CheckAsync(); // Next enables only once a selection exists; clicking it resolves the closure. var step1Next = page.Locator("button.btn.btn-primary:has-text('Next')"); await Assertions.Expect(step1Next).ToBeEnabledAsync(new() { Timeout = 10_000 }); await step1Next.ClickAsync(); // ── STEP 2: Review ──────────────────────────────────────────────────────── // The seed group lists the picked template; Next is always enabled here. // Scope the Next click to the Step-2 panel — identified by its unique // #include-deps toggle — so it cannot accidentally match a Step-1 button. await Assertions.Expect(page.Locator("[data-testid='seed-group']")) .ToBeVisibleAsync(new() { Timeout = 15_000 }); await page.Locator("div:has(#include-deps) button.btn.btn-primary:has-text('Next')") .ClickAsync(); // ── STEP 3: Encrypt ─────────────────────────────────────────────────────── const string passphrase = "zztest-passphrase-123"; await Assertions.Expect(page.Locator("#passphrase")) .ToBeVisibleAsync(new() { Timeout = 10_000 }); await page.Locator("#passphrase").FillAsync(passphrase); await page.Locator("#passphrase-confirm").FillAsync(passphrase); // Export enables only when the two passphrases match AND length ≥ 8. var exportBtn = page.Locator("button.btn.btn-primary:has-text('Export')"); await Assertions.Expect(exportBtn).ToBeEnabledAsync(new() { Timeout = 10_000 }); await exportBtn.ClickAsync(); // ── STEP 4: Download ────────────────────────────────────────────────────── // JS-interop blob download — assert the success DOM, never WaitForDownload. var summary = page.Locator("[data-testid='download-summary']"); await Assertions.Expect(summary).ToBeVisibleAsync(new() { Timeout = 20_000 }); await Assertions.Expect(summary).ToContainTextAsync("Bundle ready"); } finally { await CliRunner.DeleteTemplateAsync(tmplId); } } }