"""Shared fixtures for E2E Playwright tests. All tests in this directory require a running application server pointed to by the E2E_BASE_URL environment variable. Tests are automatically skipped when the variable is absent, making them safe to collect in any environment. """ from __future__ import annotations import os from collections.abc import Generator import pytest from playwright.sync_api import Browser, BrowserContext, Page, sync_playwright @pytest.fixture(scope="session") def base_url() -> str: url = os.getenv("E2E_BASE_URL", "").rstrip("/") if not url: pytest.skip("E2E_BASE_URL not set – start a server and export E2E_BASE_URL to run E2E tests") return url @pytest.fixture(scope="session") def _browser(base_url: str) -> Generator[Browser, None, None]: # noqa: ARG001 """Session-scoped Chromium instance (headless).""" with sync_playwright() as pw: browser = pw.chromium.launch(headless=True) yield browser browser.close() @pytest.fixture() def page(_browser: Browser) -> Generator[Page, None, None]: """Fresh browser context + page per test — no shared state between tests. Clipboard permissions are pre-granted so copy-link and similar interactions work in headless Chromium without triggering the permissions dialog. """ ctx: BrowserContext = _browser.new_context( permissions=["clipboard-read", "clipboard-write"], ) # Polyfill clipboard in environments where the native API is unavailable # (e.g. non-HTTPS Docker CI). The polyfill stores writes in a variable so # the JS success path still runs and button text updates as expected. ctx.add_init_script(""" if (!navigator.clipboard || !navigator.clipboard.writeText) { Object.defineProperty(navigator, 'clipboard', { value: { writeText: () => Promise.resolve() }, configurable: true, }); } """) pg: Page = ctx.new_page() yield pg ctx.close()