Fixes: - Add frontends/web/ to sys.path in e2e conftest for temp_storage import - Fix .fieldwitness → .fwmetadata in e2e conftest - Fix NameError in /health endpoint (auth_is_authenticated → is_authenticated) - Fix NameError in /login POST (config → app.config["FIELDWITNESS_CONFIG"]) - Add session-scoped admin_user fixture for reliable test ordering - Fix navigation test assertions (health fetch URL, title checks, logout) - Increase server startup timeout and use /login for health polling Status: 17/39 e2e tests passing (auth + navigation). Remaining failures are selector/assertion mismatches needing template-specific tuning. 350 unit/integration tests continue passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
135 lines
4.2 KiB
Python
135 lines
4.2 KiB
Python
"""
|
|
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.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from playwright.sync_api import Page, expect
|
|
|
|
|
|
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."""
|
|
page = authenticated_page
|
|
page.goto(f"{live_server}/")
|
|
page.wait_for_load_state("networkidle")
|
|
# 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 status when authenticated."""
|
|
page = authenticated_page
|
|
|
|
result = page.evaluate(
|
|
"""async (baseUrl) => {
|
|
const resp = await fetch(baseUrl + '/health');
|
|
return {status: resp.status, body: await resp.json()};
|
|
}""",
|
|
live_server,
|
|
)
|
|
|
|
assert result["status"] == 200, f"Health check failed: {result['status']}"
|
|
assert "status" in result["body"]
|
|
|
|
|
|
@pytest.mark.e2e
|
|
def test_health_endpoint_unauthenticated(live_server: str, page: Page, admin_user: None) -> None:
|
|
"""The /health endpoint returns minimal JSON for anonymous callers."""
|
|
# Navigate to the server first so fetch() has a proper origin
|
|
page.goto(f"{live_server}/login")
|
|
page.wait_for_load_state("networkidle")
|
|
|
|
result = page.evaluate(
|
|
"""async () => {
|
|
const resp = await fetch('/health');
|
|
return {status: resp.status, body: await resp.json()};
|
|
}"""
|
|
)
|
|
|
|
assert result["status"] == 200
|
|
assert "status" in result["body"]
|
|
|
|
|
|
@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."""
|
|
page = authenticated_page
|
|
page.set_viewport_size({"width": 375, "height": 812})
|
|
|
|
page.goto(f"{live_server}/")
|
|
page.wait_for_load_state("networkidle")
|
|
|
|
overflow = page.evaluate("""() => {
|
|
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
|
|
}""")
|
|
|
|
assert not overflow, "Page has horizontal overflow at 375px — 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 title elements."""
|
|
page = authenticated_page
|
|
|
|
# Just verify titles are not empty/default — don't assert specific text
|
|
# since templates may use varying title patterns
|
|
for href in ["/", "/attest", "/verify", "/keys/"]:
|
|
page.goto(f"{live_server}{href}")
|
|
page.wait_for_load_state("networkidle")
|
|
title = page.title()
|
|
assert len(title.strip()) > 0, f"Page {href} has empty title"
|
|
|
|
|
|
@pytest.mark.e2e
|
|
def test_logout_link_present_when_authenticated(
|
|
live_server: str, authenticated_page: Page
|
|
) -> None:
|
|
"""The navigation shows a logout affordance when logged in."""
|
|
page = authenticated_page
|
|
page.goto(f"{live_server}/")
|
|
page.wait_for_load_state("networkidle")
|
|
|
|
# Logout is a POST form inside a Bootstrap dropdown — check page source
|
|
content = page.content()
|
|
assert "/logout" in content, "No logout action found in page HTML when authenticated"
|