using Microsoft.Playwright; using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster; namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Admin; /// /// End-to-end CRUD round-trip for the LDAP Group Mappings admin page. /// Covers create → edit → delete via the UI against the running dev cluster. /// [Collection("Playwright")] public class LdapMappingCrudTests { private readonly PlaywrightFixture _fixture; public LdapMappingCrudTests(PlaywrightFixture fixture) { _fixture = fixture; } [SkippableFact] public async Task CreateEditDelete_LdapMapping_RoundTrips() { Skip.IfNot(await ClusterAvailability.IsAvailableAsync(), ClusterAvailability.SkipReason); // Keep the group name short and unique to avoid collisions with other test runs. var group = $"zztest-grp-{Guid.NewGuid():N}"[..18]; var page = await _fixture.NewAuthenticatedPageAsync(); // ── CREATE ──────────────────────────────────────────────────────────────── await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/admin/ldap-mappings/create"); await page.WaitForLoadStateAsync(LoadState.NetworkIdle); // The LDAP Group Name label has no `for=` attribute so GetByLabel does not // work. Locate the input that immediately follows the label text instead. await page.Locator("label:has-text('LDAP Group Name') + input.form-control.form-control-sm").FillAsync(group); await page.SelectOptionAsync(".form-select.form-select-sm", "Designer"); await page.ClickAsync("button.btn.btn-success.btn-sm:has-text('Save')"); // Wait for Blazor enhanced navigation back to the list page. await PlaywrightFixture.WaitForPathAsync(page, "/admin/ldap-mappings", excludePath: "/create"); await page.WaitForLoadStateAsync(LoadState.NetworkIdle); // The new row must be visible on the list. var newRow = page.Locator("tr", new() { HasText = group }); await Assertions.Expect(newRow).ToBeVisibleAsync(); // ── EDIT ────────────────────────────────────────────────────────────────── // Click the Edit button within that row. await newRow.Locator("button.btn.btn-outline-primary.btn-sm:has-text('Edit')").ClickAsync(); await PlaywrightFixture.WaitForPathAsync(page, "/edit"); await page.WaitForLoadStateAsync(LoadState.NetworkIdle); // Change the role to Viewer and save. await page.SelectOptionAsync(".form-select.form-select-sm", "Viewer"); await page.ClickAsync("button.btn.btn-success.btn-sm:has-text('Save')"); await PlaywrightFixture.WaitForPathAsync(page, "/admin/ldap-mappings", excludePath: "/edit"); await page.WaitForLoadStateAsync(LoadState.NetworkIdle); // Row must still be present after the edit. var editedRow = page.Locator("tr", new() { HasText = group }); await Assertions.Expect(editedRow).ToBeVisibleAsync(); // ── DELETE (no confirmation dialog) ─────────────────────────────────────── // Scope all dropdown interactions to the row's .dropdown container so we // never accidentally match a Delete button from another row's menu. var rowDropdown = editedRow.Locator(".dropdown"); var kebab = rowDropdown.Locator("button[aria-label^='More actions']"); await kebab.ClickAsync(); // Click Delete in the now-open dropdown within this row — no confirm dialog. var deleteBtn = rowDropdown.Locator(".dropdown-menu button.dropdown-item.text-danger"); await deleteBtn.ClickAsync(); await page.WaitForLoadStateAsync(LoadState.NetworkIdle); // The row must be gone. await Assertions.Expect(page.Locator("tr", new() { HasText = group })) .ToHaveCountAsync(0, new() { Timeout = 10_000 }); // TODO: best-effort cleanup if a mid-test failure left a zztest-grp-* row. // The happy path deletes via the UI above; no CLI cleanup needed for the passing case. } }