Coverage for human_requests/browsers/families/base.py: 86%
36 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 abc import ABC, abstractmethod
4from pathlib import Path
5from typing import Any, Dict, Literal, Optional
7from playwright.async_api import Browser, BrowserContext, StorageState
9Family = Literal["playwright", "patchright", "camoufox"]
10PlaywrightEngine = Literal["chromium", "firefox", "webkit"]
13class DesiredConfig:
14 """Unified "desired" configuration for all families."""
16 __slots__ = ("family", "engine", "headless", "stealth", "launch_opts")
18 def __init__(
19 self,
20 *,
21 family: Family,
22 engine: PlaywrightEngine | None,
23 stealth: bool,
24 launch_opts: Dict[str, Any],
25 ) -> None:
26 self.family = family
27 self.engine = engine
28 self.stealth = stealth
29 self.launch_opts = dict(launch_opts) # копия
32class BrowserFamily(ABC):
33 """Family interface. Implements idempotent start and soft restarts internally."""
35 @property
36 @abstractmethod
37 def name(self) -> Family: # noqa: D401
38 """Family name."""
39 raise NotImplementedError
41 @abstractmethod
42 async def start(self, cfg: DesiredConfig) -> None:
43 """Idempotent launch/restart according to cfg."""
44 raise NotImplementedError
46 @abstractmethod
47 async def close(self) -> None:
48 """Close all family resources (browser + runtime)."""
49 raise NotImplementedError
51 @property
52 @abstractmethod
53 def browser(self) -> Optional[Browser]:
54 """Current Browser or None if not started."""
55 raise NotImplementedError
57 async def new_context(
58 self,
59 *,
60 storage_state: StorageState | str | Path | None = None,
61 ) -> BrowserContext:
62 await self._ensure()
63 assert self.browser is not None
64 return await self.browser.new_context(storage_state=storage_state)
66 async def _ensure(self) -> None:
67 if self.browser is None:
68 # Семейство само знает «последнюю cfg». Упрощённо: бросаем, если не стартовало.
69 raise RuntimeError(f"{self.name}: not started yet")