feat: 实现AI驱动的Web自动化测试框架
Some checks failed
AI Web Tester CI / test (push) Has been cancelled

主要功能:
- 纯视觉元素定位 + DOM辅助的混合方案
- 解决 mouse.click() 与 Vue 页面交互问题
- 使用 elementFromPoint + JS click/focus 实现可靠点击
- 智能元素定位: 根据描述生成CSS选择器获取精确坐标
- 区域扫描作为后备定位方案
- 完整的测试报告生成 (HTML+JSON)
- 截图记录每个操作步骤

技术改进:
- controller.py: 改进 click_at 使用 JavaScript 交互
- executor.py: 添加 _find_element_by_description 智能定位
- planner.py: 增强 prompt 传入视口尺寸
- main.py: 获取实际视口大小传给 planner
This commit is contained in:
empty
2025-12-28 15:34:22 +08:00
commit a67ad26a52
24 changed files with 2137 additions and 0 deletions

199
tests/test_cases.py Normal file
View File

@@ -0,0 +1,199 @@
#!/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": "Example.com 链接测试",
"url": "http://47.99.105.253:8084",
"goal": "填入账号admin 密码password登录成功",
},
# 添加更多测试用例...
]
# ============================================================
# 测试执行器
# ============================================================
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"]
goal = case["goal"]
result = {
"name": name,
"url": url,
"goal": goal,
"status": "failed",
}
try:
with WebTester(model=model, headless=headless) as tester:
tester.goto(url)
test_result = tester.test(goal)
result["status"] = "passed"
result["steps"] = test_result["steps"]
result["report"] = test_result["report"]
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"]
goal = case["goal"]
print(f"\n{'='*60}")
print(f"🧪 [{i}/{len(TEST_CASES)}] {name}")
print(f" URL: {url}")
print(f" Goal: {goal}")
print(f"{'='*60}")
try:
tester.goto(url)
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"],
})
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)