"""Fixtures for Playwright frontend E2E tests. Architecture: - Each test gets an isolated temp SQLite DB. - A FastAPI uvicorn server runs on a random port in a background thread. - CANTEEN_SKIP_AUTH=1 skips auth middleware so Playwright doesn't need real tokens. - Playwright launches system Google Chrome (Ubuntu 26.04 can't install bundled browsers, and Chrome 148 SIGTRAPs with certain --disable-features flags; ignore_default_args workaround applied). - Viewport: iPhone 14 (390x844), Geolocation: Orlando, FL. """ # Chrome 148 on Ubuntu 26.04 (kernel 7.0) SIGTRAPs when Playwright's default # --disable-features and related flags are passed. Ignoring these defaults # allows Chrome to launch cleanly with DevTools protocol. CHROME_IGNORE_DEFAULTS = [ '--disable-field-trial-config', '--disable-background-networking', '--disable-background-timer-throttling', '--disable-breakpad', '--disable-client-side-phishing-detection', '--disable-default-apps', '--disable-dev-shm-usage', '--disable-extensions', '--disable-hang-monitor', '--disable-ipc-flooding-protection', '--disable-popup-blocking', '--disable-prompt-on-repost', '--disable-renderer-backgrounding', '--disable-sync', '--enable-automation', ] import importlib import os import socket import sys import tempfile import threading import time from pathlib import Path import pytest import uvicorn from playwright.sync_api import sync_playwright # Ensure project root is on path PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent sys.path.insert(0, str(PROJECT_ROOT)) # Global env: skip auth for all tests os.environ["CANTEEN_SKIP_AUTH"] = "1" # ── helpers ──────────────────────────────────────────────────────────────── def _find_free_port() -> int: """Find an available TCP port.""" with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(("", 0)) return s.getsockname()[1] # ── fixtures ──────────────────────────────────────────────────────────────── @pytest.fixture(scope="session") def browser(): """Launch system Chromium once per test session.""" pw = sync_playwright().start() browser = pw.chromium.launch( executable_path="/usr/bin/google-chrome-stable", headless=True, args=["--no-sandbox", "--disable-gpu"], ignore_default_args=CHROME_IGNORE_DEFAULTS, ) yield browser browser.close() pw.stop() @pytest.fixture def test_db_path(): """Create an isolated temp SQLite DB for each test.""" fd, path = tempfile.mkstemp(suffix=".db", prefix="canteen_frontend_test_") os.close(fd) os.environ["CANTEEN_DB_PATH"] = path yield path # Cleanup DB and WAL/SHM/journal files for suffix in ("", "-shm", "-wal", "-journal"): p = Path(path + suffix) if p.exists(): p.unlink() @pytest.fixture def live_server(test_db_path): """Start FastAPI + uvicorn on a random port in a background thread. Returns the base URL (e.g. 'http://127.0.0.1:12345'). """ port = _find_free_port() os.environ["CANTEEN_PORT"] = str(port) # Reload the server module so DB_PATH picks up the current # CANTEEN_DB_PATH (module-level constant read at import time). import server importlib.reload(server) app = server.app t = threading.Thread( target=uvicorn.run, kwargs={ "app": "server:app", "host": "127.0.0.1", "port": port, "log_level": "error", }, daemon=True, ) t.start() base_url = f"http://127.0.0.1:{port}" # Wait for server to be ready deadline = time.time() + 10 import urllib.request while time.time() < deadline: try: urllib.request.urlopen(f"{base_url}/", timeout=1) break except Exception: time.sleep(0.1) else: raise RuntimeError(f"Server did not start on {base_url} within 10s") yield base_url # Thread is daemon, will exit when test process ends @pytest.fixture def page(browser, live_server): """Create a Playwright page pointed at the live server. iPhone 14 viewport, Orlando FL geolocation, geolocation permission granted. """ context = browser.new_context( viewport={"width": 390, "height": 844}, geolocation={"latitude": 28.3852, "longitude": -81.5639}, permissions=["geolocation"], ) page = context.new_page() page.goto(live_server) yield page context.close()