"""Playwright E2E fixtures. This suite lives OUTSIDE `tests/` on purpose: the unit suite is zero-network (an autouse guard in tests/conftest.py fails any real socket), and pyproject's `testpaths = ["tests"]` means a plain `pytest` never collects this. Run it explicitly: ./.venv/Scripts/python.exe -m pytest e2e -m local # full local E2E (auto-starts dev_seed) STAGING_BASE_URL=https://tempuraml-lawn-estimator-dev.hf.space \ ./.venv/Scripts/python.exe -m pytest e2e -m staging Needs Playwright + chromium: `pip install -e ".[dev]"` then `python -m playwright install chromium`. """ from __future__ import annotations import os import subprocess import sys import time import urllib.request import pytest from playwright.sync_api import sync_playwright REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TOKEN = os.environ.get("DASHBOARD_DEV_TOKEN", "devtoken") ADMIN_TOKEN = os.environ.get("ADMIN_DEV_TOKEN", "admintoken") def pytest_configure(config): config.addinivalue_line("markers", "local: full E2E against a local dev_seed app") config.addinivalue_line("markers", "staging: smoke against the deployed staging app") def _wait(url: str, timeout: float = 45) -> bool: deadline = time.time() + timeout while time.time() < deadline: try: with urllib.request.urlopen(url, timeout=2) as r: if r.status == 200: return True except Exception: time.sleep(0.5) return False @pytest.fixture(scope="session") def app_url(): """Local app for `local` tests. Uses `APP_BASE_URL` if set (assumed already running), else starts scripts/dev_seed.py on :8000 and tears it down.""" env = os.environ.get("APP_BASE_URL") if env: yield env.rstrip("/") return py = os.path.join(REPO, ".venv", "Scripts", "python.exe") if not os.path.exists(py): py = sys.executable proc = subprocess.Popen([py, os.path.join(REPO, "scripts", "dev_seed.py")], cwd=REPO, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) try: assert _wait("http://127.0.0.1:8000/health"), "dev_seed did not come up on :8000" yield "http://127.0.0.1:8000" finally: proc.terminate() try: proc.wait(timeout=10) except Exception: proc.kill() @pytest.fixture(scope="session") def staging_url(): u = os.environ.get("STAGING_BASE_URL") if not u: pytest.skip("set STAGING_BASE_URL to run the staging smoke suite") return u.rstrip("/") @pytest.fixture(scope="session") def _pw(): with sync_playwright() as p: yield p @pytest.fixture(scope="session") def browser(_pw): b = _pw.chromium.launch(headless=True) yield b b.close() @pytest.fixture def page(browser): ctx = browser.new_context(viewport={"width": 1000, "height": 1300}) pg = ctx.new_page() yield pg ctx.close() @pytest.fixture def phone(browser): ctx = browser.new_context(viewport={"width": 390, "height": 844}) pg = ctx.new_page() yield pg ctx.close()