"""Fixtures for the browser end-to-end suite. The capture flags matter: with a fake UI and a fake device, Chromium answers `getDisplayMedia()` with a real, *moving* 1280x720 monitor stream. That means these tests exercise the genuine capture -> encode -> SRTP -> decode -> paint path rather than a stubbed-out media source. """ from __future__ import annotations import os import socket import subprocess import sys import time from collections.abc import Iterator, Mapping, Sequence from pathlib import Path import pytest from playwright.sync_api import expect, sync_playwright pytestmark = pytest.mark.e2e REPO_ROOT = Path(__file__).resolve().parents[2] CHROMIUM_ARGS = [ "--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream", "--use-fake-device-for-audio-capture", # Deliberate — do not "fix" this away: with this flag the suite cannot # prove the *browser's* autoplay rule. The audio tests therefore assert # mirror's own `muted` state (the application's policy), and never claim # to exercise a real browser's autoplay behaviour. See test_audio_share.py. "--autoplay-policy=no-user-gesture-required", # Two Chromium instances must each get their own UDP ports; never proxy. "--no-sandbox", "--disable-dev-shm-usage", "--no-proxy-server", ] def _free_port() -> int: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe: probe.bind(("127.0.0.1", 0)) return probe.getsockname()[1] def _start( extra_args: Sequence[str] = (), extra_env: Mapping[str, str] = {}, ) -> tuple[str, subprocess.Popen[str]]: """Spawn a real `mirror serve` on a free port and wait for it to answer. `extra_env` overrides inherited environment variables (it wins over `os.environ`, so a developer's `MIRROR_*` cannot leak into a test that names the opposite value).""" import httpx port = _free_port() process = subprocess.Popen( [ sys.executable, "-m", "mirror", "serve", "--host", "127.0.0.1", "--port", str(port), "--log-level", "warning", *extra_args, ], cwd=REPO_ROOT, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env={ **os.environ, "MIRROR_ICE_SERVERS": "", "PYTHONUNBUFFERED": "1", # A stray DEBUGPY in the developer's shell would stall the server. "DEBUGPY": "0", **extra_env, }, ) url = f"http://127.0.0.1:{port}" deadline = time.time() + 25 last_error: Exception | None = None while time.time() < deadline: if process.poll() is not None: output = process.stdout.read() if process.stdout else "" raise RuntimeError(f"mirror serve exited early ({process.returncode}):\n{output}") try: # /healthz is the readiness probe *and* the one route an address gate # never refuses, so it works for a restricted server too. if httpx.get(f"{url}/healthz", timeout=1.0).status_code == 200: return url, process except Exception as exc: # still booting last_error = exc time.sleep(0.15) process.terminate() raise RuntimeError(f"mirror serve never came up: {last_error}") def _stop(process: subprocess.Popen[str]) -> None: process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: # pragma: no cover process.kill() @pytest.fixture(scope="session") def base_url() -> Iterator[str]: """A real `mirror serve` process on a real port — not a test double.""" url, process = _start() yield url _stop(process) @pytest.fixture(scope="session") def restricted_url() -> Iterator[str]: """A server that admits only 203.0.113.0/24 — so it refuses this machine. That range is reserved for documentation by RFC 5737, which is why it is safe to use as "everybody else": nothing real is ever locked out by it. """ url, process = _start(["--allow-cidr", "203.0.113.0/24"]) yield url _stop(process) @pytest.fixture(scope="session") def browser(): with sync_playwright() as playwright: # channel="chromium" = the full Chromium binary in new headless mode. # The trimmed "headless shell" has no media stack at all and hides # navigator.mediaDevices entirely. instance = playwright.chromium.launch(channel="chromium", headless=True, args=CHROMIUM_ARGS) yield instance instance.close() @pytest.fixture def context(browser): """One browser context == one device. Closed after every test.""" created = browser.new_context(viewport={"width": 1100, "height": 720}) yield created created.close() @pytest.fixture def page(context): return context.new_page() def start_sharing(page) -> None: page.locator("#start").click() expect(page.locator("body")).to_have_attribute("data-live", "on", timeout=15_000) def stop_sharing(page) -> None: page.locator("#stop").click() expect(page.locator("body")).to_have_attribute("data-live", "off", timeout=10_000) # Returns {width, height, currentTime, distinct} for the first