feat(tester): require caller artifact locators instead of --release

Mobile runs now take --artifact android|ios=<path-or-url> from the caller
and create a fresh isolated worktree each time. Drop CI release/manifest
lookup.
This commit is contained in:
2026-10-15 01:05:09 +08:00
parent 2fcf16627d
commit 5fef8247fc
9 changed files with 223 additions and 259 deletions
+4 -3
View File
@@ -111,9 +111,10 @@ python3 <skill-dir>/scripts/resolve_agent.py \
之后重新查询并取得 `worktree` selector。项目准备失败要记为
`environment-fail`,不要继续创建 terminal。
这里的“没有项目就创建”指创建/登记 Orca 的项目记录,不是删除或重建用户目录,
也不是创建新的 Git worktree。目标目录必须已经存在且包含待测项目;如果目录本身
不存在,停止并说明需要先在目标机器准备项目。
这里的“没有项目就创建”指创建/登记 Orca 的项目记录,不是删除或重建用户目录。
目标目录必须已经存在且包含待测项目;如果目录本身不存在,停止并说明需要先在
目标机器准备项目。移动端准备阶段会另外为本轮创建隔离 Git worktree,那一步
不替代这里的 Orca 项目登记。
## 配置更新结果
+10 -10
View File
@@ -18,9 +18,9 @@ Android | … | … | page.writes | tester.android | local-app
iOS | … | … | page.writes | tester.ios | remote-ios
```
矩阵来源按优先级使用:用户明确指定的产物、builder/CI 已成功并已 obtain 的产物、
项目本地明确标记的候选产物。不能根据“曾经构建成功过”或 `builder.<platform>`
绑定猜测本轮产物。缺少路径、下载地址、commit 或平台身份时,先报告“待确认”并停止。
矩阵里的产物位置必须由调用方给出,值为本地文件或 URL。不能根据“曾经构建成功过”、
`builder.<platform>` 绑定、CI release 名或本机旧文件猜测本轮产物。缺少路径或下载
地址时,先报告“待确认”并停止。
平台绑定是测试执行目标,不是构建目标:`builder.ios` 只能证明构建配置,不能替代
`tester.ios`;`builder.android` 同理。多平台运行不得使用无法表达平台的通用
@@ -28,8 +28,8 @@ iOS | … | … | page.writes | tester.ios | remote-ios
完成矩阵后,按以下顺序检查**所有行**,但不执行测试用例:
1. 产物:文件或下载地址存在,SHA/commit(若有)与本轮目标一致;Android APK 的
package、iOS IPA 的 Bundle ID 与矩阵一致。
1. 产物:调用方给出的文件或下载地址存在;Android APK 的 package、iOS IPA 的
Bundle ID 与 catalog 契约(若有)一致。
2. 执行目标:按 [execution.md](execution.md) 精确解析每个平台的 `tester` 绑定;
不允许把本机 worktree 当成远程 Mac 的 worktree,也不允许用旧 commit 的远程目录
代替本轮产物对应的项目目录。
@@ -118,22 +118,22 @@ python3 <skill-dir>/scripts/catalog.py select --root <root> \
`driver=mobile` 不是一个可以互换的平台。运行前必须根据 catalog session、用例变量或用户指定,明确本轮是 Android 还是 iOS;无法判断时先停下询问平台,不得猜测。两类平台分别执行以下闸门:
### Android(CI bundle 模式)
### Android
按顺序确认:
1. `adb` 可执行,且版本与当前 SDK 配置一致。
2. `adb devices -l` 至少有一个状态为 `device` 的目标;`unauthorized`、`offline` 或空列表都不能继续。多个目标时必须显式设置 `ANDROID_SERIAL`。
3. 如果命令带 `--release`,由 tester skill 下载 CI manifest 指定的 APK,校验 sha256、读取 APK 身份,自动执行 `adb install -r`,再用 `adb -s "$ANDROID_SERIAL" shell pm path <package>` 验证。没有 `--release` 时才沿用已有 APK 前置条件。
3. 调用方已给出本轮 APK 的本地路径或 URL。由 tester skill 取用或下载该文件,读取 APK 身份,自动执行 `adb install -r`,再用 `adb -s "$ANDROID_SERIAL" shell pm path <package>` 验证。
4. 测试所需的 HOST、端口、局域网/DNS 和权限可从设备访问。
### iOS(CI bundle 模式)
### 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. 如果命令带 `--release`,由 tester skill 在 tester.ios 的 Mac 下载 CI manifest 指定的 IPA,解包读取 `Payload/*.app/Info.plist`,校验 sha256 和 Bundle ID,自动执行 `xcrun simctl install`,再用 `xcrun simctl get_app_container <UDID> <BUNDLE_ID>` 验证。没有 `--release` 时才沿用已有 App 前置条件。
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。记录实际探测命令及简短输出,不要把整段设备日志塞进对话。
@@ -163,7 +163,7 @@ Android+iOS 默认是全量 preflight 闸门:任一项不满足,另一平台
可按实际缺失替换为以下平台化提示:
- Android 无设备:提示用户连接并解锁设备、开启 USB 调试、接受 RSA 授权;多个设备时设置 `ANDROID_SERIAL`;再运行 `adb devices -l`,直到目标状态为 `device`。
- Android 未安装 APK:提示用户构建/安装与当前测试环境匹配的 APK,并用 `adb -s "$ANDROID_SERIAL" shell pm path "$PACKAGE"` 验证包名。
- 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`。
+1 -1
View File
@@ -22,7 +22,7 @@ runs/*.csv 同一次运行的成绩单;generate_run_csv.py 从对
| `risks` | 从低到高,只能是 `readonly` → `mutating` → `destructive` 的一段,且以 `readonly` 开头。含义见 [levels.md](levels.md) |
| `default_max_risk` | 未指定时的运行上限,必须是 `risks` 里的值 |
| `vars` | 可替换变量,如 `API_BASE: http://${HOST}:8095` |
| `artifacts` | 可选的本轮移动产物契约。按 `android` / `ios` 声明 `required` 和 `package` / `bundle_id`;具体文件路径、下载地址、commit 和 SHA 由每轮测试矩阵提供,不提交临时产物路径。 |
| `artifacts` | 可选的移动产物身份契约。按 `android` / `ios` 声明 `required` 和 `package` / `bundle_id`;每轮文件路径或下载地址由调用方提供,不提交临时产物路径。 |
| `authorize` | 风险名 → 环境变量名。该档执行前变量必须为 `true`(`readonly` 不需要) |
| `gates` | 可选。项目细分闸名 → 环境变量。用在用例的 `gate` 上,不要拿它代替 `risk` |
| `sessions` | 可复用会话。`kind`: `browser` / `mobile`;`reuse`: `run`(本轮结束关掉)或 `persist`(probe 活着就附着) |
@@ -1,26 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["schema_version", "release", "commit", "artifacts"],
"properties": {
"schema_version": {"const": 1},
"release": {"type": "string", "minLength": 1},
"commit": {"type": "string", "pattern": "^[0-9a-fA-F]{40}$"},
"artifacts": {
"type": "object",
"required": ["android", "ios"],
"properties": {
"android": {
"type": "object",
"required": ["url", "sha256", "package"],
"properties": {"url": {"type": "string"}, "sha256": {"type": "string", "pattern": "^[0-9a-fA-F]{64}$"}, "package": {"const": "page.writes"}}
},
"ios": {
"type": "object",
"required": ["url", "sha256", "bundle_id"],
"properties": {"url": {"type": "string"}, "sha256": {"type": "string", "pattern": "^[0-9a-fA-F]{64}$"}, "bundle_id": {"const": "page.writes"}}
}
}
}
}
}
+15 -25
View File
@@ -1,31 +1,21 @@
# CI mobile test bundle
# 移动产物准备
项目 CI 负责把 APK、IPA 和 `test-bundle.manifest.json` 发布到已有的 Release;tester skill 负责消费它。
不要手工复制或安装产物。
manifest 至少包含:
```json
{
"schema_version": 1,
"release": "ci-07aa8f3",
"commit": "<40-character-commit>",
"artifacts": {
"android": {"url": "<download-url>", "sha256": "<64-hex>", "package": "page.writes"},
"ios": {"url": "<download-url>", "sha256": "<64-hex>", "bundle_id": "page.writes"}
}
}
```
运行入口:
调用方必须给出每个目标平台的产物位置:本地文件或可下载 URL。tester 不从
CI release、builder 绑定或本机旧文件猜测产物。
```bash
<skill-dir>/tester run --release ci-07aa8f3 --platform android,ios
<skill-dir>/tester run \
--artifact android=<path-or-url> \
--artifact ios=<path-or-url> \
--platform android,ios
```
默认先完成 Android+iOS 双平台 preflight;任一平台无法自动准备时整轮停止。只有显式加
`--partial` 才允许运行可用平台。每个平台都必须在自己的 `tester.<platform>` 节点下载、校验、
解包、验证身份并安装,随后由现有 tester catalog 驱动真实移动 session。
URL 就下载,本地文件就直接用。每个平台都在自己的 `tester.<platform>` 节点校验身份并安装,
随后由现有 tester catalog 驱动真实移动 session。默认先完成 Android+iOS 双平台
preflight;任一平台无法准备时整轮停止。只有显式加 `--partial` 才允许运行可用平台。
运行记录写入目标项目 `.pouch/tester/runs/`,包含 manifest、sha256、身份、实际 worktree commit、
节点、安装证据和 terminal 生命周期;`automatic-repair-failed` 与产品测试失败分开记录。
每轮另外创建隔离 Git worktree,不切用户当前分支。`--commit` 缺省为当前 HEAD。
运行记录写入目标项目 `.pouch/tester/runs/`,包含产物位置、sha256、身份、实际
worktree commit、节点、安装证据和 terminal 生命周期;`automatic-repair-failed`
与产品测试失败分开记录。