Spaces:
Running
Running
feat: enhance test case management and update README
Browse files- Add automatic case indexing (1: CASE_NAME format) to fix duplicate names
- Add HuggingFace Spaces and community links to README
- README.md +12 -5
- README_zh-CN.md +12 -5
- app_gradio/demo_gradio.py +30 -5
- app_gradio/gradio_i18n.json +8 -6
- docs/images/webqa.svg +0 -0
- webqa_agent/testers/function_tester.py +11 -3
README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
-
|
2 |
|
3 |
<!-- badges -->
|
4 |
-
<p align="
|
5 |
<a href="https://github.com/MigoXLab/webqa-agent/blob/main/LICENSE"><img src="https://img.shields.io/github/license/MigoXLab/webqa-agent" alt="License"></a>
|
6 |
<a href="https://github.com/MigoXLab/webqa-agent/stargazers"><img src="https://img.shields.io/github/stars/MigoXLab/webqa-agent" alt="GitHub stars"></a>
|
7 |
<a href="https://github.com/MigoXLab/webqa-agent/network/members"><img src="https://img.shields.io/github/forks/MigoXLab/webqa-agent" alt="GitHub forks"></a>
|
@@ -9,9 +9,14 @@
|
|
9 |
<a href="https://deepwiki.com/MigoXLab/webqa-agent"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
10 |
</p>
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
-
|
15 |
|
16 |
## 🚀 Core Features
|
17 |
|
@@ -101,7 +106,9 @@ python webqa-agent.py
|
|
101 |
|
102 |
## Online Demo
|
103 |
|
104 |
-
|
|
|
|
|
105 |
|
106 |
## Usage
|
107 |
|
|
|
1 |
+
<h1 align="center">WebQA Agent</h1>
|
2 |
|
3 |
<!-- badges -->
|
4 |
+
<p align="center">
|
5 |
<a href="https://github.com/MigoXLab/webqa-agent/blob/main/LICENSE"><img src="https://img.shields.io/github/license/MigoXLab/webqa-agent" alt="License"></a>
|
6 |
<a href="https://github.com/MigoXLab/webqa-agent/stargazers"><img src="https://img.shields.io/github/stars/MigoXLab/webqa-agent" alt="GitHub stars"></a>
|
7 |
<a href="https://github.com/MigoXLab/webqa-agent/network/members"><img src="https://img.shields.io/github/forks/MigoXLab/webqa-agent" alt="GitHub forks"></a>
|
|
|
9 |
<a href="https://deepwiki.com/MigoXLab/webqa-agent"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
10 |
</p>
|
11 |
|
12 |
+
<p align="center">
|
13 |
+
Try Demo 🤗<a href="https://huggingface.co/spaces/mmmay0722/WebQA-Agent">HuggingFace</a> | 🚀<a href="https://modelscope.cn/studios/mmmmei22/WebQA-Agent/summary">ModelScope</a><br>
|
14 |
+
Join us on 🎮<a href="https://discord.gg/K5TtkVcx">Discord</a> | 💬<a href="https://aicarrier.feishu.cn/docx/NRNXdIirXoSQEHxhaqjchUfenzd">WeChat</a>
|
15 |
+
</p>
|
16 |
+
|
17 |
+
<p align="center"><a href="README.md">English</a> · <a href="README_zh-CN.md">简体中文</a></p>
|
18 |
|
19 |
+
<p align="center">🤖 <strong>WebQA Agent</strong> is an autonomous web browser agent that audits performance, functionality & UX for engineers and vibe-coding creators. ✨</p>
|
20 |
|
21 |
## 🚀 Core Features
|
22 |
|
|
|
106 |
|
107 |
## Online Demo
|
108 |
|
109 |
+
🚀 **Try WebQA Agent Online:**
|
110 |
+
- **Hugging Face Spaces**: [WebQA-Agent on Hugging Face](https://huggingface.co/spaces/mmmay0722/WebQA-Agent)
|
111 |
+
- **ModelScope Studio**: [WebQA-Agent on ModelScope](https://modelscope.cn/studios/mmmmei22/WebQA-Agent/summary)
|
112 |
|
113 |
## Usage
|
114 |
|
README_zh-CN.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
-
|
2 |
|
3 |
<!-- badges -->
|
4 |
-
<p align="
|
5 |
<a href="https://github.com/MigoXLab/webqa-agent/blob/main/LICENSE"><img src="https://img.shields.io/github/license/MigoXLab/webqa-agent" alt="License"></a>
|
6 |
<a href="https://github.com/MigoXLab/webqa-agent/stargazers"><img src="https://img.shields.io/github/stars/MigoXLab/webqa-agent" alt="GitHub stars"></a>
|
7 |
<a href="https://github.com/MigoXLab/webqa-agent/network/members"><img src="https://img.shields.io/github/forks/MigoXLab/webqa-agent" alt="GitHub forks"></a>
|
@@ -9,9 +9,14 @@
|
|
9 |
<a href="https://deepwiki.com/MigoXLab/webqa-agent"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
10 |
</p>
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
-
|
15 |
|
16 |
## 🚀 核心特性
|
17 |
|
@@ -104,7 +109,9 @@ python webqa-agent.py
|
|
104 |
|
105 |
## 在线演示
|
106 |
|
107 |
-
|
|
|
|
|
108 |
|
109 |
## 使用说明
|
110 |
|
|
|
1 |
+
<h1 align="center">WebQA Agent</h1>
|
2 |
|
3 |
<!-- badges -->
|
4 |
+
<p align="center">
|
5 |
<a href="https://github.com/MigoXLab/webqa-agent/blob/main/LICENSE"><img src="https://img.shields.io/github/license/MigoXLab/webqa-agent" alt="License"></a>
|
6 |
<a href="https://github.com/MigoXLab/webqa-agent/stargazers"><img src="https://img.shields.io/github/stars/MigoXLab/webqa-agent" alt="GitHub stars"></a>
|
7 |
<a href="https://github.com/MigoXLab/webqa-agent/network/members"><img src="https://img.shields.io/github/forks/MigoXLab/webqa-agent" alt="GitHub forks"></a>
|
|
|
9 |
<a href="https://deepwiki.com/MigoXLab/webqa-agent"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
10 |
</p>
|
11 |
|
12 |
+
<p align="center">
|
13 |
+
体验Demo 🤗<a href="https://huggingface.co/spaces/mmmay0722/WebQA-Agent">HuggingFace</a> | 🚀<a href="https://modelscope.cn/studios/mmmmei22/WebQA-Agent/summary">ModelScope</a><br>
|
14 |
+
加入我们 🎮<a href="https://discord.gg/K5TtkVcx">Discord</a> | 💬<a href="https://aicarrier.feishu.cn/docx/NRNXdIirXoSQEHxhaqjchUfenzd">微信群</a>
|
15 |
+
</p>
|
16 |
+
|
17 |
+
<p align="center"><a href="README.md">English</a> · <a href="README_zh-CN.md">简体中文</a></p>
|
18 |
|
19 |
+
<p align="center">🤖 <strong>WebQA Agent</strong> 是全自动网页评估测试 Agent,一键完成性能、功能与交互体验的测试评估 ✨</p>
|
20 |
|
21 |
## 🚀 核心特性
|
22 |
|
|
|
109 |
|
110 |
## 在线演示
|
111 |
|
112 |
+
🚀 **在线体验 WebQA Agent:**
|
113 |
+
- **Hugging Face Spaces**: [WebQA-Agent on Hugging Face](https://huggingface.co/spaces/mmmay0722/WebQA-Agent)
|
114 |
+
- **ModelScope Studio**: [WebQA-Agent on ModelScope](https://modelscope.cn/studios/mmmmei22/WebQA-Agent/summary)
|
115 |
|
116 |
## 使用说明
|
117 |
|
app_gradio/demo_gradio.py
CHANGED
@@ -157,6 +157,16 @@ def create_config_dict(
|
|
157 |
report_language: str = "zh-CN"
|
158 |
) -> Dict[str, Any]:
|
159 |
"""Create configuration dictionary"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
config = {
|
161 |
"target": {
|
162 |
"url": url,
|
@@ -166,7 +176,7 @@ def create_config_dict(
|
|
166 |
"function_test": {
|
167 |
"enabled": function_test_enabled,
|
168 |
"type": function_test_type,
|
169 |
-
"business_objectives":
|
170 |
},
|
171 |
"ux_test": {
|
172 |
"enabled": ux_test_enabled
|
@@ -321,10 +331,6 @@ def submit_test(
|
|
321 |
if not any([function_test_enabled, ux_test_enabled, performance_test_enabled, security_test_enabled]):
|
322 |
return get_text(interface_language, "messages.error_no_tests"), "", False
|
323 |
|
324 |
-
# If function test is enabled but no business objectives set
|
325 |
-
if function_test_enabled and function_test_type == "ai" and not business_objectives.strip():
|
326 |
-
return get_text(interface_language, "messages.error_no_business_objectives"), "", False
|
327 |
-
|
328 |
# Validate LLM configuration
|
329 |
valid, msg = validate_llm_config(api_key, base_url, model, interface_language)
|
330 |
if not valid:
|
@@ -357,6 +363,7 @@ def submit_test(
|
|
357 |
"tests": {
|
358 |
"function": function_test_enabled,
|
359 |
"function_type": function_test_type,
|
|
|
360 |
"ux": ux_test_enabled,
|
361 |
},
|
362 |
"submitted_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
@@ -630,6 +637,15 @@ def create_gradio_interface(language: str = "zh-CN"):
|
|
630 |
.fixed-width-table td:nth-child(6),
|
631 |
.content-wrapper .gradio-dataframe th:nth-child(6),
|
632 |
.content-wrapper .gradio-dataframe td:nth-child(6) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
633 |
width: auto !important;
|
634 |
max-width: none !important;
|
635 |
min-width: 70px !important;
|
@@ -866,12 +882,21 @@ def create_gradio_interface(language: str = "zh-CN"):
|
|
866 |
def get_history_rows(lang):
|
867 |
rows = []
|
868 |
for item in reversed(submission_history[-100:]):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
869 |
rows.append([
|
870 |
item["submitted_at"],
|
871 |
item["task_id"],
|
872 |
item["url"],
|
873 |
"✅" if item["tests"]["function"] else "-",
|
874 |
item["tests"]["function_type"],
|
|
|
875 |
"✅" if item["tests"]["ux"] else "-"
|
876 |
])
|
877 |
return rows
|
|
|
157 |
report_language: str = "zh-CN"
|
158 |
) -> Dict[str, Any]:
|
159 |
"""Create configuration dictionary"""
|
160 |
+
|
161 |
+
final_business_objectives = business_objectives.strip()
|
162 |
+
default_constraint = get_text(report_language, "config.default_business_objectives")
|
163 |
+
|
164 |
+
if final_business_objectives:
|
165 |
+
separator = ","
|
166 |
+
final_business_objectives = f"{final_business_objectives}{separator}{default_constraint}"
|
167 |
+
else:
|
168 |
+
final_business_objectives = default_constraint
|
169 |
+
|
170 |
config = {
|
171 |
"target": {
|
172 |
"url": url,
|
|
|
176 |
"function_test": {
|
177 |
"enabled": function_test_enabled,
|
178 |
"type": function_test_type,
|
179 |
+
"business_objectives": final_business_objectives
|
180 |
},
|
181 |
"ux_test": {
|
182 |
"enabled": ux_test_enabled
|
|
|
331 |
if not any([function_test_enabled, ux_test_enabled, performance_test_enabled, security_test_enabled]):
|
332 |
return get_text(interface_language, "messages.error_no_tests"), "", False
|
333 |
|
|
|
|
|
|
|
|
|
334 |
# Validate LLM configuration
|
335 |
valid, msg = validate_llm_config(api_key, base_url, model, interface_language)
|
336 |
if not valid:
|
|
|
363 |
"tests": {
|
364 |
"function": function_test_enabled,
|
365 |
"function_type": function_test_type,
|
366 |
+
"business_objectives": business_objectives,
|
367 |
"ux": ux_test_enabled,
|
368 |
},
|
369 |
"submitted_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
637 |
.fixed-width-table td:nth-child(6),
|
638 |
.content-wrapper .gradio-dataframe th:nth-child(6),
|
639 |
.content-wrapper .gradio-dataframe td:nth-child(6) {
|
640 |
+
width: auto !important;
|
641 |
+
max-width: none !important;
|
642 |
+
min-width: 200px !important;
|
643 |
+
text-align: left !important;
|
644 |
+
}
|
645 |
+
.fixed-width-table th:nth-child(7),
|
646 |
+
.fixed-width-table td:nth-child(7),
|
647 |
+
.content-wrapper .gradio-dataframe th:nth-child(7),
|
648 |
+
.content-wrapper .gradio-dataframe td:nth-child(7) {
|
649 |
width: auto !important;
|
650 |
max-width: none !important;
|
651 |
min-width: 70px !important;
|
|
|
882 |
def get_history_rows(lang):
|
883 |
rows = []
|
884 |
for item in reversed(submission_history[-100:]):
|
885 |
+
business_objectives = item["tests"].get("business_objectives", "")
|
886 |
+
function_type = item["tests"]["function_type"]
|
887 |
+
|
888 |
+
if function_type == "ai" and business_objectives:
|
889 |
+
business_display = business_objectives[:30] + "..." if len(business_objectives) > 30 else business_objectives
|
890 |
+
else:
|
891 |
+
business_display = "-"
|
892 |
+
|
893 |
rows.append([
|
894 |
item["submitted_at"],
|
895 |
item["task_id"],
|
896 |
item["url"],
|
897 |
"✅" if item["tests"]["function"] else "-",
|
898 |
item["tests"]["function_type"],
|
899 |
+
business_display,
|
900 |
"✅" if item["tests"]["ux"] else "-"
|
901 |
])
|
902 |
return rows
|
app_gradio/gradio_i18n.json
CHANGED
@@ -29,8 +29,9 @@
|
|
29 |
"function_test_type": "功能测试类型",
|
30 |
"function_test_type_info": "default: 遍历测试,覆盖可点击元素和所有链接\n ai: 基于视觉模型的智能测试,能够模拟真实用户行为、理解业务上下文,验证网页功能。",
|
31 |
"business_objectives": "AI功能测试业务目标",
|
32 |
-
"business_objectives_placeholder": "
|
33 |
-
"business_objectives_info": "ai:
|
|
|
34 |
"ux_test": "用户体验测试",
|
35 |
"performance_test": "性能测试",
|
36 |
"performance_test_info": "目前在 ModelScope 版本不可用;请前往 GitHub 体验",
|
@@ -54,7 +55,7 @@
|
|
54 |
},
|
55 |
"history": {
|
56 |
"title": "提交记录",
|
57 |
-
"headers": ["提交时间", "任务ID", "URL", "功能测试", "类型", "UX测试"],
|
58 |
"refresh_btn": "🔄 刷新历史记录"
|
59 |
},
|
60 |
"messages": {
|
@@ -118,8 +119,9 @@
|
|
118 |
"function_test_type": "Function Test Type",
|
119 |
"function_test_type_info": "default: Traverse clickable elements & links.\n ai: Vision-model intelligent test simulating users & validating functionality.",
|
120 |
"business_objectives": "AI Function Test Business Objectives",
|
121 |
-
"business_objectives_placeholder": "Test chat functionality
|
122 |
-
"business_objectives_info": "ai: Customize different scenarios, accurately find complex functional issues",
|
|
|
123 |
"ux_test": "User Experience Test",
|
124 |
"performance_test": "Performance Test",
|
125 |
"performance_test_info": "Currently unavailable in HuggingFace version; please visit GitHub for experience",
|
@@ -143,7 +145,7 @@
|
|
143 |
},
|
144 |
"history": {
|
145 |
"title": "Submission Records",
|
146 |
-
"headers": ["Submit Time", "Task ID", "URL", "Function Test", "Type", "UX Test"],
|
147 |
"refresh_btn": "🔄 Refresh History"
|
148 |
},
|
149 |
"messages": {
|
|
|
29 |
"function_test_type": "功能测试类型",
|
30 |
"function_test_type_info": "default: 遍历测试,覆盖可点击元素和所有链接\n ai: 基于视觉模型的智能测试,能够模拟真实用户行为、理解业务上下文,验证网页功能。",
|
31 |
"business_objectives": "AI功能测试业务目标",
|
32 |
+
"business_objectives_placeholder": "测试对话功能",
|
33 |
+
"business_objectives_info": "ai: 定制不同场景,精准发现复杂功能问题。留空将使用默认设置(生成1个测试用例,每个用例包含6个步骤以内)",
|
34 |
+
"default_business_objectives": "生成1个测试用例,每个用例包含6个步骤以内",
|
35 |
"ux_test": "用户体验测试",
|
36 |
"performance_test": "性能测试",
|
37 |
"performance_test_info": "目前在 ModelScope 版本不可用;请前往 GitHub 体验",
|
|
|
55 |
},
|
56 |
"history": {
|
57 |
"title": "提交记录",
|
58 |
+
"headers": ["提交时间", "任务ID", "URL", "功能测试", "类型", "业务目标", "UX测试"],
|
59 |
"refresh_btn": "🔄 刷新历史记录"
|
60 |
},
|
61 |
"messages": {
|
|
|
119 |
"function_test_type": "Function Test Type",
|
120 |
"function_test_type_info": "default: Traverse clickable elements & links.\n ai: Vision-model intelligent test simulating users & validating functionality.",
|
121 |
"business_objectives": "AI Function Test Business Objectives",
|
122 |
+
"business_objectives_placeholder": "Test chat functionality",
|
123 |
+
"business_objectives_info": "ai: Customize different scenarios, accurately find complex functional issues. Leave blank to use default settings (generate 1 test case with no more than 6 steps per case)",
|
124 |
+
"default_business_objectives": "Generate 1 test case with no more than 6 steps per case",
|
125 |
"ux_test": "User Experience Test",
|
126 |
"performance_test": "Performance Test",
|
127 |
"performance_test_info": "Currently unavailable in HuggingFace version; please visit GitHub for experience",
|
|
|
145 |
},
|
146 |
"history": {
|
147 |
"title": "Submission Records",
|
148 |
+
"headers": ["Submit Time", "Task ID", "URL", "Function Test", "Type", "Business Objectives", "UX Test"],
|
149 |
"refresh_btn": "🔄 Refresh History"
|
150 |
},
|
151 |
"messages": {
|
docs/images/webqa.svg
CHANGED
|
|
webqa_agent/testers/function_tester.py
CHANGED
@@ -335,6 +335,7 @@ class UITester:
|
|
335 |
raise ValueError(f"Invalid JSON response: {str(je)}")
|
336 |
|
337 |
if not plan_json.get("actions"):
|
|
|
338 |
raise ValueError("No valid actions found in plan")
|
339 |
|
340 |
return plan_json
|
@@ -477,8 +478,14 @@ class UITester:
|
|
477 |
)
|
478 |
self.finish_case("interrupted", "Case was interrupted by new case start")
|
479 |
|
|
|
|
|
|
|
|
|
480 |
self.current_case_data = {
|
481 |
-
"name":
|
|
|
|
|
482 |
"start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
483 |
"case_info": case_data or {},
|
484 |
"steps": [],
|
@@ -494,7 +501,7 @@ class UITester:
|
|
494 |
}
|
495 |
self.current_case_steps = []
|
496 |
self.step_counter = 0 # Reset step counter
|
497 |
-
logging.debug(f"Started tracking case: {
|
498 |
|
499 |
def add_step_data(self, step_data: Dict[str, Any], step_type: str = "action"):
|
500 |
"""Add step data to current case."""
|
@@ -547,6 +554,7 @@ class UITester:
|
|
547 |
return
|
548 |
|
549 |
case_name = self.current_case_data.get("name", "Unknown")
|
|
|
550 |
steps_count = len(self.current_case_steps)
|
551 |
|
552 |
# Get monitoring data
|
@@ -624,7 +632,7 @@ class UITester:
|
|
624 |
total_steps = 0
|
625 |
for i, case in enumerate(self.all_cases_data):
|
626 |
case_steps = case.get("steps", [])
|
627 |
-
case_name = case.get("name", f"Case_{i}")
|
628 |
total_steps += len(case_steps)
|
629 |
logging.debug(
|
630 |
f"Report validation - Case '{case_name}': {len(case_steps)} steps, status: {case.get('status', 'unknown')}"
|
|
|
335 |
raise ValueError(f"Invalid JSON response: {str(je)}")
|
336 |
|
337 |
if not plan_json.get("actions"):
|
338 |
+
logging.error(f"No valid actions found in plan: {test_plan}")
|
339 |
raise ValueError("No valid actions found in plan")
|
340 |
|
341 |
return plan_json
|
|
|
478 |
)
|
479 |
self.finish_case("interrupted", "Case was interrupted by new case start")
|
480 |
|
481 |
+
# Calculate case index (1-based)
|
482 |
+
case_index = len(self.all_cases_data) + 1
|
483 |
+
formatted_case_name = f"{case_index}: {case_name}"
|
484 |
+
|
485 |
self.current_case_data = {
|
486 |
+
"name": formatted_case_name,
|
487 |
+
"original_name": case_name, # Keep original name for reference
|
488 |
+
"case_index": case_index,
|
489 |
"start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
490 |
"case_info": case_data or {},
|
491 |
"steps": [],
|
|
|
501 |
}
|
502 |
self.current_case_steps = []
|
503 |
self.step_counter = 0 # Reset step counter
|
504 |
+
logging.debug(f"Started tracking case: {formatted_case_name} (step counter reset)")
|
505 |
|
506 |
def add_step_data(self, step_data: Dict[str, Any], step_type: str = "action"):
|
507 |
"""Add step data to current case."""
|
|
|
554 |
return
|
555 |
|
556 |
case_name = self.current_case_data.get("name", "Unknown")
|
557 |
+
original_name = self.current_case_data.get("original_name", case_name)
|
558 |
steps_count = len(self.current_case_steps)
|
559 |
|
560 |
# Get monitoring data
|
|
|
632 |
total_steps = 0
|
633 |
for i, case in enumerate(self.all_cases_data):
|
634 |
case_steps = case.get("steps", [])
|
635 |
+
case_name = case.get("name", f"Case_{i + 1}") # Use 1-based indexing as fallback
|
636 |
total_steps += len(case_steps)
|
637 |
logging.debug(
|
638 |
f"Report validation - Case '{case_name}': {len(case_steps)} steps, status: {case.get('status', 'unknown')}"
|