from contextlib import contextmanager import os import pytest from tests.e2e.config import E2ESettings playwright_api = pytest.importorskip("playwright.sync_api") # On Windows, ``localhost`` can resolve to IPv6 while Django's ephemeral live # server is listening on IPv4. Pin the test-only server to IPv4 so navigation # failures represent the application rather than resolver/bind ambiguity. os.environ.setdefault("DJANGO_LIVE_TEST_SERVER_ADDRESS", "127.0.0.1:0") @pytest.fixture(scope="session") def e2e_settings(): return E2ESettings.from_environment() @pytest.fixture def application_base_url(live_server, e2e_settings): """Configured deployment URL, falling back to Django's isolated local server.""" return e2e_settings.base_url or live_server.url @pytest.fixture def browser_page(request, e2e_settings): """Return a page factory with per-test isolation and automatic failure artifacts.""" @contextmanager def open_page(*, service_workers="block"): with playwright_api.sync_playwright() as playwright: launch_options = {"headless": e2e_settings.headless, "slow_mo": e2e_settings.slow_mo} try: browser = playwright.chromium.launch(**launch_options) except playwright_api.Error as bundled_error: try: browser = playwright.chromium.launch(channel="chrome", **launch_options) except playwright_api.Error: raise RuntimeError( "No Chromium browser is available. Run: python -m playwright install chromium" ) from bundled_error context = browser.new_context( viewport=e2e_settings.viewport, locale="fa-IR", timezone_id="Asia/Tehran", service_workers=service_workers, ) context.set_default_timeout(e2e_settings.action_timeout_ms) context.set_default_navigation_timeout(e2e_settings.navigation_timeout_ms) context.tracing.start(screenshots=True, snapshots=True, sources=True) page = context.new_page() page_errors = [] page.on("pageerror", lambda error: page_errors.append(str(error))) safe_name = request.node.nodeid.replace("::", "__").replace("/", "_").replace("\\", "_") artifact_dir = e2e_settings.artifact_dir / safe_name failed = False try: yield page if page_errors: raise AssertionError(f"Browser JavaScript errors: {page_errors}") except BaseException: failed = True artifact_dir.mkdir(parents=True, exist_ok=True) page.screenshot(path=artifact_dir / "failure.png", full_page=True) raise finally: if failed: context.tracing.stop(path=artifact_dir / "trace.zip") else: context.tracing.stop() context.close() browser.close() return open_page