test: юнит-тесты и сквозные тесты с Chrome (#21)
- tests/unit (make test, без браузера, ~5 с): схемы и валидация, /v1/solve с подменённым движком (коды ошибок, слоты, сессии, UA и cookies профиля, маскирование секретов в логах), действия, оркестратор антибота, профиль и отпечаток - tests/browser (make test-browser, в Docker): example.com, UA один раз и замена cookies на httpbin, применение и стабильность отпечатка, прохождение Cloudflare - requirements-dev.txt, make sync-dev; Makefile вызывает pip через python -m pip (у перенесённого .venv битый shebang bin/pip) - fix: в CloudflareSolver вызов bounding_box() у неожиданной корутины — ветка без iframe всегда падала с AttributeError Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
58100fa45d
commit
0ae5b0c83f
13 files changed
+773
-85
No files matched your search
@@ -0,0 +1,90 @@
|
||||
"""Цикл 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())
|
||||
Reference in new issue
Block a user