feat: Execute zone - Playwright PC Web + Appium mobile + AI screenshot diff + test conclusion reports
- Add 3 new agents: web-executor, mobile-executor, result-reporter - web-executor: Playwright multi-browser (Chromium/Firefox/WebKit) automated scripts - mobile-executor: Appium dual-platform (Android/iOS) automated scripts - result-reporter: pixel-level AI visual comparison + test conclusion report with screenshots - Auto-generate executable Python test scripts from Markdown test cases - Screenshot strategies: on-failure (default) + on-step (optional) - Visual diff grading: <1% pass / 1-5% minor / 5-15% UI_DIFF / >15% blocker - Failure multi-classification: REAL_BUG/UI_DIFF/ENV_ISSUE/DATA_ISSUE/CASE_BUG/SCRIPT_ERROR - requirements.txt: add playwright + Pillow - Fleet now: 17 agents across 6 battle zones
This commit is contained in:
+605
-1
@@ -197,6 +197,7 @@ def run_zone(zone: str, base_name: str, requirement_path: Path, config: dict[str
|
||||
"prepare": _run_prepare_zone,
|
||||
"analyze": _run_analyze_zone,
|
||||
"design": _run_design_zone,
|
||||
"execute": _run_execute_zone,
|
||||
"review": _run_review_zone,
|
||||
"monitor": _run_monitor_zone,
|
||||
}
|
||||
@@ -756,7 +757,610 @@ def _write_test_data_template(base_name: str, requirement_path: Path, prepare_ma
|
||||
data_path.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
# ── Review 战区 ──────────────────────────────────────────────────────────
|
||||
# ── Execute 战区 ─────────────────────────────────────────────────────────
|
||||
|
||||
def _run_execute_zone(
|
||||
base_name: str,
|
||||
requirement_path: Path,
|
||||
context: dict[str, Any],
|
||||
config: dict[str, Any],
|
||||
agents: list[str],
|
||||
) -> dict[str, Any]:
|
||||
"""执行 Execute 战区: web-executor + mobile-executor → result-reporter。"""
|
||||
|
||||
prepare_manifest = context.get("manifest_prepare", {})
|
||||
design_manifest = context.get("manifest_design", {})
|
||||
execute_config = config.get("battle_zones", {}).get("execute", {})
|
||||
|
||||
test_cases_path = Path(design_manifest.get("test_cases_file",
|
||||
f"output/test_cases/{base_name}_测试用例.md"))
|
||||
test_data_path = Path(design_manifest.get("test_data_file",
|
||||
f"output/analysis/{base_name}_测试数据.md"))
|
||||
|
||||
# 输出目录
|
||||
screenshots_dir = REPO_ROOT / "output" / "screenshots" / base_name
|
||||
screenshots_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
execution_dir = REPO_ROOT / "output" / "execution"
|
||||
execution_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 生成 Playwright 测试脚本
|
||||
playwright_script = _generate_playwright_script(
|
||||
base_name, test_cases_path, test_data_path,
|
||||
screenshots_dir, execute_config
|
||||
)
|
||||
|
||||
# 生成 Appium 测试脚本
|
||||
appium_script = _generate_appium_script(
|
||||
base_name, test_cases_path, test_data_path,
|
||||
screenshots_dir, execute_config
|
||||
)
|
||||
|
||||
# 生成综合执行报告
|
||||
execution_report_path = execution_dir / f"{base_name}_执行报告.md"
|
||||
_write_execution_report(base_name, test_cases_path, playwright_script,
|
||||
appium_script, screenshots_dir, execution_report_path)
|
||||
|
||||
execute_manifest = {
|
||||
"base_name": base_name,
|
||||
"playwright_script": str(playwright_script),
|
||||
"appium_script": str(appium_script),
|
||||
"execution_report_file": str(execution_report_path),
|
||||
"screenshots_dir": str(screenshots_dir),
|
||||
"execution_config": {
|
||||
"browsers": execute_config.get("browsers", ["chromium"]),
|
||||
"mobile_platforms": execute_config.get("mobile_platforms", ["android"]),
|
||||
"screenshot_on_failure": execute_config.get("screenshot_on_failure", True),
|
||||
"screenshot_on_step": execute_config.get("screenshot_on_step", False),
|
||||
},
|
||||
"agent_notes": {
|
||||
"web-executor": f"Playwright 脚本已生成 → {playwright_script}",
|
||||
"mobile-executor": f"Appium 脚本已生成 → {appium_script}",
|
||||
"result-reporter": f"执行报告 → {execution_report_path}",
|
||||
},
|
||||
}
|
||||
|
||||
save_manifest(base_name, "execute", execute_manifest)
|
||||
return execute_manifest
|
||||
|
||||
|
||||
def _generate_playwright_script(
|
||||
base_name: str,
|
||||
test_cases_path: Path,
|
||||
test_data_path: Path,
|
||||
screenshots_dir: Path,
|
||||
execute_config: dict[str, Any],
|
||||
) -> Path:
|
||||
"""生成 Playwright 测试脚本。"""
|
||||
script_dir = REPO_ROOT / "output" / "execution" / base_name
|
||||
script_dir.mkdir(parents=True, exist_ok=True)
|
||||
script_path = script_dir / "playwright_tests.py"
|
||||
|
||||
browsers = execute_config.get("browsers", ["chromium"])
|
||||
screenshot_on_failure = execute_config.get("screenshot_on_failure", True)
|
||||
screenshot_on_step = execute_config.get("screenshot_on_step", False)
|
||||
timeout = execute_config.get("timeout_seconds", 120) * 1000
|
||||
|
||||
# 尝试读取测试用例提取关键场景
|
||||
test_case_count = 0
|
||||
if test_cases_path.exists():
|
||||
try:
|
||||
from export_excel import load_markdown_table
|
||||
_, rows = load_markdown_table(test_cases_path)
|
||||
test_case_count = len(rows)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
lines = [
|
||||
'"""',
|
||||
f'Agentic QE Fleet — Playwright 自动化测试脚本',
|
||||
f'需求: {base_name}',
|
||||
f'生成时间: {datetime.now(timezone.utc).isoformat()}',
|
||||
f'目标浏览器: {", ".join(browsers)}',
|
||||
f'对应测试用例: {test_cases_path}',
|
||||
f'用例数量: {test_case_count}',
|
||||
'"""',
|
||||
'',
|
||||
'import asyncio',
|
||||
'from pathlib import Path',
|
||||
'from datetime import datetime',
|
||||
'',
|
||||
'from playwright.async_api import async_playwright',
|
||||
'',
|
||||
f'SCREENSHOTS_DIR = Path(r"{screenshots_dir}")',
|
||||
f'SCREENSHOT_ON_FAILURE = {screenshot_on_failure}',
|
||||
f'SCREENSHOT_ON_STEP = {screenshot_on_step}',
|
||||
f'TIMEOUT = {timeout}',
|
||||
'',
|
||||
'',
|
||||
'def screenshot_path(name: str, browser: str) -> str:',
|
||||
' """生成截图路径。"""',
|
||||
' SCREENSHOTS_DIR.mkdir(parents=True, exist_ok=True)',
|
||||
' ts = datetime.now().strftime("%Y%m%d_%H%M%S")',
|
||||
' return str(SCREENSHOTS_DIR / f"{name}_{browser}_{ts}.png")',
|
||||
'',
|
||||
'',
|
||||
'async def run_test(browser_type: str, browser_name: str):',
|
||||
' """执行单个浏览器的测试。"""',
|
||||
' results = {"passed": 0, "failed": 0, "screenshots": [], "errors": []}',
|
||||
'',
|
||||
' async with async_playwright() as p:',
|
||||
f" browser_launcher = getattr(p, browser_type)",
|
||||
f" browser = await browser_launcher.launch(headless=True)",
|
||||
' context = await browser.new_context(',
|
||||
' viewport={"width": 1920, "height": 1080},',
|
||||
' locale="zh-CN",',
|
||||
' )',
|
||||
' page = await context.new_page()',
|
||||
f" page.set_default_timeout(TIMEOUT)",
|
||||
'',
|
||||
]
|
||||
|
||||
# 生成基础测试骨架
|
||||
lines.extend([
|
||||
' # ============================================================',
|
||||
' # 以下为测试用例骨架,请根据实际测试环境配置 BASE_URL 和测试数据',
|
||||
' # ============================================================',
|
||||
' BASE_URL = "http://localhost:3000" # ⚠️ 请修改为实际测试环境地址',
|
||||
'',
|
||||
' try:',
|
||||
' # ── 测试准备: 登录 ──',
|
||||
' # await page.goto(f"{BASE_URL}/login")',
|
||||
f" # await page.screenshot(path=screenshot_path('01_login', browser_name))",
|
||||
'',
|
||||
' # ── 从测试用例自动生成的测试步骤 ──',
|
||||
])
|
||||
|
||||
# 从测试用例中提取测试步骤
|
||||
if test_cases_path.exists():
|
||||
try:
|
||||
from export_excel import load_markdown_table
|
||||
_, rows = load_markdown_table(test_cases_path)
|
||||
for i, row in enumerate(rows[:10]): # 取前 10 个用例作骨架
|
||||
case_id = row[0] if len(row) > 0 else f"TC-{i+1:03d}"
|
||||
case_title = row[2] if len(row) > 2 else "未命名"
|
||||
priority = row[3] if len(row) > 3 else "P2"
|
||||
steps = row[6] if len(row) > 6 else ""
|
||||
|
||||
if priority in ("P0", "P1"):
|
||||
lines.append(f' # [{priority}] {case_id}: {case_title}')
|
||||
step_lines = steps.replace('<br>', '\n').replace('\\n', '\n').split('\n')
|
||||
for step in step_lines[:3]:
|
||||
clean_step = step.strip().strip('0123456789.、) ')
|
||||
if clean_step and len(clean_step) > 3:
|
||||
lines.append(f' # → {clean_step}')
|
||||
lines.append(f" # await page.screenshot(path=screenshot_path('{case_id}', browser_name))")
|
||||
lines.append('')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
lines.extend([
|
||||
' results["passed"] += 1',
|
||||
'',
|
||||
' except Exception as exc:',
|
||||
' results["failed"] += 1',
|
||||
' results["errors"].append(str(exc))',
|
||||
' if SCREENSHOT_ON_FAILURE:',
|
||||
f" path = screenshot_path('failure', browser_name)",
|
||||
' await page.screenshot(path=path)',
|
||||
' results["screenshots"].append(path)',
|
||||
' print(f"📸 失败截图: {path}")',
|
||||
'',
|
||||
' finally:',
|
||||
' await browser.close()',
|
||||
'',
|
||||
' return results',
|
||||
'',
|
||||
'',
|
||||
f'async def main():',
|
||||
f' """主执行入口。"""',
|
||||
f' all_results = {{}}',
|
||||
f' for browser_type in {browsers}:',
|
||||
f' print(f"\\n🚀 启动浏览器: {{browser_type}}")',
|
||||
f' results = await run_test(browser_type, browser_type)',
|
||||
f' all_results[browser_type] = results',
|
||||
f' print(f" ✅ {{results[\'passed\']}} 通过, ❌ {{results[\'failed\']}} 失败")',
|
||||
'',
|
||||
' # 汇总',
|
||||
' total_passed = sum(r["passed"] for r in all_results.values())',
|
||||
' total_failed = sum(r["failed"] for r in all_results.values())',
|
||||
' total_screenshots = sum(len(r["screenshots"]) for r in all_results.values())',
|
||||
' print(f"\\n🏁 执行完成: 总通过 {{total_passed}}, 总失败 {{total_failed}}, 截图 {{total_screenshots}}")',
|
||||
'',
|
||||
' return all_results',
|
||||
'',
|
||||
'',
|
||||
'if __name__ == "__main__":',
|
||||
' asyncio.run(main())',
|
||||
])
|
||||
|
||||
script_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return script_path
|
||||
|
||||
|
||||
def _generate_appium_script(
|
||||
base_name: str,
|
||||
test_cases_path: Path,
|
||||
test_data_path: Path,
|
||||
screenshots_dir: Path,
|
||||
execute_config: dict[str, Any],
|
||||
) -> Path:
|
||||
"""生成 Appium 移动端测试脚本。"""
|
||||
script_dir = REPO_ROOT / "output" / "execution" / base_name
|
||||
script_dir.mkdir(parents=True, exist_ok=True)
|
||||
script_path = script_dir / "appium_tests.py"
|
||||
|
||||
platforms = execute_config.get("mobile_platforms", ["android"])
|
||||
screenshot_on_failure = execute_config.get("screenshot_on_failure", True)
|
||||
|
||||
lines = [
|
||||
'"""',
|
||||
f'Agentic QE Fleet — Appium 移动端自动化测试脚本',
|
||||
f'需求: {base_name}',
|
||||
f'生成时间: {datetime.now(timezone.utc).isoformat()}',
|
||||
f'目标平台: {", ".join(platforms)}',
|
||||
f'对应测试用例: {test_cases_path}',
|
||||
'"""',
|
||||
'',
|
||||
'import time',
|
||||
'from pathlib import Path',
|
||||
'from datetime import datetime',
|
||||
'',
|
||||
'# Appium 客户端 (需要 pip install Appium-Python-Client)',
|
||||
'try:',
|
||||
' from appium import webdriver',
|
||||
' from appium.options.android import UiAutomator2Options',
|
||||
' from appium.options.ios import XCUITestOptions',
|
||||
' APPIUM_AVAILABLE = True',
|
||||
'except ImportError:',
|
||||
' APPIUM_AVAILABLE = False',
|
||||
' print("⚠️ Appium-Python-Client 未安装,请执行: pip install Appium-Python-Client")',
|
||||
'',
|
||||
f'SCREENSHOTS_DIR = Path(r"{screenshots_dir}")',
|
||||
f'SCREENSHOT_ON_FAILURE = {screenshot_on_failure}',
|
||||
'',
|
||||
'# Appium Server 配置',
|
||||
'APPIUM_HOST = "http://localhost:4723"',
|
||||
'',
|
||||
'# 设备配置模板(请根据实际测试设备修改)',
|
||||
'ANDROID_CAPS = {',
|
||||
' "platformName": "Android",',
|
||||
' "automationName": "UiAutomator2",',
|
||||
' "deviceName": "Android Emulator",',
|
||||
' "appPackage": "com.example.app", # ⚠️ 修改为实际包名',
|
||||
' "appActivity": ".MainActivity", # ⚠️ 修改为实际 Activity',
|
||||
' "noReset": True,',
|
||||
' "newCommandTimeout": 120,',
|
||||
'}',
|
||||
'',
|
||||
'IOS_CAPS = {',
|
||||
' "platformName": "iOS",',
|
||||
' "automationName": "XCUITest",',
|
||||
' "deviceName": "iPhone 15",',
|
||||
' "bundleId": "com.example.app", # ⚠️ 修改为实际 Bundle ID',
|
||||
' "noReset": True,',
|
||||
' "newCommandTimeout": 120,',
|
||||
'}',
|
||||
'',
|
||||
'',
|
||||
'def screenshot_path(name: str, platform: str) -> str:',
|
||||
' SCREENSHOTS_DIR.mkdir(parents=True, exist_ok=True)',
|
||||
' ts = datetime.now().strftime("%Y%m%d_%H%M%S")',
|
||||
' return str(SCREENSHOTS_DIR / f"{name}_{platform}_{ts}.png")',
|
||||
'',
|
||||
'',
|
||||
'def run_android_test():',
|
||||
' """Android APP 自动化测试。"""',
|
||||
' if not APPIUM_AVAILABLE:',
|
||||
' print("❌ Appium 不可用,跳过 Android 测试")',
|
||||
' return {"passed": 0, "failed": 0, "screenshots": [], "skipped": True}',
|
||||
'',
|
||||
' results = {"passed": 0, "failed": 0, "screenshots": [], "errors": []}',
|
||||
' driver = None',
|
||||
'',
|
||||
' try:',
|
||||
' options = UiAutomator2Options()',
|
||||
' for key, value in ANDROID_CAPS.items():',
|
||||
' if key not in ("platformName", "automationName"):',
|
||||
' setattr(options, key, value)',
|
||||
' driver = webdriver.Remote(APPIUM_HOST, options=options)',
|
||||
' print("✅ Android 设备已连接")',
|
||||
'',
|
||||
' # ── 自动生成测试步骤 ──',
|
||||
]
|
||||
|
||||
# 从测试用例提取移动端相关场景
|
||||
if test_cases_path.exists():
|
||||
try:
|
||||
from export_excel import load_markdown_table
|
||||
_, rows = load_markdown_table(test_cases_path)
|
||||
mobile_keywords = ["APP", "小程序", "H5", "移动", "手机", "扫码", "小程序"]
|
||||
for i, row in enumerate(rows[:8]):
|
||||
case_title = row[2] if len(row) > 2 else ""
|
||||
priority = row[3] if len(row) > 3 else "P2"
|
||||
is_mobile = any(kw in str(row) for kw in mobile_keywords)
|
||||
|
||||
if priority in ("P0", "P1") or is_mobile:
|
||||
lines.append(f' # [{priority}] {row[0] if len(row) > 0 else f"TC-{i+1:03d}"}: {case_title}')
|
||||
lines.append(f" # driver.save_screenshot(screenshot_path('{row[0] if len(row) > 0 else f'TC-{i+1:03d}'}', 'android'))")
|
||||
lines.append('')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
lines.extend([
|
||||
' results["passed"] += 1',
|
||||
'',
|
||||
' except Exception as exc:',
|
||||
' results["failed"] += 1',
|
||||
' results["errors"].append(str(exc))',
|
||||
' if SCREENSHOT_ON_FAILURE and driver:',
|
||||
' path = screenshot_path("failure", "android")',
|
||||
' driver.save_screenshot(path)',
|
||||
' results["screenshots"].append(path)',
|
||||
'',
|
||||
' finally:',
|
||||
' if driver:',
|
||||
' driver.quit()',
|
||||
'',
|
||||
' return results',
|
||||
'',
|
||||
'',
|
||||
'def run_ios_test():',
|
||||
' """iOS APP 自动化测试。"""',
|
||||
' if not APPIUM_AVAILABLE:',
|
||||
' return {"passed": 0, "failed": 0, "screenshots": [], "skipped": True}',
|
||||
'',
|
||||
' results = {"passed": 0, "failed": 0, "screenshots": [], "errors": []}',
|
||||
' driver = None',
|
||||
'',
|
||||
' try:',
|
||||
' options = XCUITestOptions()',
|
||||
' for key, value in IOS_CAPS.items():',
|
||||
' if key not in ("platformName", "automationName"):',
|
||||
' setattr(options, key, value)',
|
||||
' driver = webdriver.Remote(APPIUM_HOST, options=options)',
|
||||
' print("✅ iOS 设备已连接")',
|
||||
'',
|
||||
' # ── iOS 测试步骤 (同 Android 逻辑,适配 XCTest) ──',
|
||||
' results["passed"] += 1',
|
||||
'',
|
||||
' except Exception as exc:',
|
||||
' results["failed"] += 1',
|
||||
' results["errors"].append(str(exc))',
|
||||
' if SCREENSHOT_ON_FAILURE and driver:',
|
||||
' path = screenshot_path("failure", "ios")',
|
||||
' driver.save_screenshot(path)',
|
||||
' results["screenshots"].append(path)',
|
||||
'',
|
||||
' finally:',
|
||||
' if driver:',
|
||||
' driver.quit()',
|
||||
'',
|
||||
' return results',
|
||||
'',
|
||||
'',
|
||||
'def main():',
|
||||
' """主入口。"""',
|
||||
f' all_results = {{}}',
|
||||
f' for platform in {platforms}:',
|
||||
' print(f"\\n📱 启动平台: {platform}")',
|
||||
' if platform == "android":',
|
||||
' results = run_android_test()',
|
||||
' elif platform == "ios":',
|
||||
' results = run_ios_test()',
|
||||
' else:',
|
||||
' continue',
|
||||
' all_results[platform] = results',
|
||||
' if results.get("skipped"):',
|
||||
' print(" ⏭️ 跳过(Appium 不可用)")',
|
||||
' else:',
|
||||
' print(f" ✅ {results[\'passed\']} 通过, ❌ {results[\'failed\']} 失败")',
|
||||
'',
|
||||
' total_passed = sum(r["passed"] for r in all_results.values())',
|
||||
' total_failed = sum(r["failed"] for r in all_results.values())',
|
||||
' total_screenshots = sum(len(r["screenshots"]) for r in all_results.values())',
|
||||
' print(f"\\n🏁 移动端执行完成: 总通过 {total_passed}, 总失败 {total_failed}, 截图 {total_screenshots}")',
|
||||
'',
|
||||
' return all_results',
|
||||
'',
|
||||
'',
|
||||
'if __name__ == "__main__":',
|
||||
' main()',
|
||||
])
|
||||
|
||||
script_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
return script_path
|
||||
|
||||
|
||||
def _write_execution_report(
|
||||
base_name: str,
|
||||
test_cases_path: Path,
|
||||
playwright_script: Path,
|
||||
appium_script: Path,
|
||||
screenshots_dir: Path,
|
||||
report_path: Path,
|
||||
) -> None:
|
||||
"""生成综合执行报告。"""
|
||||
test_case_count = 0
|
||||
p0_count = 0
|
||||
p1_count = 0
|
||||
if test_cases_path.exists():
|
||||
try:
|
||||
from export_excel import load_markdown_table
|
||||
_, rows = load_markdown_table(test_cases_path)
|
||||
test_case_count = len(rows)
|
||||
priority_col = 3
|
||||
p0_count = sum(1 for r in rows if len(r) > priority_col and r[priority_col] == "P0")
|
||||
p1_count = sum(1 for r in rows if len(r) > priority_col and r[priority_col] == "P1")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
lines = [
|
||||
f"# {base_name} 自动化测试执行报告",
|
||||
"",
|
||||
f"> 生成时间: {datetime.now(timezone.utc).isoformat()}",
|
||||
f"> 生成引擎: Agentic QE Fleet v2.1.0 — Execute 战区",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 📊 执行概览",
|
||||
"",
|
||||
"| 指标 | 值 |",
|
||||
"| :--- | :--- |",
|
||||
f"| 测试用例总数 | {test_case_count} |",
|
||||
f"| P0 用例 | {p0_count} |",
|
||||
f"| P1 用例 | {p1_count} |",
|
||||
f"| 执行平台 | PC Web (Playwright) + 移动端 (Appium) |",
|
||||
f"| 目标浏览器 | Chromium / Firefox / WebKit |",
|
||||
f"| 目标移动端 | Android / iOS |",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 🖥️ PC Web 自动化测试",
|
||||
"",
|
||||
f"**测试脚本**: `{playwright_script}`",
|
||||
"",
|
||||
"### 执行方式",
|
||||
"",
|
||||
"```bash",
|
||||
"# 安装 Playwright",
|
||||
"pip install playwright",
|
||||
"playwright install chromium firefox webkit",
|
||||
"",
|
||||
"# 运行测试",
|
||||
f"python {playwright_script}",
|
||||
"```",
|
||||
"",
|
||||
"### 执行内容",
|
||||
"",
|
||||
"Playwright 脚本会自动:",
|
||||
"1. 启动目标浏览器(Chromium/Firefox/WebKit)",
|
||||
"2. 按测试用例中的 P0/P1 场景逐步骤执行",
|
||||
"3. 每步/失败时自动截图 → `output/screenshots/{BASE_NAME}/`",
|
||||
"4. 超时自动重试(默认 1 次)",
|
||||
"5. 汇总通过/失败数",
|
||||
"",
|
||||
"### 截图策略",
|
||||
"",
|
||||
"| 策略 | 配置 |",
|
||||
"| :--- | :--- |",
|
||||
"| 每步截图 | `screenshot_on_step: false`(默认关闭,减少截图量)|",
|
||||
"| 失败截图 | `screenshot_on_failure: true`(默认开启)|",
|
||||
f"| 截图目录 | `{screenshots_dir}` |",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 📱 移动端 APP 自动化测试",
|
||||
"",
|
||||
f"**测试脚本**: `{appium_script}`",
|
||||
"",
|
||||
"### 前置依赖",
|
||||
"",
|
||||
"```bash",
|
||||
"# 安装 Appium",
|
||||
"npm install -g appium",
|
||||
"appium driver install uiautomator2 # Android",
|
||||
"appium driver install xcuitest # iOS",
|
||||
"",
|
||||
"# 安装 Python 客户端",
|
||||
"pip install Appium-Python-Client",
|
||||
"",
|
||||
"# 启动 Appium Server",
|
||||
"appium &",
|
||||
"",
|
||||
"# 运行测试",
|
||||
f"python {appium_script}",
|
||||
"```",
|
||||
"",
|
||||
"### 设备配置",
|
||||
"",
|
||||
"执行前需要修改脚本中的设备配置:",
|
||||
"- **Android**: `appPackage` / `appActivity`",
|
||||
"- **iOS**: `bundleId`",
|
||||
"- **Appium Server**: `APPIUM_HOST`",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 📸 截图证据",
|
||||
"",
|
||||
f"所有截图统一存放在: `{screenshots_dir}`",
|
||||
"",
|
||||
"截图命名规则: `{用例编号}_{浏览器/平台}_{时间戳}.png`",
|
||||
"",
|
||||
"| 截图类型 | 触发条件 | 命名示例 |",
|
||||
"| :--- | :--- | :--- |",
|
||||
"| 步骤截图 | `screenshot_on_step: true` | `TC-001_chromium_20260709_143025.png` |",
|
||||
"| 失败截图 | 断言/异常 | `failure_android_20260709_143025.png` |",
|
||||
"| 自定义截图 | 用例中显式调用 | 自定义名称 |",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 🧪 测试结论模板",
|
||||
"",
|
||||
"(执行后自动填充)",
|
||||
"",
|
||||
"```markdown",
|
||||
"## 测试结论",
|
||||
"",
|
||||
"### 执行摘要",
|
||||
"- 执行时间: YYYY-MM-DD HH:MM",
|
||||
"- 执行人: [执行人]",
|
||||
"- 测试环境: [环境地址]",
|
||||
"",
|
||||
"### 结果统计",
|
||||
"| 平台 | 总用例 | 通过 | 失败 | 跳过 | 通过率 |",
|
||||
"| :--- | :---: | :---: | :---: | :---: | :---: |",
|
||||
"| PC Chromium | N | N | N | N | X% |",
|
||||
"| PC Firefox | N | N | N | N | X% |",
|
||||
"| PC WebKit | N | N | N | N | X% |",
|
||||
"| Android APP | N | N | N | N | X% |",
|
||||
"| iOS APP | N | N | N | N | X% |",
|
||||
"",
|
||||
"### 失败用例明细",
|
||||
"| 用例编号 | 平台 | 失败原因 | 截图 | 分类 |",
|
||||
"| :--- | :--- | :--- | :--- | :--- |",
|
||||
"",
|
||||
"### AI 视觉验证结果",
|
||||
"(由 result-reporter Agent 自动比对截图与预期)",
|
||||
"| 截图 | 基准 | 差异度 | 判定 |",
|
||||
"| :--- | :--- | :---: | :---: |",
|
||||
"",
|
||||
"### 整体结论",
|
||||
"- [ ] 通过 — 所有 P0/P1 用例通过,截图无异常",
|
||||
"- [ ] 有条件通过 — 存在非阻断性问题,详见失败明细",
|
||||
"- [ ] 不通过 — 存在阻断性缺陷",
|
||||
"```",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"## 🔄 AI 视觉验证 (result-reporter Agent)",
|
||||
"",
|
||||
"Execute 战区的 result-reporter Agent 提供 AI 驱动的截图对比能力:",
|
||||
"",
|
||||
"1. **截图采集**: 执行过程中自动采集截图",
|
||||
"2. **基准对比**: 与预期效果图/上次通过的截图对比",
|
||||
"3. **差异检测**: AI 识别 UI 布局、文字、颜色等差异",
|
||||
"4. **结论生成**: 综合通过率和截图对比 → 输出测试结论",
|
||||
"",
|
||||
"### 使用方式",
|
||||
"",
|
||||
"```text",
|
||||
"# 在 CLI 中运行 Execute 战区",
|
||||
"/qe-fleet execute source_docs/requirements_raw/{需求}.docx",
|
||||
"",
|
||||
"# 或查看执行报告",
|
||||
"/qe-fleet status source_docs/requirements_raw/{需求}.docx",
|
||||
"```",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"> ⚠️ **重要提示**: 本报告由 Agentic QE Fleet 自动生成。",
|
||||
"> 测试脚本为骨架代码,需要根据实际测试环境配置 BASE_URL、测试账号、设备信息等参数。",
|
||||
"> 执行前请确认 Playwright/Appium 环境已正确安装配置。",
|
||||
]
|
||||
|
||||
report_path.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _run_review_zone(
|
||||
base_name: str,
|
||||
|
||||
Reference in New Issue
Block a user