""" e2e tests for general navigation and page health. These tests verify that: - The homepage loads after authentication. - All primary navigation links resolve without 5xx errors. - The layout is accessible at a mobile viewport width. The navigation link test does NOT follow every link exhaustively — it checks the primary links that appear in the base navigation bar (the links that every page shares). Blueprint-specific internal links are covered by their own test files. """ from __future__ import annotations import pytest from playwright.sync_api import Page, expect # Primary navigation links as rendered by base.html. # Each entry is (link text substring | href fragment, expected URL fragment). # We match by href since the link text includes Bootstrap icons which vary. PRIMARY_NAV_HREFS = [ "/", "/encode", "/decode", "/generate", "/attest", "/verify", "/keys/", "/fieldkit/", "/dropbox/admin", "/federation/", ] @pytest.mark.e2e def test_homepage_loads(live_server: str, authenticated_page: Page) -> None: """The index page loads after login and shows the main feature cards.""" page = authenticated_page page.goto(f"{live_server}/") page.wait_for_load_state("networkidle") # Core headings from index.html expect(page.locator("body")).to_contain_text("FieldWitness") # At least one of the feature card links is visible expect(page.locator("a[href='/encode']")).to_be_visible() @pytest.mark.e2e def test_all_nav_links_no_server_error(live_server: str, authenticated_page: Page) -> None: """Every primary navigation link returns a non-5xx response.""" page = authenticated_page errors: list[str] = [] for href in PRIMARY_NAV_HREFS: url = f"{live_server}{href}" response = page.goto(url) page.wait_for_load_state("networkidle") status = response.status if response else None if status is not None and status >= 500: errors.append(f"{href} → HTTP {status}") assert not errors, "Navigation links returned server errors:\n" + "\n".join(errors) @pytest.mark.e2e def test_health_endpoint_authenticated(live_server: str, authenticated_page: Page) -> None: """The /health endpoint returns JSON with full details when authenticated.""" import json page = authenticated_page # Use fetch() so we get the JSON body (page.goto would return HTML shell) result = page.evaluate("""async () => { const resp = await fetch('/health'); return {status: resp.status, body: await resp.json()}; }""") assert result["status"] == 200, f"Health check failed with status {result['status']}" data = result["body"] assert "status" in data, f"Unexpected health response: {data}" assert data["status"] in ("ok", "degraded"), f"Unknown health status: {data['status']}" @pytest.mark.e2e def test_health_endpoint_unauthenticated(live_server: str, page: Page) -> None: """The /health endpoint returns minimal JSON for unauthenticated callers.""" result = page.evaluate("""async () => { const resp = await fetch('/health'); return {status: resp.status, body: await resp.json()}; }""") assert result["status"] == 200 data = result["body"] # Unauthenticated response must have only status and version, not operational details assert "status" in data assert "modules" not in data, "Health leaked module details to unauthenticated caller" assert "keys" not in data, "Health leaked key details to unauthenticated caller" @pytest.mark.e2e def test_responsive_layout_mobile(live_server: str, authenticated_page: Page) -> None: """The index page renders without horizontal overflow at 375px viewport width.""" page = authenticated_page page.set_viewport_size({"width": 375, "height": 812}) page.goto(f"{live_server}/") page.wait_for_load_state("networkidle") # The page must render — verify the main heading is still in the DOM expect(page.locator("body")).to_contain_text("FieldWitness") # Check for horizontal overflow: scrollWidth should not exceed clientWidth overflow = page.evaluate("""() => { return document.documentElement.scrollWidth > document.documentElement.clientWidth; }""") assert not overflow, ( "Page has horizontal overflow at 375px viewport — layout breaks on mobile" ) @pytest.mark.e2e def test_page_titles_are_set(live_server: str, authenticated_page: Page) -> None: """Key pages have non-empty