"""Browser-matrix stage-1 profile registry. Each profile is a deterministic recipe for a Playwright BrowserContext. The orchestrator runs the scan once per profile and aggregates the results with the worst-of-rule (a HIGH on any browser → HIGH overall). Keep this module dependency-light so it can be imported in unit tests without spawning Playwright. The Playwright glue lives in `services/multi_browser_scanner.py`. Profile schema: { "id": str canonical identifier shown in the audit report "label": str human-readable name "engine": str blink | gecko | webkit "channel": str? Playwright channel ('chrome' / 'msedge') "device": str? Playwright devices preset for mobile emulation "headless": bool "viewport": {"width": int, "height": int} (ignored when `device` set) "locale": str "timezone": str "user_agent": str? overridden UA when not derived from device } """ from __future__ import annotations DEFAULT_PROFILES: list[dict] = [ { "id": "chromium-headed-de", "label": "Chromium (Headed) · de-DE", "engine": "blink", "channel": None, "device": None, "headless": False, "viewport": {"width": 1920, "height": 1080}, "locale": "de-DE", "timezone": "Europe/Berlin", "user_agent": None, }, { "id": "firefox-headed-de", "label": "Firefox (Headed, ETP-Standard) · de-DE", "engine": "gecko", "channel": None, "device": None, "headless": False, "viewport": {"width": 1920, "height": 1080}, "locale": "de-DE", "timezone": "Europe/Berlin", "user_agent": None, }, { "id": "webkit-headed-de", "label": "WebKit (Headed) · de-DE", "engine": "webkit", "channel": None, "device": None, "headless": False, "viewport": {"width": 1920, "height": 1080}, "locale": "de-DE", "timezone": "Europe/Berlin", "user_agent": None, }, { "id": "iphone-mobile-safari-de", "label": "Mobile Safari (iPhone 15) · de-DE", "engine": "webkit", "channel": None, "device": "iPhone 15", "headless": False, "viewport": None, "locale": "de-DE", "timezone": "Europe/Berlin", "user_agent": None, }, ] # Optional profiles enabled via env var BROWSER_PROFILES_EXTRA EXTRA_PROFILES: dict[str, dict] = { "chrome-channel-desktop-de": { "id": "chrome-channel-desktop-de", "label": "Chrome Channel (Google Build) · de-DE", "engine": "blink", "channel": "chrome", "device": None, "headless": False, "viewport": {"width": 1920, "height": 1080}, "locale": "de-DE", "timezone": "Europe/Berlin", "user_agent": None, }, "edge-channel-desktop-de": { "id": "edge-channel-desktop-de", "label": "Edge Channel · de-DE", "engine": "blink", "channel": "msedge", "device": None, "headless": False, "viewport": {"width": 1920, "height": 1080}, "locale": "de-DE", "timezone": "Europe/Berlin", "user_agent": None, }, "brave-default-de": { "id": "brave-default-de", "label": "Brave Default-Shields · de-DE", "engine": "blink", "channel": None, "device": None, "headless": False, "viewport": {"width": 1920, "height": 1080}, "locale": "de-DE", "timezone": "Europe/Berlin", "user_agent": None, "executable_path": "/usr/bin/brave-browser", }, } def resolve_profiles(requested: list[str] | None) -> list[dict]: """Map requested ids to profile dicts. Falls back to all defaults when `requested` is None or empty.""" if not requested: return list(DEFAULT_PROFILES) by_id = {p["id"]: p for p in DEFAULT_PROFILES} by_id.update(EXTRA_PROFILES) out: list[dict] = [] for r in requested: prof = by_id.get(r) if prof: out.append(prof) return out or list(DEFAULT_PROFILES) def default_ids() -> list[str]: return [p["id"] for p in DEFAULT_PROFILES]