Skip to content

Commit 0eb668a

Browse files
committed
refactor: change Scroll action to element-based navigation
1 parent 5a47221 commit 0eb668a

7 files changed

Lines changed: 303 additions & 152 deletions

File tree

tests/mocks/action_mocks.json

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -336,56 +336,53 @@
336336
"actions": [
337337
{
338338
"type": "Scroll",
339-
"locate": null,
340-
"param": {
341-
"direction": "down",
342-
"scrollType": "untilBottom",
343-
"distance": null
344-
}
339+
"locate": {"id": "1"},
340+
"param": null
345341
},
346342
{
347343
"type": "Scroll",
348-
"locate": null,
349-
"param": {
350-
"direction": "up",
351-
"scrollType": "untilTop",
352-
"distance": null
353-
}
344+
"locate": {"id": "2"},
345+
"param": null
354346
}
355347
],
356-
"id_map": {}
348+
"id_map": {
349+
"1": {
350+
"id": 100,
351+
"tagName": "footer",
352+
"isInViewport": false,
353+
"selector": "footer#site-footer",
354+
"xpath": "//*[@id=\"site-footer\"]",
355+
"center_y": 2000
356+
},
357+
"2": {
358+
"id": 101,
359+
"tagName": "header",
360+
"isInViewport": false,
361+
"selector": "header#site-header",
362+
"xpath": "//*[@id=\"site-header\"]",
363+
"center_y": 50
364+
}
365+
}
357366
},
358367
{
359368
"url": "https://arxiv.org/list/astro-ph/new",
360369
"actions": [
361370
{
362371
"type": "Scroll",
363-
"locate": null,
364-
"param": {
365-
"direction": "down",
366-
"scrollType": "once"
367-
}
368-
},
369-
{
370-
"type": "Scroll",
371-
"locate": null,
372-
"param": {
373-
"direction": "down",
374-
"scrollType": "once",
375-
"distance": 400
376-
}
377-
},
378-
{
379-
"type": "Scroll",
380-
"locate": null,
381-
"param": {
382-
"direction": "up",
383-
"scrollType": "once",
384-
"distance": 200
385-
}
372+
"locate": {"id": "1"},
373+
"param": null
386374
}
387375
],
388-
"id_map": {}
376+
"id_map": {
377+
"1": {
378+
"id": 200,
379+
"tagName": "section",
380+
"isInViewport": false,
381+
"selector": "section.results",
382+
"xpath": "//section[@class='results']",
383+
"center_y": 1500
384+
}
385+
}
389386
}
390387
],
391388
"SelectDropdown": [

tests/mocks/actions_negative_mocks.json

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,24 +63,30 @@
6363
],
6464
"Scroll_Negative": [
6565
{
66+
"description": "Scroll without locate.id should fail",
6667
"url": "https://arxiv.org/list/astro-ph/new",
6768
"actions": [
6869
{
6970
"type": "Scroll",
7071
"locate": null,
71-
"param": {
72-
"direction": "right",
73-
"scrollType": "once"
74-
}
72+
"param": null
7573
},
7674
{
7775
"type": "Scroll",
78-
"locate": null,
79-
"param": {
80-
"direction": "down",
81-
"scrollType": "untilRight",
82-
"distance": null
83-
}
76+
"locate": {},
77+
"param": null
78+
}
79+
],
80+
"id_map": {}
81+
},
82+
{
83+
"description": "Scroll with non-existent element should fail",
84+
"url": "https://arxiv.org/list/astro-ph/new",
85+
"actions": [
86+
{
87+
"type": "Scroll",
88+
"locate": {"id": "999"},
89+
"param": null
8490
}
8591
],
8692
"id_map": {}

webqa_agent/actions/action_executor.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ def _validate_params(self, action, required_params):
6868
for key in keys:
6969
value = value.get(key)
7070
if value is None:
71-
if action["type"] == "Scroll" and key == "distance":
72-
continue
7371
logging.error(f"Missing required parameter: {param}")
7472
return False # Return False to indicate validation failure
7573
return True # Return True if all parameters are present
@@ -267,18 +265,38 @@ async def _execute_input(self, action):
267265
return {"success": False, "message": f"Input action failed with an exception: {e}"}
268266

269267
async def _execute_scroll(self, action):
270-
"""Execute scroll action."""
271-
if not self._validate_params(action, ["param.direction", "param.scrollType", "param.distance"]):
272-
return {"success": False, "message": "Missing parameters for scroll action"}
273-
direction = action.get("param").get("direction", "down")
274-
scroll_type = action.get("param").get("scrollType", "once")
275-
distance = action.get("param").get("distance", None)
276-
277-
success = await self._actions.scroll(direction, scroll_type, distance)
268+
"""Execute scroll action - scroll to a specific element."""
269+
if not self._validate_params(action, ["locate.id"]):
270+
return {"success": False, "message": "Missing locate.id for scroll action. Scroll requires an element ID to scroll to."}
271+
272+
element_id = action.get("locate", {}).get("id")
273+
274+
success = await self._actions.scroll(element_id)
275+
276+
# Read action context for detailed error information
277+
ctx = action_context_var.get()
278+
278279
if success:
279-
return {"success": True, "message": f"Scrolled {direction} successfully."}
280+
return {"success": True, "message": f"Scrolled to element {element_id} successfully."}
280281
else:
281-
return {"success": False, "message": "Scroll action failed."}
282+
# Enrich error message with context
283+
base_message = f"Scroll to element {element_id} failed."
284+
error_details = {}
285+
286+
if ctx and ctx.error_type:
287+
error_details = {
288+
"error_type": ctx.error_type,
289+
"error_reason": ctx.error_reason,
290+
"attempted_strategies": ctx.attempted_strategies,
291+
"element_info": ctx.element_info,
292+
"playwright_error": ctx.playwright_error
293+
}
294+
295+
return {
296+
"success": False,
297+
"message": base_message,
298+
"error_details": error_details
299+
}
282300

283301
async def _execute_keyboard_press(self, action):
284302
"""Execute keyboard press action."""

0 commit comments

Comments
 (0)