Skip to content

Commit c12a15e

Browse files
committed
Captcha manual solve
1 parent f900ac6 commit c12a15e

5 files changed

Lines changed: 235 additions & 0 deletions

File tree

hyperbrowser/client/managers/async_manager/session.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
UpdateSessionProfileParams,
1818
UpdateSessionProxyParams,
1919
UpdateSessionScreenParams,
20+
UpdateSessionSolveCaptchasParams,
21+
UpdateSessionSolveCaptchasResponse,
2022
SessionGetParams,
2123
)
2224

@@ -213,6 +215,36 @@ async def update_screen_size(
213215
)
214216
return BasicResponse(**response.data)
215217

218+
async def start_captcha_solving(
219+
self,
220+
id: str,
221+
params: Optional[UpdateSessionSolveCaptchasParams] = None,
222+
) -> UpdateSessionSolveCaptchasResponse:
223+
params_obj = params or UpdateSessionSolveCaptchasParams()
224+
response = await self._client.transport.put(
225+
self._client._build_url(f"/session/{id}/update"),
226+
data={
227+
"type": "solveCaptchas",
228+
"params": {
229+
"enabled": True,
230+
**params_obj.model_dump(exclude_none=True, by_alias=True),
231+
},
232+
},
233+
)
234+
return UpdateSessionSolveCaptchasResponse(**response.data)
235+
236+
async def stop_captcha_solving(self, id: str) -> UpdateSessionSolveCaptchasResponse:
237+
response = await self._client.transport.put(
238+
self._client._build_url(f"/session/{id}/update"),
239+
data={
240+
"type": "solveCaptchas",
241+
"params": {
242+
"enabled": False,
243+
},
244+
},
245+
)
246+
return UpdateSessionSolveCaptchasResponse(**response.data)
247+
216248
def _warn_update_profile_params_boolean_deprecated(self) -> None:
217249
if SessionManager._has_warned_update_profile_params_boolean_deprecated:
218250
return

hyperbrowser/client/managers/sync_manager/session.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
UpdateSessionProfileParams,
1717
UpdateSessionProxyParams,
1818
UpdateSessionScreenParams,
19+
UpdateSessionSolveCaptchasParams,
20+
UpdateSessionSolveCaptchasResponse,
1921
SessionGetParams,
2022
)
2123

@@ -208,6 +210,36 @@ def update_screen_size(
208210
)
209211
return BasicResponse(**response.data)
210212

213+
def start_captcha_solving(
214+
self,
215+
id: str,
216+
params: Optional[UpdateSessionSolveCaptchasParams] = None,
217+
) -> UpdateSessionSolveCaptchasResponse:
218+
params_obj = params or UpdateSessionSolveCaptchasParams()
219+
response = self._client.transport.put(
220+
self._client._build_url(f"/session/{id}/update"),
221+
data={
222+
"type": "solveCaptchas",
223+
"params": {
224+
"enabled": True,
225+
**params_obj.model_dump(exclude_none=True, by_alias=True),
226+
},
227+
},
228+
)
229+
return UpdateSessionSolveCaptchasResponse(**response.data)
230+
231+
def stop_captcha_solving(self, id: str) -> UpdateSessionSolveCaptchasResponse:
232+
response = self._client.transport.put(
233+
self._client._build_url(f"/session/{id}/update"),
234+
data={
235+
"type": "solveCaptchas",
236+
"params": {
237+
"enabled": False,
238+
},
239+
},
240+
)
241+
return UpdateSessionSolveCaptchasResponse(**response.data)
242+
211243
def _warn_update_profile_params_boolean_deprecated(self) -> None:
212244
if SessionManager._has_warned_update_profile_params_boolean_deprecated:
213245
return

hyperbrowser/models/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,13 @@
262262
SessionLaunchState,
263263
UploadFileResponse,
264264
ImageCaptchaParam,
265+
CaptchaSolverType,
265266
UpdateSessionProfileParams,
266267
UpdateSessionProxyLocationParams,
267268
UpdateSessionProxyParams,
268269
UpdateSessionScreenParams,
270+
UpdateSessionSolveCaptchasParams,
271+
UpdateSessionSolveCaptchasResponse,
269272
)
270273
from .sandbox import (
271274
SandboxStatus,
@@ -524,10 +527,13 @@
524527
"SessionLaunchState",
525528
"UploadFileResponse",
526529
"ImageCaptchaParam",
530+
"CaptchaSolverType",
527531
"UpdateSessionProfileParams",
528532
"UpdateSessionProxyLocationParams",
529533
"UpdateSessionProxyParams",
530534
"UpdateSessionScreenParams",
535+
"UpdateSessionSolveCaptchasParams",
536+
"UpdateSessionSolveCaptchasResponse",
531537
# sandbox
532538
"SandboxStatus",
533539
"SandboxRegion",

hyperbrowser/models/session.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
)
1717

1818
SessionStatus = Literal["active", "closed", "error"]
19+
CaptchaSolverType = Literal["visual"]
1920

2021

2122
class BasicResponse(BaseModel):
@@ -134,6 +135,9 @@ class SessionLaunchState(BaseModel):
134135
)
135136
enable_log_capture: Optional[bool] = Field(default=None, alias="enableLogCapture")
136137
accept_cookies: Optional[bool] = Field(default=None, alias="acceptCookies")
138+
solver_type: Optional[CaptchaSolverType] = Field(
139+
default=None, alias="solverType"
140+
)
137141
profile: Optional[SessionProfile] = Field(default=None, alias="profile")
138142
static_ip_id: Optional[str] = Field(default=None, alias="staticIpId")
139143
save_downloads: Optional[bool] = Field(default=None, alias="saveDownloads")
@@ -337,6 +341,9 @@ class CreateSessionParams(BaseModel):
337341
locales: List[ISO639_1] = Field(default=["en"])
338342
screen: Optional[ScreenConfig] = Field(default=None)
339343
solve_captchas: bool = Field(default=False, serialization_alias="solveCaptchas")
344+
solver_type: Optional[CaptchaSolverType] = Field(
345+
default=None, serialization_alias="solverType"
346+
)
340347
adblock: bool = Field(default=False, serialization_alias="adblock")
341348
trackers: bool = Field(default=False, serialization_alias="trackers")
342349
annoyances: bool = Field(default=False, serialization_alias="annoyances")
@@ -480,6 +487,35 @@ class UploadFileResponse(BaseModel):
480487
original_name: Optional[str] = Field(default=None, alias="originalName")
481488

482489

490+
class UpdateSessionSolveCaptchasParams(BaseModel):
491+
"""
492+
Parameters for starting automatic captcha solving in a running session.
493+
"""
494+
495+
model_config = ConfigDict(
496+
populate_by_alias=True,
497+
)
498+
499+
solver_type: Optional[CaptchaSolverType] = Field(
500+
default=None,
501+
serialization_alias="solverType",
502+
)
503+
504+
505+
class UpdateSessionSolveCaptchasResponse(BasicResponse):
506+
"""
507+
Response from updating automatic captcha solving in a running session.
508+
"""
509+
510+
model_config = ConfigDict(
511+
populate_by_alias=True,
512+
)
513+
514+
solve_captchas: Optional[bool] = Field(default=None, alias="solveCaptchas")
515+
session_id: Optional[str] = Field(default=None, alias="sessionId")
516+
telemetry_ready: Optional[bool] = Field(default=None, alias="telemetryReady")
517+
518+
483519
class SessionEventLog(BaseModel):
484520
model_config = ConfigDict(
485521
populate_by_alias=True,

tests/integration/test_client_http.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from hyperbrowser import AsyncHyperbrowser, Hyperbrowser
88
from hyperbrowser.models.scrape import ScrapeOptions, StartScrapeJobParams
9+
from hyperbrowser.models.session import UpdateSessionSolveCaptchasParams
910

1011

1112
def _read_json_body(handler: BaseHTTPRequestHandler):
@@ -62,6 +63,35 @@ def do_GET(self):
6263

6364
_send_json(self, 404, {"message": f"unexpected route {self.path}"})
6465

66+
def do_PUT(self):
67+
body = _read_json_body(self)
68+
requests.append(
69+
{
70+
"method": self.command,
71+
"path": self.path,
72+
"api_key": self.headers.get("x-api-key"),
73+
"content_type": self.headers.get("content-type"),
74+
"body": body,
75+
}
76+
)
77+
78+
if (
79+
self.path
80+
== "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update"
81+
and body.get("type") == "solveCaptchas"
82+
):
83+
_send_json(
84+
self,
85+
200,
86+
{
87+
"success": True,
88+
"solveCaptchas": bool(body.get("params", {}).get("enabled")),
89+
},
90+
)
91+
return
92+
93+
_send_json(self, 404, {"message": f"unexpected route {self.path}"})
94+
6595
def log_message(self, format, *args):
6696
return
6797

@@ -149,3 +179,102 @@ async def test_async_client_uses_configured_api_endpoint_and_parses_responses():
149179
"body": None,
150180
},
151181
]
182+
183+
184+
def test_sync_session_captcha_solving_update_starts_and_stops_automatic_solving():
185+
server, base_url, requests = _start_server()
186+
client = Hyperbrowser(api_key="test-api-key", base_url=base_url)
187+
try:
188+
started = client.sessions.start_captcha_solving(
189+
"52dd29fb-75a2-43f9-9831-8ff377fedb0a",
190+
UpdateSessionSolveCaptchasParams(solver_type="visual"),
191+
)
192+
stopped = client.sessions.stop_captcha_solving(
193+
"52dd29fb-75a2-43f9-9831-8ff377fedb0a"
194+
)
195+
finally:
196+
client.close()
197+
server.shutdown()
198+
server.server_close()
199+
200+
assert started.success is True
201+
assert started.solve_captchas is True
202+
assert stopped.success is True
203+
assert stopped.solve_captchas is False
204+
assert requests == [
205+
{
206+
"method": "PUT",
207+
"path": "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update",
208+
"api_key": "test-api-key",
209+
"content_type": "application/json",
210+
"body": {
211+
"type": "solveCaptchas",
212+
"params": {
213+
"enabled": True,
214+
"solverType": "visual",
215+
},
216+
},
217+
},
218+
{
219+
"method": "PUT",
220+
"path": "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update",
221+
"api_key": "test-api-key",
222+
"content_type": "application/json",
223+
"body": {
224+
"type": "solveCaptchas",
225+
"params": {
226+
"enabled": False,
227+
},
228+
},
229+
},
230+
]
231+
232+
233+
@pytest.mark.anyio
234+
async def test_async_session_captcha_solving_update_starts_and_stops_automatic_solving():
235+
server, base_url, requests = _start_server()
236+
client = AsyncHyperbrowser(api_key="test-api-key", base_url=base_url)
237+
try:
238+
started = await client.sessions.start_captcha_solving(
239+
"52dd29fb-75a2-43f9-9831-8ff377fedb0a",
240+
UpdateSessionSolveCaptchasParams(solver_type="visual"),
241+
)
242+
stopped = await client.sessions.stop_captcha_solving(
243+
"52dd29fb-75a2-43f9-9831-8ff377fedb0a"
244+
)
245+
finally:
246+
await client.close()
247+
server.shutdown()
248+
server.server_close()
249+
250+
assert started.success is True
251+
assert started.solve_captchas is True
252+
assert stopped.success is True
253+
assert stopped.solve_captchas is False
254+
assert requests == [
255+
{
256+
"method": "PUT",
257+
"path": "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update",
258+
"api_key": "test-api-key",
259+
"content_type": "application/json",
260+
"body": {
261+
"type": "solveCaptchas",
262+
"params": {
263+
"enabled": True,
264+
"solverType": "visual",
265+
},
266+
},
267+
},
268+
{
269+
"method": "PUT",
270+
"path": "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update",
271+
"api_key": "test-api-key",
272+
"content_type": "application/json",
273+
"body": {
274+
"type": "solveCaptchas",
275+
"params": {
276+
"enabled": False,
277+
},
278+
},
279+
},
280+
]

0 commit comments

Comments
 (0)