test(e2e): row-scope API-key kebab dropdown selectors + visibility-gate items (review fix)

This commit is contained in:
Joseph Doherty
2026-06-06 12:16:50 -04:00
parent 57ca5d6321
commit 79586ca5ad
@@ -172,20 +172,30 @@ public sealed class ApiKeyCrudTests : IClassFixture<ApiSurfaceFixture>
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<ApiSurfaceFixture>
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" }))