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

1from __future__ import annotations 

2 

3from typing import Any, Dict, Optional 

4 

5from playwright.async_api import Browser 

6 

7from .base import BrowserFamily, DesiredConfig, Family 

8 

9 

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 """ 

15 

16 def __init__(self) -> None: 

17 self._cm: Any | None = None # AsyncCamoufox runtime CM 

18 self._browser: Browser | None = None 

19 

20 # кэш 

21 self._launch_opts_used: Dict[str, Any] | None = None 

22 

23 @property 

24 def name(self) -> Family: 

25 return "camoufox" 

26 

27 @property 

28 def browser(self) -> Optional[Browser]: 

29 return self._browser 

30 

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'.") 

35 

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() 

41 

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 ) 

49 

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 

57 

58 self._launch_opts_used = dict(cfg.launch_opts) 

59 

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 

67 

68 self._launch_opts_used = None