"""Цикл detect → solve → verify в antibot.orchestrator на поддельном провайдере.""" import pytest from patchright.async_api import Error as PlaywrightError from antibot import orchestrator from antibot.base import ChallengeInfo, Stage from antibot.exceptions import ProviderNotDetected pytestmark = pytest.mark.anyio class FakePage: def __init__(self): self.waits = [] async def wait_for_load_state(self, state): pass async def wait_for_timeout(self, ms): self.waits.append(ms) class FakeProvider: provider = "fake" def __init__(self, detects, solve_error=None): self.detects = list(detects) # что вернуть/бросить на каждый detect self.solve_error = solve_error self.solves = 0 async def detect(self, page): item = self.detects.pop(0) if len(self.detects) > 1 else self.detects[0] if isinstance(item, Exception): raise item return item async def solve(self, page, info): self.solves += 1 if self.solve_error: raise self.solve_error def info(success, stage=Stage.CHECKBOX): return ChallengeInfo(stage=stage, provider="fake", success=success, meta={"reason": "done" if success else None}) @pytest.fixture def use_provider(monkeypatch): def install(provider): async def select_one(page, name, **kwargs): return provider monkeypatch.setattr(orchestrator, "select_one", select_one) return provider return install async def test_not_requested(): outcome = await orchestrator.pass_challenges(None, FakePage()) assert outcome.cleared and outcome.reason == "antibot not requested" async def test_solved_after_one_step(use_provider): provider = use_provider(FakeProvider([info(False), info(True)])) outcome = await orchestrator.pass_challenges("fake", FakePage()) assert outcome.cleared and outcome.reason == "done" assert provider.solves == 1 async def test_detect_survives_navigation(use_provider): """Челлендж уходит на целевую страницу посреди detect — повторяем detect, а не падаем.""" destroyed = PlaywrightError("Page.query_selector: Execution context was destroyed, most likely because of a navigation.") provider = use_provider(FakeProvider([destroyed, info(True)])) outcome = await orchestrator.pass_challenges("fake", FakePage()) assert outcome.cleared assert provider.solves == 0 async def test_failing_solve_pauses_and_times_out(use_provider): page = FakePage() use_provider(FakeProvider([info(False)], solve_error=RuntimeError("click failed"))) outcome = await orchestrator.pass_challenges("fake", page, timeout_ms=50) assert not outcome.cleared assert outcome.reason == "timeout" assert outcome.last_stage == Stage.CHECKBOX assert page.waits and set(page.waits) == {orchestrator.SOLVE_RETRY_DELAY_MS} async def test_unknown_provider(): with pytest.raises(ProviderNotDetected, match="unknown captcha_type 'datadome'"): await orchestrator.pass_challenges("datadome", FakePage())