Skip to content

Commit 3d5a8f0

Browse files
authored
v0.1.6
feat: add star gif in readme feat: update deepcrawler crawl function parameters feat: add mouse action and improve descriptions for actions prompt feat: implement multi-frame crawler for iframe support
2 parents cbc323e + 6d2a3ea commit 3d5a8f0

File tree

12 files changed

+651
-131
lines changed

12 files changed

+651
-131
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828

2929
<p align="center"><a href="README.md">English</a> · <a href="README_zh-CN.md">简体中文</a></p>
3030

31+
<p align="center">
32+
If you like WebQA Agent, please give us a ⭐ on GitHub!
33+
<br/>
34+
<a href="https://github.com/MigoXLab/webqa-agent/stargazers" target="_blank">
35+
<img src="docs/images/star.gif" alt="Click Star" width="900">
36+
</a>
37+
</p>
38+
3139
<p align="center" itemprop="description">🤖 <strong>WebQA Agent</strong> is an autonomous web browser agent that audits performance, functionality & UX for engineers and vibe-coding creators. ✨</p>
3240

3341
</div>

README_zh-CN.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828

2929
<p align="center"><a href="README.md">English</a> · <a href="README_zh-CN.md">简体中文</a></p>
3030

31+
<p align="center">
32+
如果觉得有帮助,欢迎在 GitHub 上点个 ⭐ 支持!
33+
<br/>
34+
<a href="https://github.com/MigoXLab/webqa-agent/stargazers" target="_blank">
35+
<img src="docs/images/star.gif" alt="Click Star" width="900">
36+
</a>
37+
</p>
38+
3139
<p align="center">🤖 <strong>WebQA Agent</strong> 是全自动网页评估测试 Agent,一键完成性能、功能与交互体验的测试评估 ✨</p>
3240
</div>
3341

docs/images/star.gif

32 KB
Loading

tests/test_crawler.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ class TestCrawler:
2626

2727
# Different crawl parameter combinations to test
2828
CRAWL_PARAMS = [
29-
{'name': 'highlight_crawl', 'highlight': True, 'highlight_text': False, 'viewport_only': True},
30-
{'name': 'text_highlight_crawl', 'highlight': True, 'highlight_text': True, 'viewport_only': True},
31-
{'name': 'viewport_highlight_crawl', 'highlight': True, 'highlight_text': False, 'viewport_only': True},
29+
{'name': 'highlight_crawl', 'highlight': True, 'filter_text': False, 'viewport_only': True},
30+
{'name': 'text_highlight_crawl', 'highlight': True, 'filter_text': True, 'viewport_only': True},
31+
{'name': 'viewport_highlight_crawl', 'highlight': True, 'filter_text': False, 'viewport_only': True},
3232
]
3333

3434
# Directories (class attributes; accessible via self)
@@ -155,7 +155,7 @@ async def crawl_single_url(self, url: str, params: Dict[str, Any]) -> Dict[str,
155155
crawl_result = await crawler.crawl(
156156
page=self.page,
157157
highlight=params['highlight'],
158-
highlight_text=params['highlight_text'],
158+
filter_text=params['filter_text'],
159159
viewport_only=params['viewport_only'],
160160
)
161161
crawl_data = crawl_result.element_tree

webqa_agent/actions/action_executor.py

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,13 @@ def __init__(self, action_handler):
1414
"Clear": self._execute_clear,
1515
"Scroll": self._execute_scroll,
1616
"KeyboardPress": self._execute_keyboard_press,
17-
"FalsyConditionStatement": self._execute_falsy,
18-
"Check": self._execute_check,
1917
"GetNewPage": self._execute_get_new_page,
2018
"Upload": self._execute_upload,
2119
"SelectDropdown": self._execute_select_dropdown,
2220
"Drag": self._execute_drag,
2321
"GoToPage": self._execute_go_to_page, # Added missing action
2422
"GoBack": self._execute_go_back, # Added browser back navigation
23+
"Mouse": self._execute_mouse, # Added mouse action
2524
}
2625

2726
async def initialize(self):
@@ -146,14 +145,6 @@ async def _execute_keyboard_press(self, action):
146145
else:
147146
return {"success": False, "message": "Keyboard press failed."}
148147

149-
async def _execute_falsy(self, action):
150-
"""Execute falsy condition statement."""
151-
return {"success": True, "message": "Falsy condition met."}
152-
153-
async def _execute_check(self, action):
154-
"""Execute check action."""
155-
return {"success": True, "message": "Check action completed."}
156-
157148
async def _execute_get_new_page(self, action):
158149
"""Execute get new page action."""
159150
success = await self._actions.get_new_page()
@@ -335,4 +326,68 @@ async def _execute_go_back(self, action):
335326
return {"success": False, "message": "Go back action not supported by action handler"}
336327
except Exception as e:
337328
logging.error(f"Go back action failed: {str(e)}")
338-
return {"success": False, "message": f"Go back failed: {str(e)}", "playwright_error": str(e)}
329+
return {"success": False, "message": f"Go back failed: {str(e)}", "playwright_error": str(e)}
330+
331+
async def _execute_mouse(self, action):
332+
"""Unified mouse action supporting move and wheel.
333+
334+
Accepted param formats:
335+
- { op: "move", x: number, y: number }
336+
- { op: "wheel", deltaX: number, deltaY: number }
337+
- Back-compat: if op is omitted, decide by presence of keys
338+
"""
339+
try:
340+
param = action.get("param")
341+
if not param or not isinstance(param, dict):
342+
return {"success": False, "message": "Missing or invalid param for mouse action"}
343+
344+
op = param.get("op")
345+
346+
# Auto-detect if op not provided or empty
347+
if not op:
348+
if "x" in param and "y" in param:
349+
op = "move"
350+
elif "deltaX" in param or "deltaY" in param:
351+
op = "wheel"
352+
else:
353+
return {"success": False, "message": "Missing mouse operation parameters (x/y or deltaX/deltaY)"}
354+
355+
if op == "move":
356+
if not self._validate_params(action, ["param.x", "param.y"]):
357+
return {"success": False, "message": "Missing x or y coordinates for mouse move"}
358+
359+
x = param.get("x")
360+
y = param.get("y")
361+
362+
# Validate coordinates are numbers
363+
if not isinstance(x, (int, float)) or not isinstance(y, (int, float)):
364+
return {"success": False, "message": "x and y coordinates must be numbers"}
365+
366+
success = await self._actions.mouse_move(x, y)
367+
if success:
368+
return {"success": True, "message": f"Mouse moved to ({x}, {y})"}
369+
else:
370+
return {"success": False, "message": "Mouse move action failed"}
371+
372+
elif op == "wheel":
373+
# Default missing keys to 0
374+
dx = param.get("deltaX", 0)
375+
dy = param.get("deltaY", 0)
376+
377+
# Validate deltas are numbers
378+
if not isinstance(dx, (int, float)) or not isinstance(dy, (int, float)):
379+
return {"success": False, "message": "deltaX and deltaY must be numbers"}
380+
381+
success = await self._actions.mouse_wheel(dx, dy)
382+
if success:
383+
return {"success": True, "message": f"Mouse wheel scrolled (deltaX: {dx}, deltaY: {dy})"}
384+
else:
385+
return {"success": False, "message": "Mouse wheel action failed"}
386+
387+
else:
388+
logging.error(f"Unknown mouse op: {op}. Expected 'move' or 'wheel'.")
389+
return {"success": False, "message": f"Unknown mouse operation: {op}. Expected 'move' or 'wheel'"}
390+
391+
except Exception as e:
392+
logging.error(f"Mouse action execution failed: {str(e)}")
393+
return {"success": False, "message": f"Mouse action failed with an exception: {e}"}

webqa_agent/actions/action_handler.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,3 +1429,30 @@ async def drag(self, source_coords, target_coords):
14291429
except Exception as e:
14301430
logging.error(f'Drag action failed: {str(e)}')
14311431
return False
1432+
1433+
async def mouse_move(self, x: int | float, y: int | float) -> bool:
1434+
"""Move mouse to absolute coordinates (x, y)."""
1435+
try:
1436+
# Coerce to numbers in case strings are provided
1437+
target_x = float(x)
1438+
target_y = float(y)
1439+
await self.page.mouse.move(target_x, target_y)
1440+
logging.info(f"mouse move to ({target_x}, {target_y})")
1441+
await asyncio.sleep(0.1)
1442+
return True
1443+
except Exception as e:
1444+
logging.error(f"Mouse move failed: {str(e)}")
1445+
return False
1446+
1447+
async def mouse_wheel(self, delta_x: int | float = 0, delta_y: int | float = 0) -> bool:
1448+
"""Scroll the mouse wheel by delta values."""
1449+
try:
1450+
dx = float(delta_x) if delta_x is not None else 0.0
1451+
dy = float(delta_y) if delta_y is not None else 0.0
1452+
await self.page.mouse.wheel(dx, dy)
1453+
logging.info(f"mouse wheel by (deltaX={dx}, deltaY={dy})")
1454+
await asyncio.sleep(0.1)
1455+
return True
1456+
except Exception as e:
1457+
logging.error(f"Mouse wheel failed: {str(e)}")
1458+
return False

0 commit comments

Comments
 (0)