Coverage for human_requests/browsers/families/camoufox_family.py: 90%
42 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-13 21:41 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-13 21:41 +0000
1from __future__ import annotations
3from typing import Any, Dict, Optional
5from playwright.async_api import Browser
7from .base import BrowserFamily, DesiredConfig, Family
10class CamoufoxFamily(BrowserFamily):
11 """
12 Camoufox — a separate runtime. Launched as a context manager.
13 Stealth is NOT needed/allowed (antibot is built-in).
14 """
16 def __init__(self) -> None:
17 self._cm: Any | None = None # AsyncCamoufox runtime CM
18 self._browser: Browser | None = None
20 # кэш
21 self._launch_opts_used: Dict[str, Any] | None = None
23 @property
24 def name(self) -> Family:
25 return "camoufox"
27 @property
28 def browser(self) -> Optional[Browser]:
29 return self._browser
31 async def start(self, cfg: DesiredConfig) -> None:
32 assert cfg.family == "camoufox", "wrong family for CamoufoxFamily"
33 if cfg.stealth:
34 raise RuntimeError("stealth is incompatible with engine='camoufox'.")
36 need_relaunch = (
37 self._cm is None or self._browser is None or self._launch_opts_used != cfg.launch_opts
38 )
39 if need_relaunch:
40 await self.close()
42 try:
43 from camoufox.async_api import AsyncCamoufox as AsyncCamoufoxRT
44 except Exception:
45 raise RuntimeError(
46 "engine='camoufox', но пакет 'camoufox' не установлен. "
47 "Установите: pip install camoufox"
48 )
50 kwargs = dict(cfg.launch_opts)
51 kwargs["persistent_context"] = False # гарантируем неперсистентный режим
52 self._cm = AsyncCamoufoxRT(**kwargs)
53 browser_obj = await self._cm.__aenter__()
54 if not isinstance(browser_obj, Browser):
55 raise RuntimeError("Camoufox did not return a Browser in non-persistent mode.")
56 self._browser = browser_obj
58 self._launch_opts_used = dict(cfg.launch_opts)
60 async def close(self) -> None:
61 if self._browser is not None:
62 await self._browser.close()
63 self._browser = None
64 if self._cm is not None:
65 await self._cm.__aexit__(None, None, None)
66 self._cm = None
68 self._launch_opts_used = None