157 lines
4.7 KiB
Python
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()
|