"""Phase B browser journey (US02-07): start a content-analysis job from the Analyze view and watch live progress arrive over the real SSE adapter, against a real server and durable worker. The frontend's SSE→polling event adapter and the worker are exercised end to end; the vision edge is the recording fake. """ from __future__ import annotations import pytest from playwright.sync_api import expect from tests.e2e._pipeline_harness import Server, seed_library, start_worker pytestmark = pytest.mark.phase_b @pytest.fixture def running_stack(tmp_path): seeded = seed_library(tmp_path, {"beach": 1}, {"beach": "sfw"}) log = tmp_path / "vision.log" server = Server(seeded).start() worker = start_worker(seeded, fake_vision_log=log) try: yield server finally: worker.terminate() server.stop() def test_analyze_start_shows_live_progress_and_completes(page, running_stack): errors = [] page.on("console", lambda m: errors.append(m.text) if m.type == "error" else None) page.goto(f"{running_stack.base}/app/#/analyze") run = page.get_by_test_id("run-analysis") run.wait_for() assert run.is_enabled() # one confirmed-SFW asset is eligible run.click() # Live progress streams into the activity log via the browser's SSE adapter. log = page.get_by_test_id("analyze-log") log.get_by_text("Started analysis job").wait_for() # On completion the view re-renders with the updated counts: the SFW asset is # now analysed. Waiting on this observable state (not a sleep) proves the whole # start → worker → SSE → done round trip worked. analyzed_value = ( page.get_by_test_id("analyze-counts") .locator(".stat") .filter(has_text="Analyzed") .locator(".stat-value") ) # ``expect`` re-queries the locator each poll, so it survives the re-render that # replaces the counts DOM when the job completes. expect(analyzed_value).to_have_text("1", timeout=15000) assert errors == [], f"console errors: {errors}"