test(playwright): repair 14 selector-drift failures from two Central UI refactors

All 14 pre-existing Playwright failures triage to selector drift, not rig data,
timing, or application defects — every one is a test-side assumption that two
merged UI refactors invalidated without updating this suite.

`9e243493` (2026-08-11 density sweep + Theme 0.4.1) regrouped page/dialog action
buttons into `btn-group btn-group-sm`, moving the size class off the button and
onto the group. Twelve selectors still pinned `.btn-sm` on the button itself:
ConfigurationAuditLog's Search (4 tests), the Topology Create/Move dialog footers
(3), the Notification Lists row Edit/Delete (2), and the Notification Report row
Retry/Discard (2). The same sweep re-cast SiteForm's per-node subsections from
bare `<h6>` headings to Bootstrap cards, so `h6:has-text('Node A')` matches
nothing (1). Note the swept surfaces are a subset — the recipient table on the
notification-list edit form, OffsetPager, DiffDialog and friends still carry
`.btn-sm` on the button, so the selectors are deliberately asymmetric; the
remaining `.btn-sm` selectors in this suite were each re-verified against current
markup rather than swept along.

`a506b19d` moved TemplateEdit's page-embedded modals onto the DialogService host.
The attribute modal is now the DialogHost modal (`modal fade show d-block`, title
in an h5), so the `:not(.fade)` guard written to exclude DialogHost — plus the
`h6.modal-title` assertion — excluded the very modal under test (2). TemplateEdit
renders no page-local modal any more, so the guard has nothing left to guard.

Comments at each site record why the size class is absent, so a future reader
does not "fix" the selectors back.

Verified: 14 failed / 158 passed baseline -> the 36 tests in the seven affected
classes all green; solution build 0 warnings / 0 errors.
This commit is contained in:
Joseph Doherty
2026-08-15 03:33:51 -04:00
parent 2b74851f96
commit 014038fbbd
7 changed files with 49 additions and 26 deletions
@@ -109,8 +109,10 @@ public class NotificationActionTests
var (page, row) = await SeedAndLocateParkedRowAsync(notificationId, marker, subject);
// The Retry button is only rendered for Parked rows (btn-outline-success).
await Assertions.Expect(row.Locator("button.btn.btn-outline-success.btn-sm")).ToBeVisibleAsync();
await row.Locator("button.btn.btn-outline-success.btn-sm").ClickAsync();
// Its size class moved onto the enclosing `btn-group btn-group-sm` in the
// 2026-08-11 UI density sweep — do not re-add `.btn-sm` here.
await Assertions.Expect(row.Locator("button.btn.btn-outline-success")).ToBeVisibleAsync();
await row.Locator("button.btn.btn-outline-success").ClickAsync();
// Confirm the action — non-danger footer button labelled "Confirm".
var confirmButton = page.Locator(".modal-footer .btn-primary");
@@ -146,8 +148,10 @@ public class NotificationActionTests
var (page, row) = await SeedAndLocateParkedRowAsync(notificationId, marker, subject);
// The Discard button is only rendered for Parked rows (btn-outline-danger).
await Assertions.Expect(row.Locator("button.btn.btn-outline-danger.btn-sm")).ToBeVisibleAsync();
await row.Locator("button.btn.btn-outline-danger.btn-sm").ClickAsync();
// Its size class moved onto the enclosing `btn-group btn-group-sm` in the
// 2026-08-11 UI density sweep — do not re-add `.btn-sm` here.
await Assertions.Expect(row.Locator("button.btn.btn-outline-danger")).ToBeVisibleAsync();
await row.Locator("button.btn.btn-outline-danger").ClickAsync();
// Confirm the action — danger footer button labelled "Delete" (the discard
// dialog opens with danger: true).
@@ -65,7 +65,11 @@ public class NotificationListCrudTests
await Assertions.Expect(listRow).ToHaveCountAsync(1, new() { Timeout = 10_000 });
// ── ADD RECIPIENT ─────────────────────────────────────────────────────────
await listRow.Locator("button.btn-outline-primary.btn-sm:has-text('Edit')").ClickAsync();
// The list page's row actions moved into a `btn-group btn-group-sm` in the
// 2026-08-11 UI density sweep, so the size class sits on the group, not the
// button. The recipient table on the *edit form* was not swept and still
// carries `.btn-sm` on the button itself — hence the asymmetry below.
await listRow.Locator("button.btn-outline-primary:has-text('Edit')").ClickAsync();
// The Edit click triggers Blazor enhanced navigation (a SignalR round-trip that
// loads the edit form's data); wait for it to settle before asserting the heading.
@@ -96,7 +100,7 @@ public class NotificationListCrudTests
var listRowAgain = page.Locator("tr").Filter(new() { HasText = name });
// Make the row locator strict-mode-safe: assert exactly one match before acting.
await Assertions.Expect(listRowAgain).ToHaveCountAsync(1, new() { Timeout = 10_000 });
await listRowAgain.Locator("button.btn-outline-danger.btn-sm:has-text('Delete')").ClickAsync();
await listRowAgain.Locator("button.btn-outline-danger:has-text('Delete')").ClickAsync();
// Confirm the global danger dialog.
await Assertions.Expect(page.Locator(".modal-footer .btn-danger")).ToBeVisibleAsync();
@@ -172,7 +172,9 @@ public class SmsNotificationE2ETests
// ── OPEN EDIT — recipient inputs only render once the list exists ────────────
var listRow = page.Locator("tr").Filter(new() { HasText = name });
await Assertions.Expect(listRow).ToHaveCountAsync(1, new() { Timeout = 10_000 });
await listRow.Locator("button.btn-outline-primary.btn-sm:has-text('Edit')").ClickAsync();
// Row actions live in a `btn-group btn-group-sm` since the 2026-08-11 UI
// density sweep — the size class is on the group, not the button.
await listRow.Locator("button.btn-outline-primary:has-text('Edit')").ClickAsync();
// Blazor enhanced navigation (SignalR round-trip) loads the edit form's data.
await page.WaitForLoadStateAsync(LoadState.NetworkIdle);