Skip to content

Commit 31f4d4a

Browse files
Harden browser-use manager param serialization
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 41b3ac1 commit 31f4d4a

3 files changed

Lines changed: 233 additions & 2 deletions

File tree

hyperbrowser/client/managers/async_manager/agents/browser_use.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
StartBrowserUseTaskParams,
1717
StartBrowserUseTaskResponse,
1818
)
19+
from .....exceptions import HyperbrowserError
1920

2021

2122
class BrowserUseManager:
@@ -25,7 +26,17 @@ def __init__(self, client):
2526
async def start(
2627
self, params: StartBrowserUseTaskParams
2728
) -> StartBrowserUseTaskResponse:
28-
payload = params.model_dump(exclude_none=True, by_alias=True)
29+
try:
30+
payload = params.model_dump(exclude_none=True, by_alias=True)
31+
except HyperbrowserError:
32+
raise
33+
except Exception as exc:
34+
raise HyperbrowserError(
35+
"Failed to serialize browser-use start params",
36+
original_error=exc,
37+
) from exc
38+
if type(payload) is not dict:
39+
raise HyperbrowserError("Failed to serialize browser-use start params")
2940
if params.output_model_schema:
3041
payload["outputModelSchema"] = resolve_schema_input(
3142
params.output_model_schema

hyperbrowser/client/managers/sync_manager/agents/browser_use.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,25 @@
1212
StartBrowserUseTaskParams,
1313
StartBrowserUseTaskResponse,
1414
)
15+
from .....exceptions import HyperbrowserError
1516

1617

1718
class BrowserUseManager:
1819
def __init__(self, client):
1920
self._client = client
2021

2122
def start(self, params: StartBrowserUseTaskParams) -> StartBrowserUseTaskResponse:
22-
payload = params.model_dump(exclude_none=True, by_alias=True)
23+
try:
24+
payload = params.model_dump(exclude_none=True, by_alias=True)
25+
except HyperbrowserError:
26+
raise
27+
except Exception as exc:
28+
raise HyperbrowserError(
29+
"Failed to serialize browser-use start params",
30+
original_error=exc,
31+
) from exc
32+
if type(payload) is not dict:
33+
raise HyperbrowserError("Failed to serialize browser-use start params")
2334
if params.output_model_schema:
2435
payload["outputModelSchema"] = resolve_schema_input(
2536
params.output_model_schema

tests/test_browser_use_manager.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import asyncio
2+
from types import MappingProxyType, SimpleNamespace
3+
4+
import pytest
5+
6+
from hyperbrowser.client.managers.async_manager.agents.browser_use import (
7+
BrowserUseManager as AsyncBrowserUseManager,
8+
)
9+
from hyperbrowser.client.managers.sync_manager.agents.browser_use import (
10+
BrowserUseManager as SyncBrowserUseManager,
11+
)
12+
from hyperbrowser.exceptions import HyperbrowserError
13+
from hyperbrowser.models.agents.browser_use import StartBrowserUseTaskParams
14+
15+
16+
class _SyncTransport:
17+
def __init__(self) -> None:
18+
self.calls = []
19+
20+
def post(self, url: str, data=None) -> SimpleNamespace:
21+
self.calls.append((url, data))
22+
return SimpleNamespace(data={"jobId": "job_sync_1"})
23+
24+
25+
class _AsyncTransport:
26+
def __init__(self) -> None:
27+
self.calls = []
28+
29+
async def post(self, url: str, data=None) -> SimpleNamespace:
30+
self.calls.append((url, data))
31+
return SimpleNamespace(data={"jobId": "job_async_1"})
32+
33+
34+
class _SyncClient:
35+
def __init__(self) -> None:
36+
self.transport = _SyncTransport()
37+
38+
def _build_url(self, path: str) -> str:
39+
return path
40+
41+
42+
class _AsyncClient:
43+
def __init__(self) -> None:
44+
self.transport = _AsyncTransport()
45+
46+
def _build_url(self, path: str) -> str:
47+
return path
48+
49+
50+
def test_sync_browser_use_start_serializes_params():
51+
client = _SyncClient()
52+
manager = SyncBrowserUseManager(client)
53+
54+
response = manager.start(StartBrowserUseTaskParams(task="open docs"))
55+
56+
assert response.job_id == "job_sync_1"
57+
_, payload = client.transport.calls[0]
58+
assert payload == {"task": "open docs"}
59+
60+
61+
def test_async_browser_use_start_serializes_params():
62+
client = _AsyncClient()
63+
manager = AsyncBrowserUseManager(client)
64+
65+
async def run() -> None:
66+
response = await manager.start(StartBrowserUseTaskParams(task="open docs"))
67+
assert response.job_id == "job_async_1"
68+
_, payload = client.transport.calls[0]
69+
assert payload == {"task": "open docs"}
70+
71+
asyncio.run(run())
72+
73+
74+
def test_sync_browser_use_start_wraps_param_serialization_errors(
75+
monkeypatch: pytest.MonkeyPatch,
76+
):
77+
manager = SyncBrowserUseManager(_SyncClient())
78+
params = StartBrowserUseTaskParams(task="open docs")
79+
80+
def _raise_model_dump_error(*args, **kwargs):
81+
_ = args
82+
_ = kwargs
83+
raise RuntimeError("broken model_dump")
84+
85+
monkeypatch.setattr(
86+
StartBrowserUseTaskParams, "model_dump", _raise_model_dump_error
87+
)
88+
89+
with pytest.raises(
90+
HyperbrowserError, match="Failed to serialize browser-use start params"
91+
) as exc_info:
92+
manager.start(params)
93+
94+
assert isinstance(exc_info.value.original_error, RuntimeError)
95+
96+
97+
def test_sync_browser_use_start_rejects_non_dict_serialized_params(
98+
monkeypatch: pytest.MonkeyPatch,
99+
):
100+
manager = SyncBrowserUseManager(_SyncClient())
101+
params = StartBrowserUseTaskParams(task="open docs")
102+
103+
monkeypatch.setattr(
104+
StartBrowserUseTaskParams,
105+
"model_dump",
106+
lambda *args, **kwargs: MappingProxyType({"task": "open docs"}),
107+
)
108+
109+
with pytest.raises(
110+
HyperbrowserError, match="Failed to serialize browser-use start params"
111+
) as exc_info:
112+
manager.start(params)
113+
114+
assert exc_info.value.original_error is None
115+
116+
117+
def test_sync_browser_use_start_preserves_hyperbrowser_serialization_errors(
118+
monkeypatch: pytest.MonkeyPatch,
119+
):
120+
manager = SyncBrowserUseManager(_SyncClient())
121+
params = StartBrowserUseTaskParams(task="open docs")
122+
123+
def _raise_model_dump_error(*args, **kwargs):
124+
_ = args
125+
_ = kwargs
126+
raise HyperbrowserError("custom model_dump failure")
127+
128+
monkeypatch.setattr(
129+
StartBrowserUseTaskParams, "model_dump", _raise_model_dump_error
130+
)
131+
132+
with pytest.raises(
133+
HyperbrowserError, match="custom model_dump failure"
134+
) as exc_info:
135+
manager.start(params)
136+
137+
assert exc_info.value.original_error is None
138+
139+
140+
def test_async_browser_use_start_wraps_param_serialization_errors(
141+
monkeypatch: pytest.MonkeyPatch,
142+
):
143+
manager = AsyncBrowserUseManager(_AsyncClient())
144+
params = StartBrowserUseTaskParams(task="open docs")
145+
146+
def _raise_model_dump_error(*args, **kwargs):
147+
_ = args
148+
_ = kwargs
149+
raise RuntimeError("broken model_dump")
150+
151+
monkeypatch.setattr(
152+
StartBrowserUseTaskParams, "model_dump", _raise_model_dump_error
153+
)
154+
155+
async def run() -> None:
156+
with pytest.raises(
157+
HyperbrowserError, match="Failed to serialize browser-use start params"
158+
) as exc_info:
159+
await manager.start(params)
160+
assert isinstance(exc_info.value.original_error, RuntimeError)
161+
162+
asyncio.run(run())
163+
164+
165+
def test_async_browser_use_start_preserves_hyperbrowser_serialization_errors(
166+
monkeypatch: pytest.MonkeyPatch,
167+
):
168+
manager = AsyncBrowserUseManager(_AsyncClient())
169+
params = StartBrowserUseTaskParams(task="open docs")
170+
171+
def _raise_model_dump_error(*args, **kwargs):
172+
_ = args
173+
_ = kwargs
174+
raise HyperbrowserError("custom model_dump failure")
175+
176+
monkeypatch.setattr(
177+
StartBrowserUseTaskParams, "model_dump", _raise_model_dump_error
178+
)
179+
180+
async def run() -> None:
181+
with pytest.raises(
182+
HyperbrowserError, match="custom model_dump failure"
183+
) as exc_info:
184+
await manager.start(params)
185+
assert exc_info.value.original_error is None
186+
187+
asyncio.run(run())
188+
189+
190+
def test_async_browser_use_start_rejects_non_dict_serialized_params(
191+
monkeypatch: pytest.MonkeyPatch,
192+
):
193+
manager = AsyncBrowserUseManager(_AsyncClient())
194+
params = StartBrowserUseTaskParams(task="open docs")
195+
196+
monkeypatch.setattr(
197+
StartBrowserUseTaskParams,
198+
"model_dump",
199+
lambda *args, **kwargs: MappingProxyType({"task": "open docs"}),
200+
)
201+
202+
async def run() -> None:
203+
with pytest.raises(
204+
HyperbrowserError, match="Failed to serialize browser-use start params"
205+
) as exc_info:
206+
await manager.start(params)
207+
assert exc_info.value.original_error is None
208+
209+
asyncio.run(run())

0 commit comments

Comments
 (0)