using Microsoft.Playwright; namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests; [Collection("Playwright")] public class NavigationTests { private readonly PlaywrightFixture _fixture; public NavigationTests(PlaywrightFixture fixture) { _fixture = fixture; } [Fact] public async Task Dashboard_IsVisibleAfterLogin() { var page = await _fixture.NewAuthenticatedPageAsync(); // The nav sidebar should be visible with the brand. ToContainText, not // ToHaveText: the brand also carries the accent mark glyph (▮). await Expect(page.Locator(".brand")).ToContainTextAsync("ScadaBridge"); // The nav should contain "Dashboard" link (exact match to avoid "Health Dashboard") await Expect(page.GetByRole(AriaRole.Link, new() { Name = "Dashboard", Exact = true })).ToBeVisibleAsync(); } [Theory] [InlineData("Sites", "/admin/sites")] [InlineData("API Keys", "/admin/api-keys")] [InlineData("LDAP Mappings", "/admin/ldap-mappings")] public async Task AdminNavLinks_NavigateCorrectly(string linkText, string expectedPath) { var page = await _fixture.NewAuthenticatedPageAsync(); await ClickNavAndWait(page, linkText, expectedPath); } [Theory] [InlineData("SMTP Configuration", "/notifications/smtp")] [InlineData("Notification Lists", "/notifications/lists")] [InlineData("Notification Report", "/notifications/report")] [InlineData("Notification KPIs", "/notifications/kpis")] public async Task NotificationsNavLinks_NavigateCorrectly(string linkText, string expectedPath) { var page = await _fixture.NewAuthenticatedPageAsync(); await ClickNavAndWait(page, linkText, expectedPath); } [Theory] [InlineData("Templates", "/design/templates")] [InlineData("Shared Scripts", "/design/shared-scripts")] [InlineData("Connections", "/design/connections")] [InlineData("External Systems", "/design/external-systems")] public async Task DesignNavLinks_NavigateCorrectly(string linkText, string expectedPath) { var page = await _fixture.NewAuthenticatedPageAsync(); await ClickNavAndWait(page, linkText, expectedPath); } [Theory] [InlineData("Topology", "/deployment/topology")] [InlineData("Deployments", "/deployment/deployments")] public async Task DeploymentNavLinks_NavigateCorrectly(string linkText, string expectedPath) { var page = await _fixture.NewAuthenticatedPageAsync(); await ClickNavAndWait(page, linkText, expectedPath); } [Theory] [InlineData("Health Dashboard", "/monitoring/health")] [InlineData("Event Logs", "/monitoring/event-logs")] [InlineData("Parked Messages", "/monitoring/parked-messages")] public async Task MonitoringNavLinks_NavigateCorrectly(string linkText, string expectedPath) { var page = await _fixture.NewAuthenticatedPageAsync(); await ClickNavAndWait(page, linkText, expectedPath); } // Maps each navigable route to the exact heading text rendered by that page. private static readonly Dictionary RouteHeadings = new() { ["/admin/sites"] = "Site Management", ["/admin/api-keys"] = "API Key Management", ["/admin/ldap-mappings"] = "LDAP Group Mappings", ["/notifications/smtp"] = "SMTP Configuration", ["/notifications/lists"] = "Notification Lists", ["/notifications/report"] = "Notification Report", ["/notifications/kpis"] = "Notification KPIs", ["/design/templates"] = "Templates", ["/design/shared-scripts"] = "Shared Scripts", ["/design/connections"] = "Connections", ["/design/external-systems"] = "Integration Definitions", ["/deployment/topology"] = "Topology", ["/deployment/deployments"] = "Deployment Status", ["/monitoring/health"] = "Health Dashboard", ["/monitoring/event-logs"] = "Site Event Logs", ["/monitoring/parked-messages"] = "Parked Messages", }; private static async Task ClickNavAndWait(IPage page, string linkText, string expectedPath) { // Sections are collapsed by default — open them so the link is in the DOM. await PlaywrightFixture.ExpandAllNavSectionsAsync(page); await page.Locator($"nav a:has-text('{linkText}')").ClickAsync(); await PlaywrightFixture.WaitForPathAsync(page, expectedPath); Assert.Contains(expectedPath, page.Url); // Verify the destination page actually rendered its heading (catches 500s // and blank renders that a URL-only check would miss). // Every mapped route renders its heading as an

— tightened from the // broader "h1, h4, h5" to prevent strict-mode violations if multiple // heading elements match. var expectedHeading = RouteHeadings[expectedPath]; await Assertions.Expect(page.Locator("h4", new() { HasText = expectedHeading })).ToBeVisibleAsync(); } private static ILocatorAssertions Expect(ILocator locator) => Assertions.Expect(locator); }