"""Playwright fixtures for the browser suites in `tests/e2e/`. The project depends on the `playwright` package, not on `pytest-playwright`, so the three fixtures the plugin would provide are defined here — a headless chromium, a fresh context and a fresh page, all function-scoped (see `browser` for why the scope is load-bearing). A fresh context per test matters too: `sessionStorage` (the anonymous `uc.token`, `uc.lastCode`) is scoped to it, so tests cannot observe each other's identity. """ from __future__ import annotations from typing import TYPE_CHECKING import pytest from playwright.sync_api import sync_playwright if TYPE_CHECKING: from collections.abc import Iterator from playwright.sync_api import Browser, BrowserContext, Page, ViewportSize # The default desktop viewport; story tests that care about small screens call # `page.set_viewport_size` before navigating. VIEWPORT: ViewportSize = {"width": 1280, "height": 720} @pytest.fixture def browser() -> Iterator[Browser]: """A headless chromium, live for exactly one test. Function scope is load-bearing, not shyness about startup time. The *synchronous* Playwright API drives the browser from an asyncio event loop it owns in the main thread, and that loop stays flagged as running for as long as `sync_playwright()` is open — so a session- or module-scoped fixture makes every later `async def` test in the run die with `RuntimeError: Runner.run() cannot be called from a running event loop` (pytest-asyncio, `asyncio_mode = "auto"`). Closing the whole stack inside the test leaves the main thread clean for whatever runs next. """ with sync_playwright() as playwright: launched = playwright.chromium.launch(headless=True) try: yield launched finally: launched.close() @pytest.fixture def context(browser: Browser) -> Iterator[BrowserContext]: """A brand-new browser context per test: isolated storage and cookies.""" created = browser.new_context(viewport=VIEWPORT) try: yield created finally: created.close() @pytest.fixture def page(context: BrowserContext) -> Iterator[Page]: """A tab inside `context`, closed after the test.""" opened = context.new_page() try: yield opened finally: opened.close()