"""Phase 01 smoke E2E: the app boots, serves the local frontend, and a chat round-trip never leaves a stale button (answer or error banner, both fine). Run: uv run pytest tests/e2e/test_smoke.py -v """ from __future__ import annotations import httpx from playwright.sync_api import Page, expect from e2e.auth_helpers import login def test_health_endpoint(app_url: str) -> None: r = httpx.get(f"{app_url}/api/health", timeout=5) assert r.status_code == 200 assert r.json()["status"] == "ok" def test_index_page_loads_locally(page: Page, app_url: str) -> None: page.goto(app_url) assert page.title() == "Brain of Reese" assert page.locator(".brand").is_visible() # No external (CDN) resources in the document. html = page.content() assert 'src="http' not in html assert 'href="http' not in html.replace('href="http://www.w3.org', "") def test_chat_roundtrip_never_stale_button(page: Page, app_url: str) -> None: # Phase 79: chat is require_user-gated (and the in-app token gate # locks #main for anonymous) — the round-trip signs in as admin # first (real form login; DB-free, so the story's DB-down error- # banner path still works). login(page, app_url, next="/") page.locator("#message-input").fill("hello brain") page.locator("#send-btn").click() # User bubble appears first. page.locator(".msg.user .bubble").first.wait_for(state="visible", timeout=10_000) # Then either a streamed Brain answer (DB up) or an error banner # (DB down) — but the turn must always complete. page.wait_for_selector( ".msg.brain .bubble, #kb-banner.is-error", state="visible", timeout=20_000, ) # Button is never left stuck: back to "Send" and enabled. expect(page.locator("#send-btn")).to_be_enabled(timeout=10_000) expect(page.locator("#send-label")).to_have_text("Send", timeout=10_000)