Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions hyperbrowser/client/managers/async_manager/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
UpdateSessionProfileParams,
UpdateSessionProxyParams,
UpdateSessionScreenParams,
UpdateSessionSolveCaptchasParams,
UpdateSessionSolveCaptchasResponse,
SessionGetParams,
)

Expand Down Expand Up @@ -213,6 +215,36 @@ async def update_screen_size(
)
return BasicResponse(**response.data)

async def start_captcha_solving(
self,
id: str,
params: Optional[UpdateSessionSolveCaptchasParams] = None,
) -> UpdateSessionSolveCaptchasResponse:
params_obj = params or UpdateSessionSolveCaptchasParams()
response = await self._client.transport.put(
self._client._build_url(f"/session/{id}/update"),
data={
"type": "solveCaptchas",
"params": {
"enabled": True,
**params_obj.model_dump(exclude_none=True, by_alias=True),
},
},
)
return UpdateSessionSolveCaptchasResponse(**response.data)

async def stop_captcha_solving(self, id: str) -> UpdateSessionSolveCaptchasResponse:
response = await self._client.transport.put(
self._client._build_url(f"/session/{id}/update"),
data={
"type": "solveCaptchas",
"params": {
"enabled": False,
},
},
)
return UpdateSessionSolveCaptchasResponse(**response.data)

def _warn_update_profile_params_boolean_deprecated(self) -> None:
if SessionManager._has_warned_update_profile_params_boolean_deprecated:
return
Expand Down
32 changes: 32 additions & 0 deletions hyperbrowser/client/managers/sync_manager/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
UpdateSessionProfileParams,
UpdateSessionProxyParams,
UpdateSessionScreenParams,
UpdateSessionSolveCaptchasParams,
UpdateSessionSolveCaptchasResponse,
SessionGetParams,
)

Expand Down Expand Up @@ -208,6 +210,36 @@ def update_screen_size(
)
return BasicResponse(**response.data)

def start_captcha_solving(
self,
id: str,
params: Optional[UpdateSessionSolveCaptchasParams] = None,
) -> UpdateSessionSolveCaptchasResponse:
params_obj = params or UpdateSessionSolveCaptchasParams()
response = self._client.transport.put(
self._client._build_url(f"/session/{id}/update"),
data={
"type": "solveCaptchas",
"params": {
"enabled": True,
**params_obj.model_dump(exclude_none=True, by_alias=True),
},
},
)
return UpdateSessionSolveCaptchasResponse(**response.data)

def stop_captcha_solving(self, id: str) -> UpdateSessionSolveCaptchasResponse:
response = self._client.transport.put(
self._client._build_url(f"/session/{id}/update"),
data={
"type": "solveCaptchas",
"params": {
"enabled": False,
},
},
)
return UpdateSessionSolveCaptchasResponse(**response.data)

def _warn_update_profile_params_boolean_deprecated(self) -> None:
if SessionManager._has_warned_update_profile_params_boolean_deprecated:
return
Expand Down
6 changes: 6 additions & 0 deletions hyperbrowser/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,13 @@
SessionLaunchState,
UploadFileResponse,
ImageCaptchaParam,
CaptchaSolverType,
UpdateSessionProfileParams,
UpdateSessionProxyLocationParams,
UpdateSessionProxyParams,
UpdateSessionScreenParams,
UpdateSessionSolveCaptchasParams,
UpdateSessionSolveCaptchasResponse,
)
from .sandbox import (
SandboxStatus,
Expand Down Expand Up @@ -524,10 +527,13 @@
"SessionLaunchState",
"UploadFileResponse",
"ImageCaptchaParam",
"CaptchaSolverType",
"UpdateSessionProfileParams",
"UpdateSessionProxyLocationParams",
"UpdateSessionProxyParams",
"UpdateSessionScreenParams",
"UpdateSessionSolveCaptchasParams",
"UpdateSessionSolveCaptchasResponse",
# sandbox
"SandboxStatus",
"SandboxRegion",
Expand Down
36 changes: 36 additions & 0 deletions hyperbrowser/models/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)

SessionStatus = Literal["active", "closed", "error"]
CaptchaSolverType = Literal["visual"]


class BasicResponse(BaseModel):
Expand Down Expand Up @@ -134,6 +135,9 @@ class SessionLaunchState(BaseModel):
)
enable_log_capture: Optional[bool] = Field(default=None, alias="enableLogCapture")
accept_cookies: Optional[bool] = Field(default=None, alias="acceptCookies")
solver_type: Optional[CaptchaSolverType] = Field(
default=None, alias="solverType"
)
profile: Optional[SessionProfile] = Field(default=None, alias="profile")
static_ip_id: Optional[str] = Field(default=None, alias="staticIpId")
save_downloads: Optional[bool] = Field(default=None, alias="saveDownloads")
Expand Down Expand Up @@ -337,6 +341,9 @@ class CreateSessionParams(BaseModel):
locales: List[ISO639_1] = Field(default=["en"])
screen: Optional[ScreenConfig] = Field(default=None)
solve_captchas: bool = Field(default=False, serialization_alias="solveCaptchas")
solver_type: Optional[CaptchaSolverType] = Field(
default=None, serialization_alias="solverType"
)
adblock: bool = Field(default=False, serialization_alias="adblock")
trackers: bool = Field(default=False, serialization_alias="trackers")
annoyances: bool = Field(default=False, serialization_alias="annoyances")
Expand Down Expand Up @@ -480,6 +487,35 @@ class UploadFileResponse(BaseModel):
original_name: Optional[str] = Field(default=None, alias="originalName")


class UpdateSessionSolveCaptchasParams(BaseModel):
"""
Parameters for starting automatic captcha solving in a running session.
"""

model_config = ConfigDict(
populate_by_alias=True,
)

solver_type: Optional[CaptchaSolverType] = Field(
default=None,
serialization_alias="solverType",
)


class UpdateSessionSolveCaptchasResponse(BasicResponse):
"""
Response from updating automatic captcha solving in a running session.
"""

model_config = ConfigDict(
populate_by_alias=True,
)

solve_captchas: Optional[bool] = Field(default=None, alias="solveCaptchas")
session_id: Optional[str] = Field(default=None, alias="sessionId")
telemetry_ready: Optional[bool] = Field(default=None, alias="telemetryReady")


class SessionEventLog(BaseModel):
model_config = ConfigDict(
populate_by_alias=True,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "hyperbrowser"
version = "0.90.9"
version = "0.91.0"
description = "Python SDK for hyperbrowser"
authors = ["Nikhil Shahi <nshahi1998@gmail.com>"]
license = "MIT"
Expand Down
129 changes: 129 additions & 0 deletions tests/integration/test_client_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from hyperbrowser import AsyncHyperbrowser, Hyperbrowser
from hyperbrowser.models.scrape import ScrapeOptions, StartScrapeJobParams
from hyperbrowser.models.session import UpdateSessionSolveCaptchasParams


def _read_json_body(handler: BaseHTTPRequestHandler):
Expand Down Expand Up @@ -62,6 +63,35 @@ def do_GET(self):

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

def do_PUT(self):
body = _read_json_body(self)
requests.append(
{
"method": self.command,
"path": self.path,
"api_key": self.headers.get("x-api-key"),
"content_type": self.headers.get("content-type"),
"body": body,
}
)

if (
self.path
== "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update"
and body.get("type") == "solveCaptchas"
):
_send_json(
self,
200,
{
"success": True,
"solveCaptchas": bool(body.get("params", {}).get("enabled")),
},
)
return

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

def log_message(self, format, *args):
return

Expand Down Expand Up @@ -149,3 +179,102 @@ async def test_async_client_uses_configured_api_endpoint_and_parses_responses():
"body": None,
},
]


def test_sync_session_captcha_solving_update_starts_and_stops_automatic_solving():
server, base_url, requests = _start_server()
client = Hyperbrowser(api_key="test-api-key", base_url=base_url)
try:
started = client.sessions.start_captcha_solving(
"52dd29fb-75a2-43f9-9831-8ff377fedb0a",
UpdateSessionSolveCaptchasParams(solver_type="visual"),
)
stopped = client.sessions.stop_captcha_solving(
"52dd29fb-75a2-43f9-9831-8ff377fedb0a"
)
finally:
client.close()
server.shutdown()
server.server_close()

assert started.success is True
assert started.solve_captchas is True
assert stopped.success is True
assert stopped.solve_captchas is False
assert requests == [
{
"method": "PUT",
"path": "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update",
"api_key": "test-api-key",
"content_type": "application/json",
"body": {
"type": "solveCaptchas",
"params": {
"enabled": True,
"solverType": "visual",
},
},
},
{
"method": "PUT",
"path": "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update",
"api_key": "test-api-key",
"content_type": "application/json",
"body": {
"type": "solveCaptchas",
"params": {
"enabled": False,
},
},
},
]


@pytest.mark.anyio
async def test_async_session_captcha_solving_update_starts_and_stops_automatic_solving():
server, base_url, requests = _start_server()
client = AsyncHyperbrowser(api_key="test-api-key", base_url=base_url)
try:
started = await client.sessions.start_captcha_solving(
"52dd29fb-75a2-43f9-9831-8ff377fedb0a",
UpdateSessionSolveCaptchasParams(solver_type="visual"),
)
stopped = await client.sessions.stop_captcha_solving(
"52dd29fb-75a2-43f9-9831-8ff377fedb0a"
)
finally:
await client.close()
server.shutdown()
server.server_close()

assert started.success is True
assert started.solve_captchas is True
assert stopped.success is True
assert stopped.solve_captchas is False
assert requests == [
{
"method": "PUT",
"path": "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update",
"api_key": "test-api-key",
"content_type": "application/json",
"body": {
"type": "solveCaptchas",
"params": {
"enabled": True,
"solverType": "visual",
},
},
},
{
"method": "PUT",
"path": "/api/session/52dd29fb-75a2-43f9-9831-8ff377fedb0a/update",
"api_key": "test-api-key",
"content_type": "application/json",
"body": {
"type": "solveCaptchas",
"params": {
"enabled": False,
},
},
},
]