Skip to content

Commit 870d717

Browse files
committed
Include cursor-relative scrolling in computer action params
1 parent 79ee36a commit 870d717

6 files changed

Lines changed: 54 additions & 8 deletions

File tree

‎hyperbrowser/client/managers/async_manager/computer_action.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555

5656

5757
def _action_param_model(params):
58+
if isinstance(params, ScrollAtCursorActionParams):
59+
return ScrollAtCursorActionParams
60+
5861
for model in _ACTION_PARAM_MODELS.values():
5962
if isinstance(params, model):
6063
return model
@@ -64,6 +67,8 @@ def _action_param_model(params):
6467
if isinstance(action, ComputerAction):
6568
action = action.value
6669
model = _ACTION_PARAM_MODELS.get(action)
70+
if model is ScrollActionParams and "x" not in params and "y" not in params:
71+
return ScrollAtCursorActionParams
6772
if model is not None:
6873
return model
6974

@@ -247,8 +252,7 @@ async def scroll_at_cursor(
247252
keys=keys,
248253
return_screenshot=return_screenshot,
249254
)
250-
payload = dump_request(params, ScrollAtCursorActionParams, name="params")
251-
return await self._post_request(session, payload, ComputerActionResponse)
255+
return await self._execute_request(session, params)
252256

253257
async def get_clipboard_text(
254258
self,

‎hyperbrowser/client/managers/sync_manager/computer_action.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555

5656

5757
def _action_param_model(params):
58+
if isinstance(params, ScrollAtCursorActionParams):
59+
return ScrollAtCursorActionParams
60+
5861
for model in _ACTION_PARAM_MODELS.values():
5962
if isinstance(params, model):
6063
return model
@@ -64,6 +67,8 @@ def _action_param_model(params):
6467
if isinstance(action, ComputerAction):
6568
action = action.value
6669
model = _ACTION_PARAM_MODELS.get(action)
70+
if model is ScrollActionParams and "x" not in params and "y" not in params:
71+
return ScrollAtCursorActionParams
6772
if model is not None:
6873
return model
6974

@@ -247,8 +252,7 @@ def scroll_at_cursor(
247252
keys=keys,
248253
return_screenshot=return_screenshot,
249254
)
250-
payload = dump_request(params, ScrollAtCursorActionParams, name="params")
251-
return self._post_request(session, payload, ComputerActionResponse)
255+
return self._execute_request(session, params)
252256

253257
def get_clipboard_text(
254258
self,

‎hyperbrowser/models/computer_action.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ class ListWindowsActionParams(BaseModel):
232232
ScreenshotActionParams,
233233
CursorPositionActionParams,
234234
ScrollActionParams,
235+
ScrollAtCursorActionParams,
235236
TypeTextActionParams,
236237
HoldKeyActionParams,
237238
MouseDownActionParams,

‎hyperbrowser/types/computer_action.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class ListWindowsActionParams(TypedDict, total=False):
151151
ScreenshotActionParams,
152152
CursorPositionActionParams,
153153
ScrollActionParams,
154+
ScrollAtCursorActionParams,
154155
TypeTextActionParams,
155156
HoldKeyActionParams,
156157
MouseDownActionParams,

‎tests/test_computer_action_primitives.py‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,18 +214,41 @@ def test_new_request_and_response_types_are_public():
214214
assert "CursorPositionActionResponse" in models.__all__
215215
assert "ScrollAtCursorActionParams" in models.__all__
216216
assert "ScrollAtCursorActionParams" in types.__all__
217+
assert ScrollAtCursorActionParams in get_args(ComputerActionParams)
218+
assert types.ScrollAtCursorActionParams in get_args(types.ComputerActionParams)
217219
assert "ComputerActionResponseDataCursorPosition" in models.__all__
218220

219221

220222
@pytest.mark.parametrize("keys", [None, [], ["Shift_L"]])
221-
def test_scroll_at_cursor_dict_and_model_wire_parity(keys):
223+
def test_scroll_at_cursor_dict_and_model_wire_parity(manager, keys):
224+
driver, session, calls = manager
222225
params = {"action": "scroll", "scroll_x": 0, "scroll_y": -2}
223226
if keys is not None:
224227
params["keys"] = keys
225228
model = ScrollAtCursorActionParams(**params)
226-
assert dump_request(
227-
params, ScrollAtCursorActionParams, name="params"
228-
) == dump_request(model, ScrollAtCursorActionParams, name="params")
229+
run(driver._execute_request(session, params))
230+
run(driver._execute_request(session, model))
231+
assert calls[-1] == calls[-2]
232+
assert calls[-1][2] == {
233+
"action": "scroll",
234+
"scrollX": 0,
235+
"scrollY": -2,
236+
"returnScreenshot": False,
237+
**({"keys": keys} if keys is not None else {}),
238+
}
239+
240+
241+
@pytest.mark.parametrize("coordinates", [{"x": 10}, {"y": 20}, {"x": None, "y": None}])
242+
def test_scroll_requests_reject_incomplete_or_null_coordinates(manager, coordinates):
243+
driver, session, calls = manager
244+
with pytest.raises(ValueError):
245+
run(
246+
driver._execute_request(
247+
session,
248+
{"action": "scroll", "scroll_x": 0, "scroll_y": 1, **coordinates},
249+
)
250+
)
251+
assert not calls
229252

230253

231254
def test_old_scroll_model_still_requires_coordinates():

‎tests/typecheck/valid_requests.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
SandboxImageBuildListParams as LegacySandboxImageBuildListParams,
1212
VolumeListParams as LegacyVolumeListParams,
1313
ScrollActionParams as LegacyScrollActionParams,
14+
ScrollAtCursorActionParams as LegacyScrollAtCursorActionParams,
1415
ComputerActionResponseData,
1516
ComputerActionResponseDataListWindows,
1617
ComputerActionResponseDataCursorPosition,
@@ -390,6 +391,12 @@ def valid_sync_computer_actions(client: Hyperbrowser) -> None:
390391
client.computer_action.drag("session-id", [{"x": 1, "y": 2}], keys=["Shift_L"])
391392
client.computer_action.scroll("session-id", 10, 20, 0, 1, keys=["Alt_L"])
392393
client.computer_action.scroll_at_cursor("session-id", scroll_y=1, keys=["Alt_L"])
394+
client.computer_action._execute_request(
395+
"session-id", {"action": "scroll", "scroll_x": 0, "scroll_y": 1}
396+
)
397+
client.computer_action._execute_request(
398+
"session-id", LegacyScrollAtCursorActionParams(scroll_x=0, scroll_y=1)
399+
)
393400
cursor: CursorPositionActionResponse = client.computer_action.cursor_position(
394401
"session-id"
395402
)
@@ -420,6 +427,12 @@ async def valid_async_computer_actions(client: AsyncHyperbrowser) -> None:
420427
await client.computer_action.scroll_at_cursor(
421428
"session-id", scroll_y=1, keys=["Alt_L"]
422429
)
430+
await client.computer_action._execute_request(
431+
"session-id", {"action": "scroll", "scroll_x": 0, "scroll_y": 1}
432+
)
433+
await client.computer_action._execute_request(
434+
"session-id", LegacyScrollAtCursorActionParams(scroll_x=0, scroll_y=1)
435+
)
423436
cursor: CursorPositionActionResponse = await client.computer_action.cursor_position(
424437
"session-id"
425438
)

0 commit comments

Comments
 (0)