Files
.pouch/skills/tester/references/run.md
T

251 lines
14 KiB
Markdown
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.
# tester 运行
只在用户要跑测试时读本文件。
Agent、执行目标和临时 terminal 的完整协议见 [execution.md](execution.md)。每轮运行
都要先解析并更新 `agent`,输出生效配置列表,准备目标项目并创建一个全新的 terminal;不能复用
已有 terminal。
测试结束、失败、超时或中断时,都要关闭本轮创建的 terminal。
## 0. 建立测试矩阵并完成全量 preflight
测试不是“找到一个能跑的设备就开始”。每轮先建立一份测试矩阵;移动端每个目标产物
必须单独占一行:
```text
平台 | 产物路径/下载地址 | commit | 包名或 Bundle ID | tester 绑定 | 执行环境
Android | … | … | page.writes | tester.android | local-app
iOS | … | … | page.writes | tester.ios | remote-ios
```
矩阵来源按优先级使用:用户明确指定的产物、builder/CI 已成功并已 obtain 的产物、
项目本地明确标记的候选产物。不能根据“曾经构建成功过”或 `builder.<platform>`
绑定猜测本轮产物。缺少路径、下载地址、commit 或平台身份时,先报告“待确认”并停止。
平台绑定是测试执行目标,不是构建目标:`builder.ios` 只能证明构建配置,不能替代
`tester.ios`;`builder.android` 同理。多平台运行不得使用无法表达平台的通用
`tester.test` 绑定。每一行都必须解析出独立的 tester Agent、Orca 环境、项目和路径。
完成矩阵后,按以下顺序检查**所有行**,但不执行测试用例:
1. 产物:文件或下载地址存在,SHA/commit(若有)与本轮目标一致;Android APK 的
package、iOS IPA 的 Bundle ID 与矩阵一致。
2. 执行目标:按 [execution.md](execution.md) 精确解析每个平台的 `tester` 绑定;
不允许把本机 worktree 当成远程 Mac 的 worktree,也不允许用旧 commit 的远程目录
代替本轮产物对应的项目目录。
3. 平台环境:Android 执行 adb、设备、APK、HOST 检查;iOS 在 macOS 执行
`uname`、`xcrun`/`simctl`、模拟器或真机、App 安装、HOST 检查。
4. 依赖环境:浏览器、权限、网络、测试账号或 catalog 明确要求的其它 session 前置。
所有检查完成后才能进入下方的 `select` 和 session 初始化。任一行不可用时,必须返回
完整清单并停止:
```text
## tester preflight:阻塞
目标产物: Android, iOS
可用: <平台 + 产物 + 环境>
不可用:
- <平台>: <具体检查项和观测结果>
修复: <用户可执行动作>
验证: <可复制命令>
本轮未创建测试 session,未执行测试用例。
完成修复后重跑原 tester 命令。
```
不能因为 Android 可用就先测 Android,也不能用 API、另一平台或旧产物替代不可用行。
先按 `execution.md` 处理目标机器和项目目录,输出配置更新结果,再进入测试流程。目标测试项目必须包含
可读取的 `.pouch/tester/catalog.yaml`。父仓库可能包含多个测试子项目;运行前用
`catalog.py roots --root <repo-root>` 发现全部目标根,不能把父仓库无 catalog 当成失败,
也不能偷偷回退到某一个子项目或复制测试文件。所有目标根都要被 fan-out 调度,每个根独立
做产物/环境 preflight,并单独生成运行快照。
`catalog.py select` 输出的 `agent` 是本轮使用的配置快照。记录中要保留 Agent、目标
环境、主机、项目、路径、worktree id、terminal handle,以及 terminal 是否成功关闭。
## 1. 选范围
未指定范围 → `--tag smoke`,风险上限 = `default_max_risk`,`automation` 默认只跑 `automated`。
用户说分类、id、tag、风险上限时,原样传给 select:
```bash
python3 <skill-dir>/scripts/catalog.py select --root <root> \
[--category CAT] [--id ID]... [--tag smoke|full|slow] \
[--max-risk <risk>] [--driver script|browser|mobile] \
[--automation automated|assisted|manual|any] \
[--platform android|ios]... \
[--artifact android=<path-or-url>] [--artifact ios=<path-or-url>] \
[--binding android=tester.android] [--binding ios=tester.ios]
```
选中任一 `driver=mobile` 用例时,`--platform`、每个平台的 `--artifact` 和对应
`tester` binding 是硬性参数;`catalog.py select` 会检查产物文件/下载地址和
`.pouch/agent.yaml` 中的 binding。缺任何一项直接返回非零并打印 `preflight: blocked`,
不能拿到 cases 后绕过闸门开始测试。默认 binding 是 `tester.<platform>`,所以 iOS
会强制检查 `tester.ios`,不会回退到 `builder.ios` 或通用 `tester.test`。
`--automation` 默认 `automated`(需人工的条目会被跳过,并在 JSON `skipped` 里说明);用户明确要求现场配合时用 `assisted`,`any` 表示不按人工参与度筛。
单根 `select` 的 JSON 字段:`max_risk` / `automation` / `count` / `vars`(已展开的变量,用来替换 `how` 里的 `${…}`)/ `artifacts` / `preflight` / `skipped` / `run_together` / `status_probe`(按分类,`url` 已展开)/ `cases`。从父仓库 fan-out 时返回 `mode: fan-out` 和每个根的独立结果,不能把多个根的 cases 拼成一个列表。
`select` 已排除 `retired` 和风险高于上限的条目(后者若用户点了具体 id,会在 JSON 的 `skipped` 里说明,不执行)。
若 catalog 有 `run_together`,命中组内任一条就把整组按 `ids` 顺序扩进列表(JSON 带 `run_together`)。这不是擅自扩大范围。
无命中:停下说明筛选条件,不要扩大范围。
## 2. 运行前闸
- `HOST` 未设(环境变量 / `HX_TEST_HOST` / `local.yaml` 都没有):全部停,environment-fail。
- 命中条的 `risk` 在 catalog `authorize` 里有对应变量:变量不是 `true` 则该条 `skipped`。有 `gate` 时还要 `catalog.gates` 对应变量为 `true`;`assume` 不成立同样 `skipped`。即使用户点了 id 也不执行。
- 授权/假设不成立是**主动不跑**(`skipped`),不是 `not-run`:这是我们决定不跑,不是没轮到。
- `status_probe` 是**分类级前置条件**(业界对应 pytest 的 session fixture / Robot 的 Suite Setup)。用 `select` 返回的 `status_probe` 解析生效值,不要自己读 catalog 猜。返回的 `probe.url` 已用 `vars` 展开,直接请求即可;`select` 同批返回 `vars`:
| `categories[].status_probe` | 生效探测 |
|------------------------------|----------|
| 未声明 | 继承顶层 `catalog.status_probe`(`source: global`)|
| `false` | 该分类不做闸(`source: disabled`)|
| mapping | 整体覆盖顶层,不合并(`source: category`)|
- 同一生效探测一次运行只请求一次,结果复用。
- 探测不可达或命中 `stop_if`:在 preflight 阶段记录对应分类/平台的不可用项并停止整轮;
先返回用户处理,不创建后续测试 session。只有所有本轮目标环境通过,才进入用例执行。
## 2.1 移动端平台环境闸门与用户引导
`driver=mobile` 不是一个可以互换的平台。运行前必须根据 catalog session、用例变量或用户指定,明确本轮是 Android 还是 iOS;无法判断时先停下询问平台,不得猜测。两类平台分别执行以下闸门:
### Android
按顺序确认:
1. `adb` 可执行,且版本与当前 SDK 配置一致。
2. `adb devices -l` 至少有一个状态为 `device` 的目标;`unauthorized`、`offline` 或空列表都不能继续。多个目标时必须显式设置 `ANDROID_SERIAL`。
3. 被测 APK 已安装,包名与 catalog 的变量一致;可用 `adb -s "$ANDROID_SERIAL" shell pm path "$PACKAGE"` 验证。
4. 测试所需的 HOST、端口、局域网/DNS 和权限可从设备访问。
### iOS
按顺序确认:
1. 运行节点是 macOS,并且 `xcrun`、Xcode Command Line Tools 和 `simctl` 可用。Linux/Windows 节点不能伪装成 iOS 环境;需要 macOS 或可访问的远程 Mac。
2. 模拟器用 `xcrun simctl list devices available` 确认目标存在且已启动,再用 `xcrun simctl bootstatus <UDID> -b` 确认完成启动;真机用 `xcrun xctrace list devices` 确认已连接、已信任且允许开发。
3. 被测 IPA/App 已安装,Bundle ID 与 catalog 的变量一致;模拟器可用 `xcrun simctl get_app_container <UDID> <BUNDLE_ID>` 验证。
4. 测试所需的 HOST、端口、局域网/DNS 和权限可从该模拟器/真机访问。
任一项不满足,都要在 preflight 汇总中列出平台、具体检查项、实际探测命令和简短输出,
然后停止整轮;不能先执行其它平台,不能改成 API 测试,也不能把它们标成产品 `fail`。
只有 preflight 全部通过后,才把对应移动用例的结果写入运行快照。
### 环境缺失时的用户引导
对话中必须输出一段可执行的引导,至少包含以下字段:
```text
## tester 环境引导:需要补齐
平台: Android | iOS
状态: environment-fail
缺失: <具体探测项和观测结果>
请完成:
1. <修复动作;不要只写“检查设备”>
2. <如需连接/启动/安装,写出动作>
验证:
<一条或多条可复制的验证命令>
完成后: 回复“环境已就绪”,然后重跑原 tester 命令。
```
可按实际缺失替换为以下平台化提示:
- Android 无设备:提示用户连接并解锁设备、开启 USB 调试、接受 RSA 授权;多个设备时设置 `ANDROID_SERIAL`;再运行 `adb devices -l`,直到目标状态为 `device`。
- Android 未安装 APK:提示用户构建/安装与当前测试环境匹配的 APK,并用 `adb -s "$ANDROID_SERIAL" shell pm path "$PACKAGE"` 验证包名。
- iOS 非 macOS 或无 Xcode:明确说明 iOS 测试需要 macOS + Xcode/Command Line Tools,提示用户切换到本机 Mac 或配置可访问的远程 Mac;不要建议在 Linux 上安装一个同名命令来绕过检查。
- iOS 模拟器未启动:提示用户在 Xcode 或 `simctl` 中创建/启动可用设备,使用 `xcrun simctl bootstatus <UDID> -b` 验证。
- iOS 真机未就绪:提示用户连接数据线、解锁并信任此电脑、开启开发者模式并确认设备出现在 `xcrun xctrace list devices`。
以下是两个可复现的引导示例,测试 skill 行为时应得到同类结果:
1. Android:`adb devices -l` 只有 `List of devices attached`。期望状态为 `environment-fail`,引导连接/授权设备并给出 `adb devices -l` 验证命令。
2. iOS:Linux 节点没有 `xcrun`。期望状态为 `environment-fail`,引导切换到 macOS + Xcode 节点,并说明不能用 API 或 Android session 代替。
环境引导只负责让用户补齐前置条件;除 catalog 已明确授权的 session 初始化外,不自动安装 SDK、配对真机、修改系统安全设置或上传产物。
## 3. Session
看命中条用到的 `session` 键:
- 尚未初始化:按 catalog `sessions.<name>.init` 做。`reuse: persist` 的先 `probe`,活着就附着。
- 本轮已初始化:直接用,不要重开浏览器或模拟器。
- `reuse: run`:整轮结束再关。`persist`:不关。
- 中途 session 挂了:后续同 driver 标 environment-fail,不要标产品失败。
## 4. 执行
按 select 返回的顺序,一条一条来。select 按 `catalog.yaml` 的 `categories` 顺序,同一分类内按该 yaml 列表顺序(不是文件名字母序)。默认失败即停(产品 `fail` 或探测到的不安全状态)。用户说继续 / keep-going 才往后跑。
例外:当前条和下一条同属 `run_together` 且该组 `continue_on_fail: true` 时,前一条产品 `fail` 仍跑后一条。`environment-fail` 仍整轮停。
**automation**
- `automated`:Agent 端到端执行。
- `assisted`:照常驱动,但遇到以 `[人工]` 开头的 `how.steps` 时停下,请现场人员执行并回报,确认后再继续。无人值守时该条记 `not-run`。
- `manual`:没有 `driver`,`run-script` 会直接返回 `not-run`。只能由人执行、回报结果,Agent 负责记录。
**script**
有 `how.run`:
```bash
python3 <skill-dir>/scripts/catalog.py run-script --root <root> --id <id>
```
这个命令必须发送到本轮创建的 terminal 中执行;`<root>` 是目标机器上的项目根,
不是发起端当前目录。远程目标上的 skill 脚本不可用时,直接在该 terminal 中执行
`how.run` 展开后的命令,并保留同样的 stdout/exit 证据。`run-script` 只跑命令,返回
`executed` + stdout/exit(或 skipped / environment-fail)。**pass/fail 仍由 Agent 对照
`expected` 判定**,不要把 curl 退出码 0 当成产品通过。
没有 `run`:按 `how.steps` 做,证据对照 `expected`。不要发明 steps 里没有的调用。
**browser / mobile**
挂到已有 session,执行 `how.steps`,对照 `expected`。必须留下可见文本、URL 或交互证据。
## 5. 记录与报告
每条:`id, result, evidence, notes`。`result` 只能是 `pass` / `fail` / `environment-fail` / `skipped` / `not-run`。`skipped` 是主动不跑(retired / pending / 超风险上限 / 未授权 / `assume` 不成立),`not-run` 是没轮到(失败即停后剩下的 / 被前置整组挡住的 / 需人工而无人值守的)。
写入 `.pouch/tester/runs/<UTC时间>-<范围>-<随机后缀>.yaml`,可用
`generate_run_csv.new_run_id(<范围>)` 生成同名 `runId`。记录包含唯一 `runId`、筛选条件、HOST 是否已设(可以写 HOST 值)、session 是否复用、每个分类的 `status_probe` 生效值与观测结果(`source` / `disabled` 原因 / `hit` / `observed`)、逐条结果。记录文件只创建一次,不覆盖已有文件。立刻用同一快照生成 CSV,不要手改:
```bash
python3 <skill-dir>/scripts/generate_run_csv.py --root <root> --run <yaml>
```
这会写出与快照同名的 `.pouch/tester/runs/<run-id>.csv`(`executed=yes|no`),不会修改 `cases.csv`。对话里两个路径都报。
对话报告:
```text
## tester 运行:完成 | 部分完成 | 阻塞
范围: …
风险上限: …
session: browser=复用|新开|未用;mobile=…
Agent: <cli> / <model> / <permissionMode>
执行目标: <environment>/<host>:<project>:<path>
terminal: 新建 <handle>,已关闭|清理失败
计数: pass=N fail=N environment-fail=N skipped=N not-run=N
失败: id + 一句证据
跳过: id + 原因
快照: .pouch/tester/runs/<file>.yaml
成绩单: .pouch/tester/runs/<file>.csv
下一步: 一句话
```