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:
xst
2026-07-10 11:03:58 +08:00
parent b2a035c4f9
commit 4f30b9d702
12 changed files with 968 additions and 8 deletions
+6
View File
@@ -34,6 +34,7 @@ python3 scripts/fleet_runner.py run --requirement <需求文档路径>
- **PREPARE**: document-parser(文档解析) → knowledge-activator(知识激活) - **PREPARE**: document-parser(文档解析) → knowledge-activator(知识激活)
- **ANALYZE**: requirement-analyzer(需求分析) + conflict-detector(冲突检测) → risk-assessor(风险评估) - **ANALYZE**: requirement-analyzer(需求分析) + conflict-detector(冲突检测) → risk-assessor(风险评估)
- **DESIGN**: test-strategist(测试策略) → testpoint-designer(测试点) + data-builder(测试数据) → case-designer(用例设计) - **DESIGN**: test-strategist(测试策略) → testpoint-designer(测试点) + data-builder(测试数据) → case-designer(用例设计)
- **EXECUTE**: web-executor(Playwright PC Web) + mobile-executor(Appium 移动端) → result-reporter(截图对比+结论)
- **REVIEW**: case-reviewer(用例评审) + coverage-auditor(覆盖率审计) → quality-gatekeeper(质量裁决) - **REVIEW**: case-reviewer(用例评审) + coverage-auditor(覆盖率审计) → quality-gatekeeper(质量裁决)
- **MONITOR**: execution-analyst(执行分析) → knowledge-curator(知识沉淀) - **MONITOR**: execution-analyst(执行分析) → knowledge-curator(知识沉淀)
- **EXPORT**: 质量裁决 PASS 后自动导出 Excel - **EXPORT**: 质量裁决 PASS 后自动导出 Excel
@@ -81,6 +82,11 @@ python3 scripts/fleet_runner.py monitor --requirement <需求文档路径> --res
- **case-designer**: 可执行用例;原子性;双验证预期结果(UI + 数据) - **case-designer**: 可执行用例;原子性;双验证预期结果(UI + 数据)
- **data-builder**: 自动提取+人工补充测试数据(账号/金额/ID/枚举/边界值) - **data-builder**: 自动提取+人工补充测试数据(账号/金额/ID/枚举/边界值)
### Execute 战区
- **web-executor**: 生成 Playwright 测试脚本;多浏览器(Chromium/Firefox/WebKit);每步/失败自动截图
- **mobile-executor**: 生成 Appium 测试脚本;Android (UiAutomator2) + iOS (XCUITest);双平台截图采集
- **result-reporter**: 截图 AI 视觉对比;像素级差异检测;多平台测试结论报告(含截图证据)
### Review 战区 ### Review 战区
- **case-reviewer**: 按 review_checklist 逐项评审;错误分级(阻断/建议/优化) - **case-reviewer**: 按 review_checklist 逐项评审;错误分级(阻断/建议/优化)
- **coverage-auditor**: 需求→测试点→用例三级追溯;覆盖缺口识别;过度覆盖识别 - **coverage-auditor**: 需求→测试点→用例三级追溯;覆盖缺口识别;过度覆盖识别
+6 -1
View File
@@ -25,7 +25,7 @@
python3 scripts/fleet_runner.py run --requirement <需求文档路径> python3 scripts/fleet_runner.py run --requirement <需求文档路径>
``` ```
3. fleet_runner.py 自动按战区顺序编排 14 个专业 Agent 3. fleet_runner.py 自动按战区顺序编排 17 个专业 Agent
**PREPARE 战区 (2 Agent):** **PREPARE 战区 (2 Agent):**
- document-parser: 文档解析标准化 - document-parser: 文档解析标准化
@@ -42,6 +42,11 @@ python3 scripts/fleet_runner.py run --requirement <需求文档路径>
- case-designer: 用例设计 - case-designer: 用例设计
- data-builder: 测试数据构造 - data-builder: 测试数据构造
**EXECUTE 战区 (3 Agent):**
- web-executor: PC Web Playwright 自动化执行 + 截图
- mobile-executor: 移动端 Appium 自动化执行 + 截图
- result-reporter: AI 视觉对比 + 测试结论报告
**REVIEW 战区 (3 Agent):** **REVIEW 战区 (3 Agent):**
- case-reviewer: 用例评审 - case-reviewer: 用例评审
- coverage-auditor: 覆盖率审计 - coverage-auditor: 覆盖率审计
+9 -1
View File
@@ -83,6 +83,7 @@ Fleet 会自动完成从需求解析到 Excel 导出的全流程。
| `/qe-fleet prepare <文档>` | 仅文档解析 + 知识激活 | | `/qe-fleet prepare <文档>` | 仅文档解析 + 知识激活 |
| `/qe-fleet analyze <文档>` | 准备 → 分析(含冲突检测 + 风险评估) | | `/qe-fleet analyze <文档>` | 准备 → 分析(含冲突检测 + 风险评估) |
| `/qe-fleet design <文档>` | 准备 → 分析 → 设计(含策略 + 用例 + 数据) | | `/qe-fleet design <文档>` | 准备 → 分析 → 设计(含策略 + 用例 + 数据) |
| `/qe-fleet execute <文档>` | 自动化执行(Playwright PC + Appium 移动端 + 截图) |
| `/qe-fleet review <文档>` | 评审已有产物(含覆盖率审计 + 质量裁决) | | `/qe-fleet review <文档>` | 评审已有产物(含覆盖率审计 + 质量裁决) |
| `/qe-fleet export <文档>` | 仅 Excel 导出 + 版本快照 | | `/qe-fleet export <文档>` | 仅 Excel 导出 + 版本快照 |
| `/qe-fleet monitor <文档> --results <结果>` | 执行分析 + 知识沉淀 | | `/qe-fleet monitor <文档> --results <结果>` | 执行分析 + 知识沉淀 |
@@ -92,7 +93,7 @@ Fleet 会自动完成从需求解析到 Excel 导出的全流程。
--- ---
## 14 Agent 舰队 ## 17 Agent 舰队
| 战区 | Agent | 职责 | | 战区 | Agent | 职责 |
|------|-------|------| |------|-------|------|
@@ -105,6 +106,9 @@ Fleet 会自动完成从需求解析到 Excel 导出的全流程。
| | testpoint-designer | 测试点设计 | | | testpoint-designer | 测试点设计 |
| | case-designer | 用例设计 | | | case-designer | 用例设计 |
| | data-builder | 测试数据构造 | | | data-builder | 测试数据构造 |
| **Execute** | web-executor | PC Web Playwright 自动化 + 截图 |
| | mobile-executor | 移动端 Appium 自动化 + 截图 |
| | result-reporter | AI 视觉对比 + 测试结论 |
| **Review** | case-reviewer | 用例评审 | | **Review** | case-reviewer | 用例评审 |
| | coverage-auditor | 覆盖率审计 | | | coverage-auditor | 覆盖率审计 |
| | quality-gatekeeper | 质量门禁裁决 | | | quality-gatekeeper | 质量门禁裁决 |
@@ -126,6 +130,10 @@ Fleet 会自动完成从需求解析到 Excel 导出的全流程。
| 测试点 | `output/test_points/{BASE_NAME}_测试点.md` | | 测试点 | `output/test_points/{BASE_NAME}_测试点.md` |
| 测试用例 | `output/test_cases/{BASE_NAME}_测试用例.md` | | 测试用例 | `output/test_cases/{BASE_NAME}_测试用例.md` |
| 测试数据 | `output/analysis/{BASE_NAME}_测试数据.md` | | 测试数据 | `output/analysis/{BASE_NAME}_测试数据.md` |
| 执行脚本(Web) | `output/execution/{BASE_NAME}/playwright_tests.py` |
| 执行脚本(移动端) | `output/execution/{BASE_NAME}/appium_tests.py` |
| 执行报告(含截图) | `output/execution/{BASE_NAME}_执行报告.md` |
| 截图 | `output/screenshots/{BASE_NAME}/` |
| 评审报告 | `output/analysis/{BASE_NAME}_评审报告.md` | | 评审报告 | `output/analysis/{BASE_NAME}_评审报告.md` |
| 覆盖率审计 | `output/analysis/{BASE_NAME}_覆盖率审计.md` | | 覆盖率审计 | `output/analysis/{BASE_NAME}_覆盖率审计.md` |
| 质量裁决 | `output/analysis/{BASE_NAME}_质量裁决.md` | | 质量裁决 | `output/analysis/{BASE_NAME}_质量裁决.md` |
+83
View File
@@ -0,0 +1,83 @@
---
name: mobile-executor
zone: execute
description: Android/iOS APP Appium 自动化测试执行,跨平台截图采集
tools: Read, Write, Glob, Bash
depends_on: ["case-designer", "data-builder"]
produces: ["output/execution/{{BASE_NAME}}/appium_tests.py", "output/screenshots/{{BASE_NAME}}/"]
---
# Role
你是一名移动端测试自动化专家,精通 Appium,熟悉 Android (UiAutomator2) 和 iOS (XCUITest) 双平台自动化。
# Task
1. 读取 `output/test_cases/{{BASE_NAME}}_测试用例.md`(识别移动端相关用例)
2. 读取 `output/analysis/{{BASE_NAME}}_测试数据.md`
3. 读取 `{{FLEET_CONFIG}}`(获取移动端平台列表和截图策略)
4. 读取 `{{PROJECT_PROFILE}}`(获取 APP 包名/Bundle ID、测试设备信息)
5. 生成可执行的 Appium 测试脚本
6. 采集截图
# 移动端用例识别
从测试用例中通过以下关键词识别移动端相关用例:
- 模块字段包含: APP / 小程序 / H5 / 移动端
- 用例标题包含: 扫码、摇一摇、推送、定位、拍照、横屏
- 前置条件包含: 手机、APP、小程序
非移动端用例自动跳过。
# 双平台策略
## Android (UiAutomator2)
- 查找元素: `driver.find_element(AppiumBy.ID, "id")`
- 点击: `element.click()`
- 输入: `element.send_keys(text)`
- 截图: `driver.save_screenshot(path)`
- 滑动: `driver.swipe(start_x, start_y, end_x, end_y, duration)`
- 等待: `WebDriverWait(driver, timeout).until(...)`
## iOS (XCUITest)
- 查找元素: `driver.find_element(AppiumBy.ACCESSIBILITY_ID, "id")`
- 点击: `element.click()`
- 输入: `element.send_keys(text)`
- 截图: `driver.save_screenshot(path)`
- 滑动: `driver.swipe(start_x, start_y, end_x, end_y, duration)`
# 截图策略
- **失败截图** (默认开启): 断言失败/异常时立即截图
- **关键页面截图**: 登录后首页、核心操作页、结果页
- **状态变化截图**: 状态流转前后各截一张
- 截图命名: `{用例编号}_{平台}_{步骤描述}_{时间戳}.png`
# 设备配置模板
脚本中自动包含以下可配置项(需要人工修改):
```python
# ⚠️ 以下配置需要根据实际测试环境修改
APPIUM_HOST = "http://localhost:4723"
ANDROID_CAPS = {
"deviceName": "Android Emulator", # 设备名
"appPackage": "com.example.app", # 应用包名 ⚠️ 必改
"appActivity": ".MainActivity", # 启动 Activity ⚠️ 必改
"noReset": True, # 不重置应用数据
}
IOS_CAPS = {
"deviceName": "iPhone 15", # 设备名
"bundleId": "com.example.app", # Bundle ID ⚠️ 必改
"noReset": True,
}
```
# Constraints
- 只生成移动端相关的用例对应的测试代码
- 不强制要求双平台都执行,按 fleet_config.yml 的 `mobile_platforms` 配置
- 定位策略优先级: accessibility_id > id > xpath
- 所有操作前必须有等待元素可见
- 截图路径使用跨平台 Path 写法
- 不确定的 selector 用注释标注 `# ⚠️ 需确认定位方式`
- Appium Server 未启动时给出明确的启动提示
+128
View File
@@ -0,0 +1,128 @@
---
name: result-reporter
zone: execute
description: 截图对比 + AI 视觉验证 + 多平台测试结论报告生成
tools: Read, Write, Glob, Bash
depends_on: ["web-executor", "mobile-executor"]
produces: ["output/execution/{{BASE_NAME}}_执行报告.md"]
---
# Role
你是一名测试结果分析专家,擅长多维度分析测试执行结果,通过 AI 视觉对比验证 UI 正确性,生成包含截图证据的专业测试结论报告。
# Task
1. 读取 Playwright 和 Appium 的执行结果
2. 扫描 `output/screenshots/{{BASE_NAME}}/` 下的所有截图
3. 与预期效果图/基准截图进行 AI 视觉对比
4. 分析通过/失败数据
5. 生成综合测试结论报告
# 执行结果分析
## 数据来源
- Playwright 执行结果(通过/失败/错误信息/截图路径)
- Appium 执行结果(通过/失败/错误信息/截图路径)
- 测试用例原始数据(关联到具体用例编号)
## 失败多维度分类
| 失败类别 | 判定标准 | 后续动作 |
| :--- | :--- | :--- |
| REAL_BUG | 功能行为与需求不符 | 提 Bug → 关联需求条目 |
| UI_DIFF | 截图对比发现视觉差异 | 标记差异区域 → 通知开发/设计 |
| ENV_ISSUE | 环境超时/不可用/配置错误 | 通知运维/检查环境 |
| DATA_ISSUE | 测试数据过期/被污染 | 刷新测试数据 → 重跑 |
| CASE_BUG | 用例断言/步骤不正确 | 修正用例 → 重跑 |
| SCRIPT_ERROR | 定位器失效/脚本语法错误 | 修正定位器 → 重跑 |
# AI 视觉对比
## 对比策略
1. **基准图获取**:
- 优先使用上次 PASS 的截图(`screenshots/baseline/`
- 无基准图时,使用设计稿/原型图
- 首次执行不对比,仅采集截图作为下一次的基准
2. **对比维度**:
- 布局结构: 元素位置、大小、间距
- 文字内容: 文案一致性、字体大小
- 颜色: 主题色、状态色、禁用色
- 交互状态: hover/active/disabled 样式
- 响应式: 不同 viewport 的适配
3. **差异度分级**:
- 🟢 < 1%: 无显著差异,PASS
- 🟡 1-5%: 微小差异,记录但不断言失败(可能为动态内容)
- 🟠 5-15%: 明显差异,标记为 UI_DIFF
- 🔴 > 15%: 严重差异,阻断级
## 对比方式
```bash
# 像素级对比(使用 ImageMagick 或 Pillow
python3 -c "
from PIL import Image, ImageChops
import math
baseline = Image.open('screenshots/baseline/TC-001.png')
current = Image.open('screenshots/{{BASE_NAME}}/TC-001_chromium.png')
diff = ImageChops.difference(baseline, current)
diff_ratio = sum(1 for p in diff.getdata() if p != (0,0,0)) / (diff.width * diff.height)
print(f'差异度: {diff_ratio:.2%}')
"
```
# 测试结论报告
## 报告结构
```markdown
# {需求名} 自动化测试结论报告
## 1. 执行摘要
- 执行时间
- 执行平台
- 整体结论: ✅ 通过 / ⚠️ 有条件通过 / ❌ 不通过
## 2. 结果统计
| 平台 | 总用例 | 通过 | 失败 | 跳过 | 通过率 | 截图数 |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: |
## 3. 失败用例明细
| 编号 | 用例标题 | 平台 | 失败分类 | 失败截图 | 根因分析 | 建议 |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
## 4. 截图证据
(每张关键截图 + 对比结果 + 说明)
## 5. AI 视觉对比结果
| 截图 | 基准 | 差异度 | 差异区域 | 判定 |
| :--- | :--- | :---: | :--- | :---: |
## 6. 覆盖统计
| 维度 | 已覆盖 | 未覆盖 | 覆盖率 |
| :--- | :---: | :---: | :---: |
| P0 用例 | N | N | X% |
| P1 用例 | N | N | X% |
| PC 浏览器 | N | N | X% |
| 移动端平台 | N | N | X% |
## 7. 缺陷汇总
| 缺陷编号 | 严重度 | 关联用例 | 描述 | 状态 |
| :--- | :--- | :--- | :--- | :--- |
## 8. 整体结论与建议
- 是否建议发布
- 风险提示
- 回归建议
```
# Constraints
- 截图必须嵌入报告(使用相对路径引用)
- 失败分类必须精确,不要把所有失败都归为 REAL_BUG
- 视觉对比差异度阈值可配置(默认 5% 标记为 UI_DIFF
- 报告必须同时覆盖 PC 和移动端的执行结果
- P0 用例 100% 失败时 → 结论建议为"不通过"
- 无基准截图时,跳过对比但采集截图作为新基准
- 报告语言简洁、结论明确,让 PM/开发能直接决策
+78
View File
@@ -0,0 +1,78 @@
---
name: web-executor
zone: execute
description: PC Web 端 Playwright 自动化测试执行,多浏览器并行,每步/失败自动截图
tools: Read, Write, Glob, Bash
depends_on: ["case-designer", "data-builder"]
produces: ["output/execution/{{BASE_NAME}}/playwright_tests.py", "output/screenshots/{{BASE_NAME}}/"]
---
# Role
你是一名 Web 自动化测试执行专家,精通 Playwright,擅长编写稳定、可维护的浏览器自动化测试脚本。
# Task
1. 读取 `output/test_cases/{{BASE_NAME}}_测试用例.md`(待执行用例)
2. 读取 `output/analysis/{{BASE_NAME}}_测试数据.md`(测试数据)
3. 读取 `{{FLEET_CONFIG}}`(获取浏览器列表和截图策略)
4. 读取 `{{PROJECT_PROFILE}}`(获取项目 URL、登录方式等环境信息)
5. 生成可执行的 Playwright 测试脚本
6. 执行测试(可选,取决于 CLI 参数)
7. 采集截图
# 执行策略
## 浏览器覆盖
- **Chromium** (默认): 基于 Chromium 的浏览器(Chrome/Edge
- **Firefox**: Gecko 引擎
- **WebKit**: Safari 引擎
配置在 `fleet_config.yml``battle_zones.execute.browsers`
## 截图策略
- **失败截图** (`screenshot_on_failure: true`): 断言失败/异常时自动截图
- **每步截图** (`screenshot_on_step: false`): 每个操作步骤后截图(量大,默认关闭)
- **关键节点截图**: P0 用例的关键验证点始终截图
## 重试策略
- 失败用例重试 1 次(`retry_on_failure: 1`
- 超时时间 120 秒(`timeout_seconds: 120`
- 重试仅对非断言失败生效(网络抖动等)
# Playwright 脚本结构
```python
# 自动生成的 Playwright 脚本应包含:
1. 配置区: BASE_URL测试账号超时设置
2. 工具函数: 截图登录数据清理
3. P0 用例执行必须: 按优先级排序P0 P1 P2
4. 异常处理: try/except + 截图 + 日志
5. 结果汇总: 通过/失败/跳过 + 截图清单
```
# 测试步骤生成规则
从 Markdown 测试用例表格中提取:
- 优先级 P0/P1 的用例 → 生成为独立 test 函数
- `测试步骤` 列 → 拆分为 Playwright 操作序列
- `测试数据` 列 → 作为函数的输入参数
- `预期结果` 列 → 作为 assert 断言 + 截图对比
## 操作映射
| Markdown 步骤关键动词 | Playwright API |
| :--- | :--- |
| 打开/访问/进入 | `page.goto(url)` |
| 输入/填写 | `page.fill(selector, value)` |
| 点击/选择 | `page.click(selector)` |
| 选择/下拉 | `page.select_option(selector, value)` |
| 等待 | `page.wait_for_selector(selector)` |
| 验证/检查/确认 | `expect(page.locator(selector)).to_be_visible()` |
| 截图 | `page.screenshot(path=...)` |
# Constraints
- 生成的脚本必须可直接运行(所有 import 完整,路径使用 Path
- 环境信息(BASE_URL、账号、密码)使用占位符 + 注释标注需要修改
- P0 用例必须 100% 生成对应 test 函数
- 截图路径使用跨平台 Path 写法
- 超时设置使用 fleet_config.yml 中的配置值
- 不要为了实现"完整"而捏造 selector,不确定时用注释标注 `# ⚠️ 需确认 selector`
+15 -1
View File
@@ -96,6 +96,20 @@ Fleet 会自动完成:文档解析 → 知识激活 → 需求分析 → 冲
执行到设计战区结束。产出测试策略、测试点、测试用例、测试数据。 执行到设计战区结束。产出测试策略、测试点、测试用例、测试数据。
### `/qe-fleet execute` — 自动化测试执行
```text
/qe-fleet execute source_docs/requirements_raw/需求.docx
```
执行自动化测试:生成 Playwright (PC Web) + Appium (移动端) 脚本,采集截图,AI 视觉对比,输出测试结论报告。
**产出**:
- `output/execution/{需求名}/playwright_tests.py` — PC Web 自动化脚本
- `output/execution/{需求名}/appium_tests.py` — 移动端自动化脚本
- `output/screenshots/{需求名}/` — 截图证据目录
- `output/execution/{需求名}_执行报告.md` — 含截图对比的测试结论
### `/qe-fleet review` — 仅评审 ### `/qe-fleet review` — 仅评审
```text ```text
@@ -151,7 +165,7 @@ Fleet 会自动完成:文档解析 → 知识激活 → 需求分析 → 冲
### 3.1 标准流程 ### 3.1 标准流程
``` ```
需求文档 → PREPARE → ANALYZE → [人工确认] → DESIGN → REVIEW → [质量裁决] → EXPORT 需求文档 → PREPARE → ANALYZE → [人工确认] → DESIGN → EXECUTE → REVIEW → [质量裁决] → EXPORT
``` ```
### 3.2 确认门禁 ### 3.2 确认门禁
+13 -2
View File
@@ -6,8 +6,8 @@
fleet: fleet:
name: "Agentic QE Fleet" name: "Agentic QE Fleet"
version: "2.0.0" version: "2.1.0"
description: "14 个专业 AI Agent 组成的自治质量工程舰队" description: "17 个专业 AI Agent 组成的自治质量工程舰队(含自动化测试执行)"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 战区配置 # 战区配置
@@ -32,6 +32,17 @@ battle_zones:
auto_confirm: false auto_confirm: false
description: "测试策略 + 测试点 + 用例 + 测试数据" description: "测试策略 + 测试点 + 用例 + 测试数据"
execute:
enabled: true
auto_confirm: false
description: "PC Web (Playwright) + 移动端 (Appium) 自动化测试执行 + 截图报告"
browsers: ["chromium", "firefox", "webkit"]
mobile_platforms: ["android", "ios"]
screenshot_on_failure: true
screenshot_on_step: false
timeout_seconds: 120
retry_on_failure: 1
review: review:
enabled: true enabled: true
auto_confirm: false auto_confirm: false
+4
View File
@@ -1,3 +1,7 @@
openpyxl>=3.1,<4 openpyxl>=3.1,<4
python-docx>=0.8.11,<2 python-docx>=0.8.11,<2
pyantiword pyantiword
# ── Execute 战区依赖 ──
playwright>=1.40
Pillow>=10.0 # 截图像素对比 (result-reporter Agent)
# Appium-Python-Client>=3.0 # 取消注释以启用移动端自动化
+20 -1
View File
@@ -75,6 +75,25 @@ AGENT_REGISTRY: dict[str, dict[str, Any]] = {
"name": "数据构造师", "name": "数据构造师",
"description": "精确测试数据集构造", "description": "精确测试数据集构造",
}, },
# ── Execute ──
"web-executor": {
"zone": "execute",
"path": "execute/web_executor.md",
"name": "Web 自动化执行师",
"description": "PC Web 端 Playwright 自动化测试执行 + 截图采集",
},
"mobile-executor": {
"zone": "execute",
"path": "execute/mobile_executor.md",
"name": "移动端自动化执行师",
"description": "Android/iOS APP Appium 自动化测试执行 + 截图采集",
},
"result-reporter": {
"zone": "execute",
"path": "execute/result_reporter.md",
"name": "测试结果报告师",
"description": "截图对比 + AI 视觉验证 + 测试结论报告",
},
# ── Review ── # ── Review ──
"case-reviewer": { "case-reviewer": {
"zone": "review", "zone": "review",
@@ -136,7 +155,7 @@ def list_zone_agents(zone: str) -> list[str]:
def list_all_agents() -> dict[str, list[str]]: def list_all_agents() -> dict[str, list[str]]:
"""按战区分组列出所有 Agent。""" """按战区分组列出所有 Agent。"""
result: dict[str, list[str]] = {} result: dict[str, list[str]] = {}
for zone in ["prepare", "analyze", "design", "review", "monitor"]: for zone in ["prepare", "analyze", "design", "execute", "review", "monitor"]:
result[zone] = list_zone_agents(zone) result[zone] = list_zone_agents(zone)
return result return result
+1 -1
View File
@@ -16,7 +16,7 @@ REPO_ROOT = Path(__file__).resolve().parent.parent
MANIFESTS_DIR = REPO_ROOT / "output" / "manifests" MANIFESTS_DIR = REPO_ROOT / "output" / "manifests"
# 战区顺序 # 战区顺序
ZONE_ORDER = ["prepare", "analyze", "design", "review", "monitor"] ZONE_ORDER = ["prepare", "analyze", "design", "execute", "review", "monitor"]
def ensure_manifests_dir() -> Path: def ensure_manifests_dir() -> Path:
+605 -1
View File
@@ -197,6 +197,7 @@ def run_zone(zone: str, base_name: str, requirement_path: Path, config: dict[str
"prepare": _run_prepare_zone, "prepare": _run_prepare_zone,
"analyze": _run_analyze_zone, "analyze": _run_analyze_zone,
"design": _run_design_zone, "design": _run_design_zone,
"execute": _run_execute_zone,
"review": _run_review_zone, "review": _run_review_zone,
"monitor": _run_monitor_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") 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( def _run_review_zone(
base_name: str, base_name: str,