fix: load the page the challenge was hiding, and stop trusting a stray box

Nothing waited for the destination once the interstitial let go: page_html
stayed unset and the networkidle wait only ran on the branch that never saw
a challenge, so a challenge clearing on its own returned whatever had loaded
by then. Wait for it on both branches.

An exhausted budget produced timeout=0, which Playwright reads as no timeout
at all, turning every remaining wait unbounded exactly when it should fail
fast. Floor it instead.

Scroll the widget into view before measuring it, since bounding_box reports
viewport coordinates and an off-screen widget was clicked at a point that hit
nothing, and reject containers taller than a checkbox row so a full-page
wrapper cannot pass for one - both reported success while clicking blank
space.

Drop max_attempts, which nothing reads now that the solver is gone, and
refresh AGENTS.md, which still described camoufox and a solver in the
dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ThePhaseless
2026-08-18 03:41:58 +02:00
co-authored by Claude Opus 5
parent 426aae4310
commit d0b013029c
4 changed files with 33 additions and 19 deletions
+13 -3
View File
@@ -13,9 +13,9 @@ from playwright_captcha.solvers.click.cloudflare.utils.detection import (
from starlette.testclient import TestClient
from main import app
from src.endpoints import read_item
from src.endpoints import _remaining_ms, read_item
from src.models import LinkRequest
from src.utils import BrowserDepClass
from src.utils import BrowserDepClass, TimeoutTimer
client = TestClient(app)
@@ -65,7 +65,8 @@ def test_bypass(website: str):
def test_json_api():
"""JSON APIs must return 200, not crash on the UA evaluate.
"""
JSON APIs must return 200, not crash on the UA evaluate.
Firefox renders application/json in a built-in viewer whose CSP blocks
Playwright's eval-based evaluate() (issue #394). The browser must be
@@ -171,6 +172,7 @@ def fake_dep(
def locator(selector: str) -> MagicMock:
handle = MagicMock()
handle.count = AsyncMock(side_effect=lambda: count_for(selector))
handle.first.scroll_into_view_if_needed = AsyncMock(return_value=None)
handle.first.bounding_box = AsyncMock(return_value=widget_box)
handle.first.input_value = AsyncMock(return_value="")
return handle
@@ -216,6 +218,14 @@ async def test_domcontentloaded_timeout_returns_408():
assert exc.value.status_code == HTTPStatus.REQUEST_TIMEOUT
def test_exhausted_budget_never_disables_playwright_timeouts():
"""Playwright reads timeout=0 as no timeout at all, so the floor must hold."""
spent = TimeoutTimer(duration=0)
assert spent.remaining() == 0
assert _remaining_ms(spent) > 0
@pytest.mark.asyncio
async def test_missing_user_agent_header_is_not_a_500():
"""A request without a user-agent header degrades to empty, never a 500 (#394)."""