Files
ai-web-tester/tests/test_cases.py
empty c6def51435
Some checks failed
AI Web Tester CI / test (push) Has been cancelled
feat: 添加AI主动探索测试模式
新增功能:
- explorer.py: AI功能探索器
  - 自动发现页面可交互元素
  - 元素分类 (navigation/button/link/card/menu)
  - 危险操作保护 (删除/退出只记录不执行)
  - DOM快速定位替代AI定位 (速度提升10x)
  - 站点地图和BUG清单生成

- main.py: 添加 explore() 方法
- generator.py: 添加探索报告生成 (暗色主题+Mermaid站点图)
- test_cases.py: 支持 goal/explore/hybrid 三种模式

测试结果:
- 成功发现30个可交互元素
- 自动分类: Links(11), Navigation(8), Cards(8), Buttons(2), Menu(1)
- 生成完整HTML探索报告
2025-12-28 20:39:15 +08:00

294 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
测试用例模板 - 快速设计和运行多个测试(支持并行执行)
"""
import sys
sys.path.insert(0, ".")
from src import WebTester
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List, Dict, Any
import time
# ============================================================
# 测试用例配置
# ============================================================
TEST_CASES = [
# 目标模式: 执行指定目标
{
"name": "登录",
"url": "http://47.99.105.253:8084",
"mode": "goal", # 目标驱动模式
"goal": "填入账号admin 密码password登录成功",
},
# 探索模式: AI 自主发现功能
{
"name": "功能探索",
"url": "http://47.99.105.253:8084",
"mode": "explore", # 探索模式
"config": {
"max_depth": 3,
"max_clicks": 30,
"dangerous_patterns": ["删除", "移除", "退出", "注销"], # 记录但不执行
"require_login": { # 需要先登录
"goal": "填入账号admin 密码password登录成功"
}
}
},
# 混合模式: 先执行目标,再探索
# {
# "name": "登录后探索",
# "url": "http://47.99.105.253:8084",
# "mode": "hybrid",
# "steps": [
# {"action": "goal", "goal": "登录"},
# {"action": "explore", "config": {"max_clicks": 10}}
# ]
# },
]
# ============================================================
# 测试执行器
# ============================================================
def run_single_case(case: Dict[str, Any], model: str = "claude",
headless: bool = True) -> Dict[str, Any]:
"""运行单个测试用例(独立浏览器实例)"""
name = case.get("name", "Unknown")
url = case["url"]
mode = case.get("mode", "goal")
result = {
"name": name,
"url": url,
"mode": mode,
"status": "failed",
}
try:
with WebTester(model=model, headless=headless) as tester:
tester.goto(url)
if mode == "goal":
# 目标模式
goal = case.get("goal", "")
test_result = tester.test(goal)
result["status"] = "passed"
result["steps"] = test_result["steps"]
result["report"] = test_result["report"]
elif mode == "explore":
# 探索模式
config = case.get("config", {})
# 如果需要先登录
require_login = config.pop("require_login", None)
if require_login:
login_goal = require_login.get("goal", "")
if login_goal:
tester.test(login_goal)
tester.browser.wait(1000)
# 执行探索
explore_result = tester.explore(config)
result["status"] = "passed"
result["elements"] = len(explore_result.get("discovered_elements", []))
result["bugs"] = len(explore_result.get("bug_list", []))
result["report"] = explore_result.get("report", "")
elif mode == "hybrid":
# 混合模式
for step in case.get("steps", []):
if step.get("action") == "goal":
tester.test(step["goal"])
elif step.get("action") == "explore":
tester.explore(step.get("config", {}))
result["status"] = "passed"
except Exception as e:
result["error"] = str(e)
return result
def run_tests(model: str = "claude", headless: bool = False):
"""串行运行所有测试用例"""
results = []
with WebTester(model=model, headless=headless) as tester:
for i, case in enumerate(TEST_CASES, 1):
name = case.get("name", f"Test {i}")
url = case["url"]
mode = case.get("mode", "goal")
print(f"\n{'='*60}")
print(f"🧪 [{i}/{len(TEST_CASES)}] {name}")
print(f" URL: {url}")
print(f" Mode: {mode}")
print(f"{'='*60}")
try:
tester.goto(url)
if mode == "goal":
goal = case.get("goal", "")
result = tester.test(goal)
# 检查所有步骤是否成功
all_passed = all(r.get("success", False) for r in result.get("results", []))
failed_count = sum(1 for r in result.get("results", []) if not r.get("success", False))
if all_passed:
print(f"✅ 完成: {result['steps']} 步骤")
status = "passed"
else:
print(f"⚠️ 部分失败: {failed_count}/{result['steps']} 步骤失败")
status = "failed"
print(f"📄 报告: {result['report']}")
results.append({
"name": name,
"status": status,
"steps": result["steps"],
"report": result["report"],
})
elif mode == "explore":
config = case.get("config", {}).copy()
# 如果需要先登录
require_login = config.pop("require_login", None)
if require_login:
login_goal = require_login.get("goal", "")
if login_goal:
print(f" 🔐 执行登录...")
tester.test(login_goal)
tester.browser.wait(1000)
# 执行探索
print(f" 🔍 开始功能探索...")
result = tester.explore(config)
elements = len(result.get("discovered_elements", []))
bugs = len(result.get("bug_list", []))
print(f"✅ 探索完成: 发现 {elements} 个元素, {bugs} 个问题")
print(f"📄 报告: {result.get('report', '')}")
results.append({
"name": name,
"status": "passed",
"elements": elements,
"bugs": bugs,
"report": result.get("report", ""),
})
except Exception as e:
print(f"❌ 失败: {e}")
results.append({
"name": name,
"status": "failed",
"error": str(e),
})
_print_summary(results)
return results
def run_tests_parallel(model: str = "claude", max_workers: int = 3):
"""
并行运行所有测试用例
Args:
model: AI 模型
max_workers: 最大并行数(默认 3
"""
print(f"\n🚀 并行模式启动 (workers={max_workers})")
print(f"📋 待执行测试: {len(TEST_CASES)}\n")
results = []
start_time = time.time()
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任务
future_to_case = {
executor.submit(run_single_case, case, model, True): case
for case in TEST_CASES
}
# 收集结果
for future in as_completed(future_to_case):
case = future_to_case[future]
try:
result = future.result()
status = "" if result["status"] == "passed" else ""
print(f"{status} {result['name']}")
results.append(result)
except Exception as e:
print(f"{case['name']}: {e}")
results.append({
"name": case["name"],
"status": "failed",
"error": str(e),
})
elapsed = time.time() - start_time
print(f"\n⏱️ 总耗时: {elapsed:.1f}")
_print_summary(results)
return results
def _print_summary(results: List[Dict[str, Any]]):
"""打印测试总结"""
print(f"\n{'='*60}")
print("📊 测试总结")
print(f"{'='*60}")
passed = sum(1 for r in results if r["status"] == "passed")
failed = len(results) - passed
print(f"✅ 通过: {passed}")
print(f"❌ 失败: {failed}")
if results:
print(f"📈 通过率: {passed/len(results)*100:.1f}%")
def run_single_test(url: str, goal: str, model: str = "claude"):
"""运行单个测试"""
with WebTester(model=model) as tester:
tester.goto(url)
result = tester.test(goal)
print(f"✅ 完成: {result['steps']} 步骤")
print(f"📄 报告: {result['report']}")
return result
# ============================================================
# 主入口
# ============================================================
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="AI Web Tester - 测试用例运行器")
parser.add_argument("--url", help="单个测试的 URL")
parser.add_argument("--goal", help="单个测试的目标描述")
parser.add_argument("--model", default="claude", choices=["claude", "openai"], help="AI 模型")
parser.add_argument("--headless", action="store_true", help="无头模式运行")
parser.add_argument("--parallel", action="store_true", help="并行执行测试")
parser.add_argument("--workers", type=int, default=3, help="并行工作线程数")
args = parser.parse_args()
if args.url and args.goal:
run_single_test(args.url, args.goal, args.model)
elif args.parallel:
run_tests_parallel(model=args.model, max_workers=args.workers)
else:
run_tests(model=args.model, headless=args.headless)