From 79586ca5ad1f2a75793db3f7986b87a7dddd5bc5 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 6 Jun 2026 12:16:50 -0400 Subject: [PATCH] test(e2e): row-scope API-key kebab dropdown selectors + visibility-gate items (review fix) --- .../Admin/ApiKeyCrudTests.cs | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Admin/ApiKeyCrudTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Admin/ApiKeyCrudTests.cs index 61ff33d2..76d59f05 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Admin/ApiKeyCrudTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Admin/ApiKeyCrudTests.cs @@ -172,20 +172,30 @@ public sealed class ApiKeyCrudTests : IClassFixture var row = page.Locator("tr").Filter(new() { HasText = keyName }); await Assertions.Expect(row).ToBeVisibleAsync(new() { Timeout = 15_000 }); - var kebab = page.Locator($"button[aria-label=\"More actions for {keyName}\"]"); + // Scope ALL dropdown interactions to THIS row's .dropdown container so the kebab and + // its menu items can never multi-match against another row's (hidden) menu under + // Playwright strict mode (e.g. when the list has test residue / multiple keys). + var rowDropdown = row.Locator(".dropdown"); + var kebab = rowDropdown.Locator("button[aria-label^='More actions']"); var disabledBadge = row.Locator("span.badge.bg-secondary"); - // Open the kebab (Bootstrap dropdown) and click Disable. The dropdown item is only - // visible/clickable once the menu is .show; Playwright auto-waits for it. + // Open the kebab (Bootstrap dropdown) and click Disable. Gate the click on the item's + // visibility so we don't race the Bootstrap open-transition before the menu is .show. await kebab.ClickAsync(); - await page.Locator("button.dropdown-item").Filter(new() { HasText = "Disable" }).ClickAsync(); + var disableItem = rowDropdown.Locator(".dropdown-menu button.dropdown-item") + .Filter(new() { HasText = "Disable" }); + await Assertions.Expect(disableItem).ToBeVisibleAsync(); + await disableItem.ClickAsync(); // Authoritative: the "Disabled" badge appears on the row's name cell. await Assertions.Expect(disabledBadge).ToBeVisibleAsync(new() { Timeout = 10_000 }); // Re-open the kebab and click Enable; the badge must disappear from this row. await kebab.ClickAsync(); - await page.Locator("button.dropdown-item").Filter(new() { HasText = "Enable" }).ClickAsync(); + var enableItem = rowDropdown.Locator(".dropdown-menu button.dropdown-item") + .Filter(new() { HasText = "Enable" }); + await Assertions.Expect(enableItem).ToBeVisibleAsync(); + await enableItem.ClickAsync(); await Assertions.Expect(disabledBadge).ToHaveCountAsync(0, new() { Timeout = 10_000 }); } @@ -219,9 +229,19 @@ public sealed class ApiKeyCrudTests : IClassFixture var row = page.Locator("tr").Filter(new() { HasText = keyName }); await Assertions.Expect(row).ToBeVisibleAsync(new() { Timeout = 15_000 }); - // Open the kebab and click the danger "Delete" item. - await page.Locator($"button[aria-label=\"More actions for {keyName}\"]").ClickAsync(); - await page.Locator(".dropdown-item.text-danger").Filter(new() { HasText = "Delete" }).ClickAsync(); + // Scope ALL dropdown interactions to THIS row's .dropdown container so the kebab and + // its menu items can never multi-match against another row's (hidden) menu under + // Playwright strict mode (e.g. when the list has test residue / multiple keys). + var rowDropdown = row.Locator(".dropdown"); + var kebab = rowDropdown.Locator("button[aria-label^='More actions']"); + + // Open the kebab and click the danger "Delete" item. Gate the click on the item's + // visibility so we don't race the Bootstrap open-transition before the menu is .show. + await kebab.ClickAsync(); + var deleteItem = rowDropdown.Locator(".dropdown-menu button.dropdown-item") + .Filter(new() { HasText = "Delete" }); + await Assertions.Expect(deleteItem).ToBeVisibleAsync(); + await deleteItem.ClickAsync(); // The confirm dialog must appear before we confirm. await Assertions.Expect(page.Locator(".modal-title").Filter(new() { HasText = "Delete API Key" }))