"""U3 — the declared embed seam, in a real DOM. The Python suite can prove what the server OFFERS. It cannot prove where a fragment lands, whether the author's own markup survived the mount, or whether four radio groups scattered down a report still submit as one POST — and that last one is the operator's most important workflow. Before U3 those properties were true by construction, because the server did the placing and the `form=` bindings were static by the time the page was parsed. Now they are true because `/_booth/embed.js` does it in a live document, which is a different kind of claim and needs a different kind of test. So: a real uvicorn on an ephemeral port, a real Chromium. SKIPS, NEVER FAILS, when playwright or the shared browser is unavailable. The box-wide store at /opt/ms-playwright pins specific Chromium revisions and a playwright release that wants a newer one dies with an opaque "Executable doesn't exist" — see pyproject's version bound. A test layer that goes red for an environment reason teaches nothing and trains people to ignore it. """ import json import socket import threading import time import pytest from booth.app import create_app playwright_api = pytest.importorskip( "playwright.sync_api", reason="playwright is not installed" ) @pytest.fixture(scope="module") def browser(): with playwright_api.sync_playwright() as pw: try: b = pw.chromium.launch() except Exception as exc: # noqa: BLE001 - any launch failure is a skip pytest.skip(f"no usable chromium: {exc}") yield b b.close() @pytest.fixture def live(tmp_path): """A real server, because a browser cannot talk to a TestClient.""" import uvicorn sock = socket.socket() sock.bind(("127.0.0.1", 0)) port = sock.getsockname()[1] sock.close() app = create_app(tmp_path, ttl_hours=24, start_sweeper=False) config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error") server = uvicorn.Server(config) thread = threading.Thread(target=server.run, daemon=True) thread.start() deadline = time.time() + 10 while not server.started and time.time() < deadline: time.sleep(0.02) if not server.started: pytest.skip("uvicorn did not come up") try: yield f"http://127.0.0.1:{port}", tmp_path finally: server.should_exit = True thread.join(timeout=10) SEAM = '' def _multi(booth): from booth.marks import declare_pick booth.mkdir(parents=True, exist_ok=True) declare_pick(booth, "batch", {"title": "Round one", "questions": [ {"key": "r1", "prompt": "First?", "options": ["keep", "cut"]}, {"key": "r2", "prompt": "Second?", "options": ["keep", "cut"]}, ]}) return booth def _single(booth): from booth.marks import declare_pick booth.mkdir(parents=True, exist_ok=True) declare_pick(booth, "winner", {"prompt": "Which render wins?", "options": ["A — baseline", "B — async"]}) return booth def _open(browser, base, name, html, booth): (booth / "index.html").write_text(html, encoding="utf-8") page = browser.new_page() page.goto(f"{base}/b/{name}/", wait_until="networkidle") return page def _answer(booth): raw = json.loads((booth / ".marks.json").read_text()) return raw["marks"][0].get("answer") # ---- the chrome -------------------------------------------------------------- def test_a_declaring_page_gets_its_chrome_mounted(browser, live): base, data = live b = _single(data / "b") page = _open(browser, base, "b", f"