mirror of
https://github.com/ThePhaseless/Byparr.git
synced 2026-09-24 14:20:08 +01:00
refactor: drop the solver and prove the challenge page is gone
Nothing calls ClickSolver any more now that the checkbox is clicked through the mouse, so take it out of the browser dependency instead of leaving it constructed but unused. Assert on the returned body as well: a 200 alone passed even when the interstitial itself was handed back, which is the defect this branch fixes. Judge on _cf_chl_opt, since Cloudflare keeps serving cdn-cgi/challenge-platform as a beacon on pages that are already cleared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
61db8d82ee
commit
3dcf529609
+1
-16
@@ -6,16 +6,11 @@ from typing import Annotated, NamedTuple, cast
|
||||
from fastapi import Header
|
||||
from invisible_playwright.async_api import InvisiblePlaywright
|
||||
from playwright.async_api import Browser, BrowserContext, Page
|
||||
from playwright_captcha import (
|
||||
ClickSolver,
|
||||
FrameworkType,
|
||||
)
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.consts import (
|
||||
BROWSER_LOCALE,
|
||||
LOG_LEVEL,
|
||||
MAX_ATTEMPTS,
|
||||
PROXY_PASSWORD,
|
||||
PROXY_SERVER,
|
||||
PROXY_USERNAME,
|
||||
@@ -46,7 +41,6 @@ class TimeoutTimer(BaseModel):
|
||||
|
||||
class BrowserDepClass(NamedTuple):
|
||||
page: Page
|
||||
solver: ClickSolver
|
||||
context: BrowserContext
|
||||
|
||||
|
||||
@@ -106,13 +100,4 @@ async def get_browser(
|
||||
browser = cast("Browser", browser_raw)
|
||||
context = await browser.new_context()
|
||||
page = await context.new_page()
|
||||
solver = ClickSolver(
|
||||
framework=FrameworkType.PLAYWRIGHT,
|
||||
page=page,
|
||||
max_attempts=MAX_ATTEMPTS,
|
||||
attempt_delay=1,
|
||||
)
|
||||
try:
|
||||
yield BrowserDepClass(page, solver, context)
|
||||
finally:
|
||||
await solver.cleanup()
|
||||
yield BrowserDepClass(page, context)
|
||||
|
||||
+5
-11
@@ -10,10 +10,6 @@ from playwright.async_api import TimeoutError as PlaywrightTimeoutError
|
||||
from playwright_captcha.solvers.click.cloudflare.utils.detection import (
|
||||
CF_INTERSTITIAL_INDICATORS_SELECTORS,
|
||||
)
|
||||
from playwright_captcha.utils.exceptions import (
|
||||
CaptchaDetectionError,
|
||||
CaptchaSolvingError,
|
||||
)
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from main import app
|
||||
@@ -63,6 +59,9 @@ def test_bypass(website: str):
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
solution = response.json()["solution"]
|
||||
assert "_cf_chl_opt" not in solution["response"]
|
||||
assert "__cf_chl" not in solution["url"]
|
||||
|
||||
|
||||
def test_json_api():
|
||||
@@ -148,7 +147,7 @@ def fake_dep(
|
||||
challenged: bool = False,
|
||||
marker_counts: list[int] | None = None,
|
||||
) -> BrowserDepClass:
|
||||
"""Build a browser dependency triple backed by mocks."""
|
||||
"""Build a browser dependency pair backed by mocks."""
|
||||
page = AsyncMock()
|
||||
page.url = "https://example.test/login"
|
||||
page.goto.return_value = MagicMock(
|
||||
@@ -186,7 +185,7 @@ def fake_dep(
|
||||
|
||||
context = AsyncMock()
|
||||
context.cookies.return_value = []
|
||||
return BrowserDepClass(page=page, solver=AsyncMock(), context=context)
|
||||
return BrowserDepClass(page=page, context=context)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -201,7 +200,6 @@ async def test_networkidle_timeout_after_domcontentloaded_returns_content():
|
||||
assert response.status == "ok"
|
||||
assert response.solution.status == HTTPStatus.OK
|
||||
assert response.solution.response == "<html><title>Login</title></html>"
|
||||
dep.solver.solve_captcha.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -239,9 +237,6 @@ async def test_user_agent_survives_csp_blocked_evaluate():
|
||||
async def test_challenge_that_clears_is_reported_as_success():
|
||||
"""A challenge is over when its markup goes, not when the solver says so."""
|
||||
dep = fake_dep(challenged=True, marker_counts=[1, 0])
|
||||
dep.solver.solve_captcha.side_effect = CaptchaSolvingError(
|
||||
"challenge still present"
|
||||
)
|
||||
|
||||
response = await read_item(
|
||||
LinkRequest(url="https://example.test/login", max_timeout=5), dep
|
||||
@@ -255,7 +250,6 @@ async def test_challenge_that_clears_is_reported_as_success():
|
||||
async def test_challenge_that_never_clears_returns_408():
|
||||
"""A challenge still up when the budget runs out is a timeout, not a 500."""
|
||||
dep = fake_dep(challenged=True, marker_counts=[1])
|
||||
dep.solver.solve_captcha.side_effect = CaptchaDetectionError("iframes not found")
|
||||
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await read_item(
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ def fake_dep(*, html: str = ARTICLE_HTML) -> BrowserDepClass:
|
||||
raise PlaywrightTimeoutError(message)
|
||||
|
||||
page.wait_for_load_state.side_effect = wait_for_load_state
|
||||
return BrowserDepClass(page=page, solver=AsyncMock(), context=AsyncMock())
|
||||
return BrowserDepClass(page=page, context=AsyncMock())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user