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

18 KiB
Raw Blame History

tester 运行

只在用户要跑测试时读本文件。

Agent、执行目标和临时 terminal 的完整协议见 execution.md。每轮运行 都要先解析并更新 agent,输出生效配置列表,准备目标项目并创建一个全新的 terminal;不能复用 已有 terminal。 测试结束、失败、超时或中断时,都要关闭本轮创建的 terminal。

0. 建立测试矩阵并完成全量 preflight

test-preflight 是 integration 和 journey 的共同硬闸。tester 必须先调用其 verify 或 prepare,保存独立的 preflight YAML/CSV;状态为 blocked 或 environment-fail 时不创建 browser/mobile session,所有尚未执行的 testcase 记 not-run。preflight 证据 不算 testcase,也不进入 testcase CSV/YAML 的总数。

测试不是“找到一个能跑的设备就开始”。每轮先建立一份测试矩阵;移动端每个目标产物 必须单独占一行:

平台 | 产物路径/下载地址 | commit | 包名或 Bundle ID | tester 绑定 | 执行环境
Android | … | … | page.writes | tester.android | local-app
iOS     | … | … | page.writes | tester.ios     | remote-ios

矩阵里的产物位置必须由调用方给出,值为本地文件或 URL。不能根据“曾经构建成功过”、 builder.<platform> 绑定、CI release 名或本机旧文件猜测本轮产物。缺少路径或下载 地址时,先报告“待确认”并停止。

平台绑定是测试执行目标,不是构建目标:builder.ios 只能证明构建配置,不能替代 tester.ios;builder.android 同理。多平台运行不得使用无法表达平台的通用 tester.test 绑定。每一行都必须解析出独立的 tester Agent、Orca 环境、项目和路径。

完成矩阵后,按以下顺序检查所有行,但不执行测试用例:

  1. 产物:调用方给出的文件或下载地址存在;Android APK 的 package、iOS IPA 的 Bundle ID 与 catalog 契约(若有)一致。
  2. 执行目标:按 execution.md 精确解析每个平台的 tester 绑定; 不允许把本机 worktree 当成远程 Mac 的 worktree,也不允许用旧 commit 的远程目录 代替本轮产物对应的项目目录。
  3. 平台环境:Android 执行 adb、设备、APK、HOST 检查;iOS 在 macOS 执行 uname、xcrun/simctl、模拟器或真机、App 安装、HOST 检查。
  4. 依赖环境:浏览器、权限、网络、测试账号或 catalog 明确要求的其它 session 前置。

所有检查完成后才能进入下方的 select 和 session 初始化。任一行不可用时,必须返回 完整清单并停止:

## 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,执行 profile = catalog 的 execution_profile(默认 standard), standard 的风险上限 = default_max_risk,automation 默认只跑 automated。 execution_profile=isolated-full 只用于隔离可重置环境:不按风险上限或 AUTHORIZE_*/gate 授权变量过滤 active 用例,保留每条用例原始 risk;安全门禁由 test-preflight 能力探针和 cleanup/rollback 契约承担。

用户说分类、id、tag、风险上限时,原样传给 select:

python3 <skill-dir>/scripts/catalog.py select --root <root> \
  [--category CAT] [--id ID]... [--tag smoke|full|slow] \
  [--max-risk <risk>] [--driver script|browser|mobile] \
  [--execution-profile standard|isolated-full] \
  [--automation automated|assisted|manual|any] \
  [--test-type integration|journey] [--suite <client-or-integration-suite>] \
  [--platform android|ios]... \
  [--artifact android=<path-or-url>] [--artifact ios=<path-or-url>] \
  [--binding android=tester.android] [--binding ios=tester.ios]

客户端旅程还可按标准套件选择:

python3 <skill-dir>/scripts/catalog.py select --root <root> --suite client-smoke

client-smoke、client-regression、safety 是 client_suite 的固定值,不替换既有 tag=smoke|full|slow。safety 不参与未指定范围的默认选择。

集成测试显式选择:

python3 <skill-dir>/scripts/catalog.py select --root <root> --test-type integration
python3 <skill-dir>/scripts/catalog.py select --root <root> --suite api-contract

未指定 --test-type/--suite 时,默认选择不会包含 test_type=integration。

选中任一 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(需人工的条目会被记为 not-run,不会伪造人工步骤);用户明确要求现场配合时用 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 拼成一个列表。

standard 的 select 已排除 retired 和风险高于上限的条目(后者若用户点了具体 id,会在 JSON 的 skipped 里说明);isolated-full 保留所有 active 条目,不因风险或授权变量 skipped。

若 catalog 有 run_together,命中组内任一条就把整组按 ids 顺序扩进列表(JSON 带 run_together)。这不是擅自扩大范围。

无命中:停下说明筛选条件,不要扩大范围。

2. 双层执行

preflight 通过后,先按 catalog 顺序执行 integration,再按客户端套件顺序执行 journey。集成服务不可达是 environment-fail;服务已达但 API/权限/数据契约断言错误 是 fail。任何 integration fail 都阻断依赖的 journey,并把它们记为 not-run;此时 不得创建客户端 session。正确测试账号登录失败发生在客户端已启动后,仍是产品 fail。

运行器可复用 scripts/two_tier.py 的 run_integration_first,它只接收外部 driver 回调,不实现 API、浏览器或移动执行器;mock fixture 只能验证调用顺序和失败传播,不能 冒充真实部署、Mac 或设备成功。

3. 运行前闸

  • HOST 未设(环境变量 / local.yaml 都没有):全部停,environment-fail。

  • standard:命中条的 risk 在 catalog authorize 里有对应变量时,变量不是 true 则该条 skipped;有 gate 时还要对应变量为 true,assume 不成立同样 skipped。即使用户点了 id 也不执行。

  • isolated-full:不读取 AUTHORIZE_MUTATING、AUTHORIZE_DESTRUCTIVE、AUTHORIZE_ALBUM、AUTHORIZE_PASSWORD 等人工开关;相册、照片权限/测试图片和中文拼音真实 composition/candidate 能力必须由 test-preflight 证明。

  • standard 中授权/假设不成立是主动不跑(skipped);isolated-full 的能力缺失是 preflight blocked/environment-fail,并为全部 testcase 写 not-run + blocked_by: [test-preflight]。

  • 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。只有所有本轮目标环境通过,才进入用例执行。

3.1 移动端平台环境闸门与用户引导

driver=mobile 不是一个可以互换的平台。运行前必须根据 catalog session、用例变量或用户指定,明确本轮是 Android 还是 iOS;无法判断时先停下询问平台,不得猜测。两类平台分别执行以下闸门:

Android

按顺序确认:

  1. adb 可执行,且版本与当前 SDK 配置一致。
  2. adb devices -l 至少有一个状态为 device 的目标;unauthorized、offline 或空列表都不能继续。多个目标时必须显式设置 ANDROID_SERIAL。
  3. 调用方已给出本轮 APK 的本地路径或 URL。由 tester skill 取用或下载该文件,读取 APK 身份,自动执行 adb install -r,再用 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 的本地路径或 URL。由 tester skill 在 tester.ios 的 Mac 取用或下载该文件,解包读取 Payload/*.app/Info.plist,校验 Bundle ID,自动执行 xcrun simctl install,再用 xcrun simctl get_app_container <UDID> <BUNDLE_ID> 验证。
  4. 测试所需的 HOST、端口、局域网/DNS 和权限可从该模拟器/真机访问。

Android+iOS 默认是全量 preflight 闸门:任一项不满足,另一平台也不得安装或执行,并在记录中标为 not-run。只有用户显式加 --partial 才允许已通过的平台继续。不能改成 API 测试、不能把它们标成产品 fail,也不要继续初始化被闸门挡住的平台 session。记录实际探测命令及简短输出,不要把整段设备日志塞进对话。 任一项不满足,都要在 preflight 汇总中列出平台、具体检查项、实际探测命令和简短输出,然后停止整轮;只有 preflight 全部通过后,才把对应移动用例的结果写入运行快照。

环境缺失时的用户引导

对话中必须输出一段可执行的引导,至少包含以下字段:

## tester 环境引导:需要补齐

平台: Android | iOS
状态: environment-fail
缺失: <具体探测项和观测结果>

请完成:
1. <修复动作;不要只写“检查设备”>
2. <如需连接/启动/安装,写出动作>

验证:
<一条或多条可复制的验证命令>

完成后: 回复“环境已就绪”,然后重跑原 tester 命令。

可按实际缺失替换为以下平台化提示:

  • Android 无设备:提示用户连接并解锁设备、开启 USB 调试、接受 RSA 授权;多个设备时设置 ANDROID_SERIAL;再运行 adb devices -l,直到目标状态为 device。
  • Android 缺少产物位置:停下向调用方要本轮 APK 的本地路径或 URL,不要手工 scp 或沿用旧包。
  • 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、配对真机、修改系统安全设置或上传产物。

4. Session

看命中条用到的 session 键:

  • 尚未初始化:按 catalog sessions.<name>.init 做。reuse: persist 的先 probe,活着就附着。
  • 本轮已初始化:直接用,不要重开浏览器或模拟器。
  • reuse: run:整轮结束再关。persist:不关。
  • 中途 session 挂了:后续同 driver 标 environment-fail,不要标产品失败。

5. 执行

按 select 返回的顺序,一条一条来。select 按 catalog.yaml 的 categories 顺序,同一分类内按该 yaml 列表顺序(不是文件名字母序)。同一 phase 的独立用例继续执行;只有显式 depends_on 的失败依赖才写 not-run + blocked_by。用户说 targeted rerun 时只重跑受影响用例,修复后再 full integration → journey。

例外:当前条和下一条同属 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:

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 或交互证据。

6. 记录与报告

每条:id, result, evidence, notes,依赖阻断时另有 blocked_by。result 只能是 pass / fail / environment-fail / skipped / not-run。skipped 是 standard 中主动不跑(retired / pending / 超风险上限 / 未授权 / assume 不成立);not-run 是没轮到或 preflight/依赖阻断。失败用例可在 YAML 顶层 defects(兼容 test_issues)记录 testcase、issue_type=product|test、expected/observed、artifact hash、environment、evidence path、reproduction、next step。

写入 .pouch/tester/runs/<UTC时间>-<范围>-<随机后缀>.yaml,可用 generate_run_csv.new_run_id(<范围>) 生成同名 runId。记录包含唯一 runId、筛选条件、HOST 是否已设(可以写 HOST 值)、session 是否复用、每个分类的 status_probe 生效值与观测结果(source / disabled 原因 / hit / observed)、逐条结果。记录文件只创建一次,不覆盖已有文件。立刻用同一快照生成 CSV,不要手改:

python3 <skill-dir>/scripts/generate_run_csv.py --root <root> --run <yaml>

这会写出与快照同名的 .pouch/tester/runs/<run-id>.csv(executed=yes|no),不会修改 cases.csv。对话里两个路径都报。

需要复核已有成绩单时,显式同时传入同一次运行的 CSV 和 YAML:

python3 <skill-dir>/scripts/summarize_run.py \
  --csv .pouch/tester/runs/<run-id>.csv \
  --yaml .pouch/tester/runs/<run-id>.yaml

汇总状态只读取 CSV result 列,必须输出五类 pass / fail / environment-fail / skipped / not-run,且计数和等于 CSV 数据行数。汇总前会检查 CSV 数据行数、CSV 唯一 testcase id 数、YAML results 数量和 CSV/YAML testcase id 集合;重复或缺失 id、缺失或 未知 CSV 状态、任一数量或集合不一致都会非零退出并给出诊断。YAML 的 counts 或其它 历史汇总字段不会参与状态计数。退出码 0 表示汇总校验成功,非零表示报告不一致或输入 无效,不等同于产品测试失败。

对话报告:

## 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
下一步: 一句话