AI_UIAutomation/prompts/must_test_conversion.md

235 lines
13 KiB
Markdown
Raw Permalink 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.

# 必测项 → 自动化 转换提示词(子提示词)
> **配合主提示词使用**。本文件只覆盖「必测项」专项的特殊处理(来源、结构、映射、双协议、step 级回写)。
> 通用规则——技术栈 / DeviceDriver 接口 / 脚本模板 / 元素发现工作流 / 边跑边写调试 / 失败截图 / 报告——**一律遵循** `prompts/ones_to_automation.md`,此处不重复。
> 冲突时,以本子提示词的「必测项专项」约定为准。
---
## 1. 必测项来源(ONES)
- 团队 `98Q19ZsW`(`sz.ones.cn`)
- 测试计划:**`必测项-AI自动化`** plan uuid `CQz9YCNX`
- 用例库:**`App 必测项`** library uuid `EPfZfC9Y`(97 条)
- `ones` 二进制:`/Users/woan/local/bin/ones`
- 读取:
```bash
# 列表(注意:list 不返回 steps)
/Users/woan/local/bin/ones testcase case list EPfZfC9Y
# 单条完整步骤(控制用例必须用这个拿 steps)
/Users/woan/local/bin/ones testcase case search --key 15974 # WiFi控制设备
/Users/woan/local/bin/ones testcase case search --key 15975 # 蓝牙控制设备
```
---
## 2. 必测项的两种结构(转换前必须理解)
必测项**不是**一种新用例,而是两类已有维度的「视图」:
### A. 添加(connect)—— 按单品,每型号一条 case
- 分布在品类模块(摄像头/灯&WiFi/蓝牙/开关/URC HUB/温湿度&hub/Lock/扫地机 类),约 73 条「添加X验证」。
- 每条 ONES case → 一个设备的添加流程 → 落到 `tests/<cat>/<device>_connect.test.ts`
- 主键 = ONES 用例号(`number`)。
### B. 控制(control)—— 2 条超级用例,按连接协议分组,**每个 step = 一个单品的核心控制**
| ONES | 名称 | 步数 | 前置条件 |
|---|---|---|---|
| `15975` (uuid `Lqpkx6mp`) | 蓝牙控制设备 | 49 | 关 WiFi/热点、开蓝牙 |
| `15974` (uuid `Vp7vuhbu`) | WiFi控制设备 | 56 | 开 WiFi/热点、关蓝牙 |
- 控制粒度在 **step**,不在 case。主键 = `(ones_number, step_uuid)`
- 同一设备(Bot/Lock/Curtain/Meter…)在两条里都出现 → 两个控制断言(不同协议)。
- camera / robot / osc **只在 WiFi** 出现(本身是 WiFi 设备)。
- 控制内容不止开关:meter 温湿度校正、camera 出流停留 3min、robot 清扫/暂停/回充、Humidifier2 绑温湿度计、curtain/roller 百分比。
- 落到对应设备的 `tests/<cat>/<device>_control.test.ts`(多数断言主提示词流程里已存在)。
### C. 非单品(本次不转,除非用户要求)
~12 条平台级:登录/房间/消息中心/场景/覆盖安装。归 `tests/automation/` 或平台用例,不在「各单品添加+控制」范围。
---
## 3. 落点原则
**必测是「视图」,不是「副本」。不要新建 `tests/必测/` 目录。** 每条必测项映射到已有的
`{device}_connect.test.ts`(添加) / `{device}_control.test.ts`(控制),用**标记 + manifest**去选,而不是搬代码。这与「步骤沉到 `utils/common`、`.test.ts` 只做薄编排」一致。
---
## 4. 品类模块 → 仓库目录映射
| ONES 模块 | 仓库目录 |
|---|---|
| 摄像头类 | `camera`(出流类也可拆 `osc`) |
| 灯类&WiFi | `ceiling_light` / `strip_light` / `color_bulb` / `humidifier` / `air_condition` |
| 蓝牙类 | `curtain` / `sensor` / `fan` / `remote` |
| 开关类 | `plug`(含 Relay Switch / Garage Door) |
| URC HUB | `hub` / `urc` / `bot` |
| 温湿度&hub类 | `meter` / `hub` / `sensor` |
| Lock类 | `lock` / `keypad` |
| 扫地机类 | `robot` |
设备名取 `config/device.config.ts``DEVICE_CONFIG`,不要在脚本里写死。
---
## 5. 映射 manifest(核心产物)
生成 `test-plan/must-test.manifest.ts`,作为「ONES 必测项 ↔ 代码 ↔ 回写 ↔ 覆盖率」的中间层。**双主键**:添加按 case,控制按 step。
```ts
// test-plan/must-test.manifest.ts —— 由 scripts/gen-must-test-manifest.ts 从 ONES 生成,勿手改
export type MustTestItem =
| { kind: 'add'; ones: number; name: string; cat: string; device: string;
file: string; testName: string; status: 'done'|'todo'|'na' }
| { kind: 'ctrl'; ones: 15974|15975; step: string; proto: 'wifi'|'ble';
name: string; cat: string; device: string; action: string;
file: string; testName: string; status: 'done'|'todo'|'na' };
export const MUST_TEST: MustTestItem[] = [
{ kind:'add', ones:91013, name:'添加Plug验证', cat:'plug', device:'Plug 4D',
file:'tests/plug/plug_connect.test.ts', testName:'[P0] 通过BLE添加Plug', status:'todo' },
{ kind:'ctrl', ones:15974, step:'<stepUuid>', proto:'wifi', name:'点击控制Plug 开/关',
cat:'plug', device:'Plug 4D', action:'开/关',
file:'tests/plug/plug_control.test.ts', testName:'[P0][ble+wifi] 开/关 Plug', status:'todo' },
// ... 全部 添加 case + 两条控制用例的全部 step
];
```
**生成脚本要点**(`scripts/gen-must-test-manifest.ts`):
1. `case list EPfZfC9Y` → 取全部「添加X验证」case(模块属品类) → 生成 `kind:'add'` 行。
2. `case search --key 15974/15975` → 遍历 `steps[]`,每步生成 `kind:'ctrl'` 行,带 `step.uuid` / `proto` / 从 `desc` 解析的 `device`+`action`。
3. 用第 4 节映射表填 `cat`,用 `DEVICE_CONFIG``device`,推断目标 `file`
4. `status` 初始 `todo`,实现后由测试运行结果回填(见第 9 节)。
---
## 6. P0 标记约定(带 ONES 锚点)
**关键:锚点必须打在 `reporter.record(名称, ...)` 的名称里**——结果写入 `reports/.results.json` 用的是 record 名称,`buildAnchoredPayloads` 从中解析 `[ONES:号(#step)]` 做回写。`it()` 标题里也加同一锚点作**备注**(测试报告里可见、便于追溯),但回写不读 it 标题。
```ts
// it 标题:加 [ONES:号] 作备注(可读/可追溯)
it(`[ONES:15968] 通过BLE添加${deviceName}设备`, async () => {
// ...
// reporter.record 名称:加 [P0][ONES:号] —— 这是回写真正依据
reporter.record(`[P0][ONES:15968] 添加${deviceName}`, 'PASS', dur, detail);
});
// 控制(协议相关):record 名称带 用例号#step_uuid + 协议;协议由 PROTO 环境变量切(见 §7)
const CTRL = process.env.PROTO === 'wifi' ? '[P0][ONES:15974#<step>][wifi]' : '[P0][ONES:15975#<step>][ble]';
reporter.record(`${CTRL} 开/关 ${deviceName}`, status, dur, detail);
```
- 一条 ONES step 可由多个用例覆盖 → 用同一 step 锚点,回写自动聚合(fail>skip>pass)。
- 筛选:`vitest -t '\[P0\]'`(全量) / 结果文件里按 `[ble]`/`[wifi]` 区分协议。
### 同品类多型号:设备维度动态锚点
一个品类的脚本只测默认设备,但同品类多个 UI 相似型号(如 Curtain/Curtain3/BlindTilt)应共用脚本、各自回写。**不要写死 ONES 号**,改用 `utils/common/ones-anchor.helper.ts` 按当前设备动态解析:
```ts
import { onesAdd, onesCtrl } from '../../utils/common';
const ADD_ANCHOR = onesAdd('curtain', deviceName); // 添加: 按 CURTAIN_DEVICE 解析
const CTRL_CURTAIN = onesCtrl('curtain', deviceName); // 控制: 按设备 + PROTO 解析
reporter.record(`${ADD_ANCHOR} 添加窗帘设备`, ...);
```
- `ANCHORS` map(在 `ones-anchor.helper.ts`)按 `品类 → 设备名(DEVICE_CONFIG) → {add, ctrlBle, ctrlWifi}` 维护;**新增型号补一行即可**。
- 跑法:**换 `<CAT>_DEVICE` 环境变量按型号多跑几遍**(× PROTO),每遍写到该型号的 ONES 用例。
```bash
CURTAIN_DEVICE='Curtain3 2B' PROTO=ble npx vitest run tests/curtain
```
- 约束:每个型号要是账号里真实存在的设备才能跑(变体覆盖上限 = 真机数量)。
- UI **不相似**的型号(需新写)→ 单独脚本 + 自己的锚点,不走此复用。
---
## 7. 双协议运行模式(本次确定:双协议覆盖)
控制必测**两种协议都要跑**,以与 ONES 的两条用例 1:1 对齐。协议是**运行模式**,靠前置切换手机网络:
- `PROTO=ble`:关 WiFi/热点、开蓝牙 → 跑所有 `[ble]` 控制(对应 15975)
- `PROTO=wifi`:开 WiFi/热点、关蓝牙 → 跑所有 `[wifi]` 控制(对应 15974)
- 切换动作优先 `adb`(Android)/串口;无法自动化时,按 [[feedback-manual-navigation]] 让用户手动切换并确认后继续。
- 仅在该协议下存在的设备(camera/robot/osc 只在 wifi)才生成对应模式的断言。
```jsonc
// package.json
"test:must:add": "vitest run -t '\\[P0\\].*添加'",
"test:must:ctrl:ble": "PROTO=ble vitest run -t '\\[P0\\].*\\[ble\\]'",
"test:must:ctrl:wifi": "PROTO=wifi vitest run -t '\\[P0\\].*\\[wifi\\]'",
"test:must": "npm run test:must:add && npm run test:must:ctrl:ble && npm run test:must:ctrl:wifi"
```
---
## 8. 控制 step → 断言 转换规则
每个 step 是「操作 + 预期」,转成该设备 control 测试里的一条断言:
- `step.desc` = 操作(如「点击控制Bot 不加密开&不加密关&加密按压」)→ 拆成对应控制动作序列。
- `step.result` = 预期(如「对应Bot固件响应动作」)→ 断言(状态变更 / UI 反馈 / 出流成功 / 图表加载)。
- 复杂控制按设备类型走既有 helper:开关类用控制 helper;meter 校正走设置页校正流程;camera 出流后**停留 3min**再断言画面/水印;robot 断言清扫/暂停/回充状态。
- 多数动作主提示词的 control 流程已实现 → 复用,不重写;仅补必测特有断言并打 P0 锚点。
---
## 9. step 级结果回写 ONES正确方法GraphQL mutation
> ⚠️ **必须走 `ones graphql` mutation,不要用 curl 直连 REST `.../cases/update`。**
> `ones config show` 会把 token 打码成 `***`,curl 直连必然 `401 AuthFailure.InvalidToken`;
> 而 `ones graphql` 复用 CLI 登录认证、无需 token/PAT,且已在权限白名单内。
> 此机制已在 `utils/ones-sync.ts` 的 `postPayloads` 实现,跑 `scripts/sync-ones-results.ts` 即用。
**key 拼接规则(确定式)**
- case: `testcase_plan_case-<planUUID>-<caseUUID>`
- step: `testcase_plan_case_step-<planUUID>-<caseUUID>-<stepUuid>`
**写入 mutation**
```bash
# case 级
ones graphql 'mutation { updateTestcasePlanCase(key: "testcase_plan_case-CQz9YCNX-<caseUUID>", result: "passed") { key } }'
# step 级result 字段名是 step_result不是 execute_result/result
ones graphql 'mutation { updateTestcasePlanCaseStep(key: "testcase_plan_case_step-CQz9YCNX-<caseUUID>-<stepUuid>", step_result: "passed", actual_result: "开/关成功") { key } }'
```
- `result` / `step_result` 取值:`passed` / `failed` / `skipped` / `to_do`(PASS→passed、FAIL→failed、SKIP→skipped)。
**必测项据此:**
- **按用例聚合、一趟写完(批量规则)**:同一用例的所有 step 结果先聚合,在**一次回写流程里**写完该用例的全部 step、再写 case 级 result——不要把一个用例的步骤分散到多次回写里逐个触发。(`buildAnchoredPayloads` 已按 caseUUID 聚合 step,`postPayloads` 对一个用例的 steps 连续写完再写 case,即满足此规则。GraphQL 不支持单请求多 mutation,故实现上是同一趟内连续多次 mutation,效果即"一次性更新该用例多个步骤"。)
- **添加/功能 case**:只写 case 级 `updateTestcasePlanCase`
- **控制用例 15974 / 15975**:case 级 result 由 step 聚合(全跑完才 passed/failed,否则 `to_do`)。`[ble]`→15975、`[wifi]`→15974。
- 匹配靠测试名 `[ONES:号#step]` 锚点(已在 `buildAnchoredPayloads` 实现),不用 LCS 误配。
**回写参数已固化(用例固定、仅新增)**:`test-plan/ones-writeback-params.json` 保存了计划全部用例(号→`uuid`)+ 15974/15975 的步骤 uuid。用例与步骤 uuid 与具体计划无关,**plan UUID 是回写时唯一变量**——后续给定任意必测项 plan 链接,取出 planUUID 即可按上面 key 规则拼出所有 case/step key 直接回写,无需重新查 ONES。用例新增后重跑 `npm run gen:writeback-params` 刷新。
**读回校验:**
```bash
ones graphql '{ testcasePlanCaseSteps(filter: { testcasePlan: { uuid_in: ["CQz9YCNX"] }, testcaseCase: { uuid_in: ["<caseUUID>"] } }, limit: 60) { key stepResult actualResult } }'
```
(注意:查询用驼峰 `stepResult`/`actualResult`,filter 用 `testcasePlan`/`testcaseCase` 嵌套 `uuid_in`。)
---
## 10. 覆盖率核对
用 manifest 对照 ONES 必测清单,产出未实现列表:
- `add` 行:哪些「添加X」还没有对应 `_connect` 测试。
- `ctrl` 行:两条用例共 105 步,哪些 step 还没对应断言。
- 输出「已实现 / todo / na(无实体设备或暂不支持)」三态,na 必须 `log` 说明原因,不可静默跳过。
---
## 11. 端到端工作流
1. `gen-must-test-manifest.ts` 从 ONES 拉取 → 生成 `must-test.manifest.ts`
2. 按 manifest 的 `todo` 行,在对应 `_connect`/`_control` 测试里补断言并打 `[P0][ONES:...]` 锚点(遵循主提示词「边跑边写」)。
3. `npm run test:must`(添加 + ble + wifi 三段);协议切换不可自动化时请用户手动配合。
4. 结果按锚点回写 ONES plan `CQz9YCNX`(添加按 case、控制按 step)。
5. 更新 manifest `status`,刷新覆盖率。
---
## 相关记忆
[[project-must-test-ones-source]] · [[project-maestro-conversion]] · [[feedback-test-case-reuse]] · [[feedback-manual-navigation]]