200 lines
12 KiB
Markdown
200 lines
12 KiB
Markdown
# tester 目录字段
|
||
|
||
术语与命名理由见 [concepts.md](concepts.md);本文件只列字段与约束。
|
||
|
||
项目根 `.pouch/tester/`:
|
||
|
||
```
|
||
catalog.yaml 分类、风险阶梯、session、变量、默认 Agent 目标
|
||
local.yaml.example HOST、授权开关和 Agent 目标模板(默认 127.0.0.1)
|
||
local.yaml 本机覆盖,不提交
|
||
cases/<category>.yaml 一个分类一个文件(执行源)
|
||
cases.csv 给人看的扁平清单;只由 cases/*.yaml 生成,不包含运行结果
|
||
runs/<run-id>.yaml 每次运行快照(执行源);run-id 必须唯一
|
||
runs/*.csv 同一次运行的成绩单;generate_run_csv.py 从对应 yaml 生成
|
||
```
|
||
|
||
两类用例共用同一套 cases/runs 和五态报告,不另造成绩单。新目录显式声明顶层
|
||
`test_type: journey | integration`;旧目录没有该字段时按已有 `client_suite`/`journey`
|
||
或 `integration_suite` 推断。`category` 仍是文件/执行顺序容器,不被套件字段替代。
|
||
|
||
## catalog.yaml
|
||
|
||
| 字段 | 含义 |
|
||
|------|------|
|
||
| `categories` | `{id, title, status_probe?}` 列表,id 与 `cases/<id小写>.yaml` 对应;列表顺序即 `select` / 执行顺序。`status_probe` 见下 |
|
||
| `risks` | 从低到高,只能是 `readonly` → `mutating` → `destructive` 的一段,且以 `readonly` 开头。含义见 [levels.md](levels.md) |
|
||
| `default_max_risk` | 未指定时的运行上限,必须是 `risks` 里的值 |
|
||
| `execution_profile` | `standard`(默认)或 `isolated-full`。后者只用于隔离可重置环境,保留 risk 语义但不按 risk/authorize/gate 过滤 active 用例;设备、服务和 cleanup/rollback 契约仍由 preflight 检查,客户端能力由 journey 真实验证 |
|
||
| `vars` | 可替换变量,如 `API_BASE: http://${HOST}:8095` |
|
||
| `artifacts` | 可选的移动产物身份契约。按 `android` / `ios` 声明 `required` 和 `package` / `bundle_id`;每轮文件路径或下载地址由调用方提供,不提交临时产物路径。 |
|
||
| `authorize` | 风险名 → 环境变量名。该档执行前变量必须为 `true`(`readonly` 不需要) |
|
||
| `gates` | 可选。项目细分闸名 → 环境变量。用在用例的 `gate` 上,不要拿它代替 `risk` |
|
||
| `sessions` | 可复用会话。`kind`: `browser` / `mobile`;`reuse`: `run`(本轮结束关掉)或 `persist`(probe 活着就附着) |
|
||
| `id_pattern` | 用例 id 正则 |
|
||
| `run_together` | 可选。成组执行:`[{ids: [TC-…, TC-…], continue_on_fail: true}]`。点到组内任一条,`select` 会按 `ids` 顺序把整组扩进命中列表。`continue_on_fail: true` 时组内前一条产品 fail 仍跑后一条(返回/收尾)。一个 id 只能属于一组。 |
|
||
| `status_probe` | 可选。顶层默认值,一个**分类级前置条件**:`{url, stop_if: {字段: [命中值]}}`。运行前请求 `url`,响应命中任一 `stop_if` 条件即视为前置不满足。分类项可用同名键覆盖:缺省继承顶层、`false` 关闭、mapping 整体替换。命中或不可达只影响用它的分类,不是产品失败。业界对应 pytest 的 session fixture / Robot 的 Suite Setup。`select` 会按分类返回解析后的生效值,`url` 已用 `vars` 展开 |
|
||
| `client_suites` | 可选的标准套件声明;出现时必须完整包含 `client-smoke`、`client-regression`、`safety`。缺省运行仍按 `tag` 兼容旧目录 |
|
||
| `integration_suites` | 可选的集成套件声明;标准值为 `api-contract`、`authorization`、`build-contract` |
|
||
|
||
`isolated-full` 不得把风险字段改成 `readonly`,也不得把 `AUTHORIZE_ALBUM`、
|
||
`AUTHORIZE_PASSWORD`、`IME_PINYIN` 等变量当成门禁。移动用例必须通过 test-preflight
|
||
的设备、产物、服务和网络检查;相册、权限和输入法能力默认具备,交由 journey 用例验证。
|
||
|
||
`local.yaml` 与环境变量覆盖 `HOST` 和授权开关。模板中的 `HOST` 默认是
|
||
`127.0.0.1`;远程目标必须显式覆盖。查找顺序:环境变量 > `local.yaml` >
|
||
`catalog.yaml` 的 vars。
|
||
|
||
## agent
|
||
|
||
`catalog.yaml.agent` 是可提交的默认 Agent 目标,`local.yaml.agent` 对其做浅层覆盖。
|
||
它不参与 `HOST` 变量解析,也不会把远程机器路径写入环境变量。机器路径和临时 Agent
|
||
选择优先写在不提交的 `local.yaml`。
|
||
|
||
| 字段 | 含义 |
|
||
|------|------|
|
||
| `environment` | Orca 保存的远程环境选择器;空值使用当前环境 |
|
||
| `host` | 目标机器标识,默认 `local` |
|
||
| `project` | Orca project id 或名称;也可由 terminal id 自动解析 |
|
||
| `path` | 目标机器上的项目路径;空值表示当前项目根目录 |
|
||
| `role` | Agent 角色,tester 固定为 `test` |
|
||
| `cli` | `codex` / `cursor-agent` / `grok` / `omp` / `pi` |
|
||
| `tier` | `standard` / `strong` |
|
||
| `model` | 精确模型 ID;`null` 表示使用 CLI 默认模型 |
|
||
| `reasoningEffort` | `low` / `medium` / `high` / `xhigh` 或 `null` |
|
||
| `permissionMode` | `read-only` / `workspace-write` |
|
||
|
||
用户给 project id、项目名称或 terminal id 时,tester 先解析并写入 `local.yaml.agent`,
|
||
再展示更新后的 Agent/环境/项目/目录生效列表。每轮创建新 terminal,不复用既有
|
||
terminal;运行结束用 `orca terminal close --terminal <handle>` 关闭本轮创建的 terminal。
|
||
运行快照保存解析后的 Agent、目标、worktree 和 terminal handle。旧版 `execution` 字段
|
||
只作为兼容输入读取,不再作为新配置写出。详见 [execution.md](execution.md)。
|
||
|
||
运行记录使用 UTC 时间加随机后缀命名,例如
|
||
`20260920T101530Z-smoke-a13f9c.yaml`,并在 YAML 顶层保存同一个 `runId`。
|
||
不要使用固定的 `latest.yaml` 或覆盖已有运行文件。
|
||
|
||
## 一条用例
|
||
|
||
写在对应分类文件的 `cases:` 列表里。
|
||
|
||
| 字段 | 必填 | 含义 |
|
||
|------|------|------|
|
||
| `id` | 是 | 匹配 `id_pattern` |
|
||
| `test_type` | 新用例是 | `journey` 或 `integration`;缺省兼容旧目录并按字段推断 |
|
||
| `category` | 是 | 必须等于文件声明的 category |
|
||
| `title` | 是 | 短名 |
|
||
| `what` | 是 | 一句可观察的「测什么」 |
|
||
| `lifecycle` | 是 | `active` / `pending` / `retired`。`pending` = 暂缓,仍保留在目录里但不执行,原因写在 `notes` |
|
||
| `tag` | 是 | `smoke` / `full` / `slow`,判定见 [levels.md](levels.md)。`tag=smoke` 只能配 `risk=readonly` |
|
||
| `risk` | 是 | `readonly` / `mutating` / `destructive` |
|
||
| `gate` | 否 | 项目细分闸,必须在 catalog `gates` 里;`risk=readonly` 不要填 |
|
||
| `driver` | 否 | `script` / `browser` / `mobile`;`automation=manual` 时禁止填 |
|
||
| `automation` | 否 | `automated`(默认)/ `assisted` / `manual`,见 [concepts.md](concepts.md) |
|
||
| `session` | browser/mobile 必填 | 指向 catalog `sessions` 的键 |
|
||
| `how` | 是 | 怎么测,见下 |
|
||
| `expected` | 是 | 怎么过,至少一条 `{kind, value}` |
|
||
| `maps_to` | 否 | 对人文档的功能项 id |
|
||
| `assume` | 否 | 额外跳过条件(环境变量表达式)。不要重复 `risk` / `gate` 已覆盖的授权变量,否则 `validate` 报错 |
|
||
| `depends_on` | 否 | 依赖的 testcase id 列表;依赖 fail/environment-fail/not-run 时本条写 `not-run` 和 `blocked_by`,独立用例继续 |
|
||
| `setup` | 否 | 人话前置 |
|
||
| `teardown` | 否 | 跑完回退 |
|
||
| `notes` | 否 | 备注 |
|
||
|
||
### 集成测试用例契约
|
||
|
||
`test_type: integration` 的用例不创建 browser/mobile session,也不能声明 `platform` 或
|
||
客户端 `journey`。必须有 `integration_suite`(迁移期可用 `suite`)且值为
|
||
`api-contract`、`authorization` 或 `build-contract`;`fixture` 必须是非空 mapping;
|
||
`request`、`input`、`call`、`integration` 之一或旧式 `how` 用于描述调用;`expected` 是
|
||
协议/数据断言。`mutating`/`destructive` 集成用例必须提供 `cleanup.steps` 或
|
||
`cleanup.run`(只读探针可用 `required: false` + `reason`)。
|
||
|
||
选择 `--suite api-contract` 或 `--test-type integration` 才会进入集成阶段;无显式类型
|
||
时的默认选择不会包含 integration。
|
||
|
||
### 客户端旅程用例契约
|
||
|
||
声明 `client_suite` 后,以下字段都必须存在;`catalog.py validate` 和 `select` 使用同一
|
||
套检查。契约缺失是 `preflight: blocked`,不生成逐条产品失败。
|
||
|
||
| 字段 | 必填 | 约束 |
|
||
|------|------|------|
|
||
| `client_suite` | 是 | `client-smoke`、`client-regression` 或 `safety`;安全套件必须显式选择,不属于默认 smoke/full |
|
||
| `journey.goal` | 是 | 用户目标,不写接口目标 |
|
||
| `journey.entry_state` | 是 | 启动/登录前的可观察入口状态 |
|
||
| `journey.actions` | 是 | 非空用户操作列表;真实 App/browser 驱动从这里开始 |
|
||
| `journey.assertions` | 是 | 非空可观察 UI 断言列表;最终业务状态可另由 `expected` 的 api-field/api-status 佐证 |
|
||
| `journey.final_state` | 是 | 旅程完成后的业务状态 |
|
||
| `journey.failure_policy` | 是 | `stop` 或 `continue`;默认不隐式吞掉前置失败 |
|
||
| `fixture` | 是 | mapping,至少有 `account`;账号和数据由 app catalog 提供,skill 不写具体账号 |
|
||
| `session` | 是 | 复用的 browser/mobile session 名,必须在 catalog.sessions 声明 |
|
||
| `cleanup` | 是 | mapping,必须有非空 `steps`/`run`;只读旅程也要用 `required: false` + `reason` 显式说明无需清理 |
|
||
| `platform` | 是 | `android`/`ios` 的字符串或列表,不能重复;平台差异只能进入执行适配层 |
|
||
|
||
一个最小形状如下(值是占位符,不是 app 规则):
|
||
|
||
```yaml
|
||
client_suite: client-smoke
|
||
platform: [android, ios]
|
||
journey:
|
||
goal: "用户完成一个可观察的客户端目标"
|
||
entry_state: logged_out
|
||
actions: ["启动客户端", "输入测试账号并登录", "完成目标操作"]
|
||
assertions: ["看到目标页面", "看到成功提示"]
|
||
final_state: "目标状态已保存"
|
||
failure_policy: stop
|
||
fixture:
|
||
account: test-role
|
||
data: []
|
||
session: mobile
|
||
cleanup:
|
||
steps: ["删除本轮 run-id 创建的数据"]
|
||
```
|
||
|
||
账号登录属于 session 初始化:正确测试账号登录失败是产品 `fail`;设备、产物或服务
|
||
不可达才是 `environment-fail`。fixture/cleanup 缺失必须在启动、登录和任何旅程操作前
|
||
阻断。旅程失败后,尚未轮到的用例记 `not-run`;风险、生命周期或明确范围排除记
|
||
`skipped`。清理失败单独写入运行快照的 cleanup 证据,不改造为一个新的 result 值。
|
||
|
||
## how
|
||
|
||
按 `driver` 分派;`automation=manual` 时没有 `driver`,只有 `how.steps`(人工步骤)。
|
||
|
||
- `script`:`how.run` 是一条可在项目根执行的命令(可多行脚本)。没有 `run` 时 Agent 按 `how.steps` 做,仍不得发明未写明的请求。
|
||
- `browser`:`how.url` + `how.steps`。`expected.kind` 用 `visible-text` / `url` / `interaction`。
|
||
- `mobile`:`how.steps`,挂到 `session` 指定的模拟器。`expected` 同样是可观察信号。
|
||
|
||
可选 `how.timeout`,秒,默认 60。
|
||
|
||
`automation=assisted` 时,至少一个 `how.steps` 条目要以 `[人工]` 开头(写在 YAML 里要加引号,`[` 开头会被当成流式序列)。
|
||
|
||
## expected.kind
|
||
|
||
`api-status` / `api-field` / `script-exit` / `visible-text` / `url` / `interaction`
|
||
|
||
## 判定
|
||
|
||
`pass` / `fail` / `environment-fail` / `skipped` / `not-run`
|
||
|
||
## 运行失败回流
|
||
|
||
run YAML 的 `results` 必须保留每条 testcase 行。失败可在顶层 `defects`(兼容别名
|
||
`test_issues`)追加:
|
||
|
||
```yaml
|
||
defects:
|
||
- testcase: TC-CLIENT-001
|
||
issue_type: product # product | test
|
||
expected: "候选词可选择"
|
||
observed: "composition 成功但候选列表为空"
|
||
artifact_hash: sha256:...
|
||
environment: "Android emulator API 35 / tester.android"
|
||
evidence_path: .pouch/tester/evidence/TC-CLIENT-001.png
|
||
reproduction: "打开相册后输入 nihao"
|
||
next_step: "开发修复候选词渲染后 targeted rerun"
|
||
```
|
||
|
||
PR 触发的 run 可带 `feedback` 计划:先更新 PR/关联 issue,再通知 dispatcher 重新分配开发;
|
||
需求/bug 触发则 route 直接为 development。tester 只记录计划,不隐式调用远端写操作。
|