Files

157 lines
4.7 KiB
Python

"""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()