diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/NotificationListCrudTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/NotificationListCrudTests.cs index 2e14d7d2..7ebb6c9c 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/NotificationListCrudTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/NotificationListCrudTests.cs @@ -61,10 +61,16 @@ public class NotificationListCrudTests var listRow = page.Locator("tr").Filter(new() { HasText = name }); await Assertions.Expect(listRow).ToBeVisibleAsync(); + // Make the row locator strict-mode-safe: assert exactly one match before acting. + 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 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. + await page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await Assertions.Expect(page.Locator("h4:has-text('Edit Notification List')")).ToBeVisibleAsync(); // The edit page has TWO "Name" text inputs (list name + recipient name). Scope the @@ -88,15 +94,18 @@ public class NotificationListCrudTests await page.WaitForLoadStateAsync(LoadState.NetworkIdle); 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(); // Confirm the global danger dialog. await Assertions.Expect(page.Locator(".modal-footer .btn-danger")).ToBeVisibleAsync(); await page.Locator(".modal-footer .btn-danger").ClickAsync(); - // Success toast appears (auto-dismisses at 5s, so assert promptly) and the row is gone. - await Assertions.Expect(page.Locator(".toast")).ToHaveCountAsync(1, new() { Timeout = 15_000 }); - await Assertions.Expect(page.Locator(".toast-body:has-text('Deleted.')")).ToBeVisibleAsync(); + // Assert the success toast in one web-first check — count + body text together — so the + // second assertion can't race the toast's 5s auto-dismiss. + await Assertions.Expect(page.Locator(".toast", new() { HasText = "Deleted." })) + .ToHaveCountAsync(1, new() { Timeout = 15_000 }); await Assertions.Expect(page.Locator("tr").Filter(new() { HasText = name })) .ToHaveCountAsync(0, new() { Timeout = 10_000 }); } @@ -123,6 +132,6 @@ public class NotificationListCrudTests await page.Locator("button.btn-success:has-text('Save')").ClickAsync(); await Assertions.Expect(page.Locator("div.text-danger.small:has-text('Name required.')")).ToBeVisibleAsync(); - Assert.Contains("/create", page.Url); + await Assertions.Expect(page).ToHaveURLAsync(new System.Text.RegularExpressions.Regex("/create")); } }