From f21483489a7f4e20f4b2e629e9eeea850fe449b4 Mon Sep 17 00:00:00 2001 From: protokey Date: Sat, 24 Oct 2026 22:02:08 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20url=20=D1=82=D0=B5=D0=BA=D1=83=D1=89?= =?UTF-8?q?=D0=B5=D0=B9=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B?= =?UTF-8?q?=20=D0=B2=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=B5=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D1=87=D0=B8=20(#116)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SPA меняет адрес без навигации (страница созданной заявки и т.п.): ответ v1/v2 отдаёт page.url после действий, клиент 0.2.1 - Result.url. Документация и unit-тесты. Co-Authored-By: Claude Opus 5.5 --- client/pyproject.toml | 2 +- client/src/robotex/__init__.py | 2 +- client/src/robotex/models.py | 2 ++ docs/API.md | 1 + src/api/schemas.py | 2 ++ src/worker/windows.py | 3 +++ tests/unit/conftest.py | 2 ++ tests/unit/test_client.py | 1 + tests/unit/test_v1.py | 1 + 9 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/pyproject.toml b/client/pyproject.toml index 7c03b9c..7732dc0 100644 --- a/client/pyproject.toml +++ b/client/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "robotex" -version = "0.2.0" +version = "0.2.1" description = "Python-клиент Robotex API: браузер с обходом антиботов, задачи v2 и окна сессий" readme = "README.md" requires-python = ">=3.10" diff --git a/client/src/robotex/__init__.py b/client/src/robotex/__init__.py index cf9a079..1a9dca2 100644 --- a/client/src/robotex/__init__.py +++ b/client/src/robotex/__init__.py @@ -21,7 +21,7 @@ from robotex.errors import ( ) from robotex.models import ActionResult, Result, Task -__version__ = "0.2.0" +__version__ = "0.2.1" __all__ = [ "Action", "ActionResult", "AsyncRobotex", "AsyncSession", "AsyncTaskRef", "AuthError", "InvalidRequest", diff --git a/client/src/robotex/models.py b/client/src/robotex/models.py index 1588a32..e6951c1 100644 --- a/client/src/robotex/models.py +++ b/client/src/robotex/models.py @@ -24,6 +24,7 @@ class Result: code: str | None = None error: str | None = None user_agent: str | None = None + url: str | None = None cookies: list[dict[str, Any]] = field(default_factory=list) actions: list[ActionResult] = field(default_factory=list) html: str | None = None @@ -59,6 +60,7 @@ class Result: code=data.get("code"), error=data.get("error"), user_agent=data.get("user_agent"), + url=data.get("url"), cookies=data.get("cookies") or [], actions=[ActionResult.from_dict(a) for a in data.get("actions") or []], html=data.get("html"), diff --git a/docs/API.md b/docs/API.md index 7a9a985..1045de4 100644 --- a/docs/API.md +++ b/docs/API.md @@ -121,6 +121,7 @@ Cookie: session_id= # необязательно, см. «Сесси | `status` | `"ok"` — антибот пройден и все действия выполнены, иначе `"err"` | | `code` | при `"err"`: причина, см. таблицу ниже | | `error` | при `"err"`: текст ошибки; для упавшего действия — `: <текст>` | +| `url` | адрес страницы после выполнения действий (SPA меняет его без навигации, например на страницу созданной заявки) | | `user_agent` | родной UA браузера, с которым проходили антибот. Используйте его в своём HTTP-клиенте вместе с `cf_clearance` (см. [Usage.md](Usage.md)) | | `cookies` | все cookies контекста | | `actions` | по элементу на выполненное действие: `type`, `ok`, `result` (результат или текст ошибки), `duration_ms` | diff --git a/src/api/schemas.py b/src/api/schemas.py index 23e4ed9..3581096 100644 --- a/src/api/schemas.py +++ b/src/api/schemas.py @@ -161,6 +161,8 @@ class SolveResponse(BaseModel): code: Literal["no_slots", "request_failed", "challenge_failed", "action_failed"] | None = None error: str | None = None user_agent: str | None = None + # адрес страницы после выполнения действий + url: str | None = None cookies: list[dict[str, Any]] | None = None actions: list[ActionResult] = [] html: str | None = None diff --git a/src/worker/windows.py b/src/worker/windows.py index c41d203..aa1b053 100644 --- a/src/worker/windows.py +++ b/src/worker/windows.py @@ -138,6 +138,8 @@ def make_page_action(payload: TaskRequest): "html": await page.inner_html("html"), "screenshot": screenshot, "user_agent": await page.evaluate("() => navigator.userAgent"), + # адрес страницы после действий: SPA меняет его без навигации (номер заявки и т.п.) + "url": page.url, } return page_action @@ -149,6 +151,7 @@ def build_result(payload: TaskRequest, result: dict) -> dict: response = { "status": "ok", "user_agent": result["user_agent"], + "url": result.get("url"), "cookies": result["cookies"], "actions": result["actions"], } diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 3377a0f..825d90c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -21,6 +21,8 @@ class FakeContext: class FakePage: + url = "https://example.com/after-actions" + def __init__(self, context): self.context = context diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 43d628e..694e56c 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -69,6 +69,7 @@ def test_run(rx, monkeypatch): result = rx.run("https://example.com", [Action.inner_text("h1")], html=True) assert result.ok and result.results == ["Example"] assert result.html == "page" and result.user_agent == "UA/engine" + assert result.url == "https://example.com/after-actions" assert result.cookie("cf_clearance") == "secret-cookie" assert result.session_id diff --git a/tests/unit/test_v1.py b/tests/unit/test_v1.py index 7a0d6f3..2e78dd1 100644 --- a/tests/unit/test_v1.py +++ b/tests/unit/test_v1.py @@ -25,6 +25,7 @@ def test_ok(env, monkeypatch): assert data["status"] == "ok" assert "code" not in data and "error" not in data assert data["user_agent"] == "UA/engine" + assert data["url"] == "https://example.com/after-actions" assert data["actions"] == [{"type": "wait_for", "ok": True, "duration_ms": 5}] assert data["html"] == "page" assert data["screenshot"] == "cG5n" # base64(b"png")