113 lines
9.0 KiB
Markdown
113 lines
9.0 KiB
Markdown
# CLAUDE.md
|
|
|
|
SwitchBot App 的 AI 驱动 UI 自动化测试项目。本文件是项目常驻规则,进项目即生效。**这些规则优先于默认行为,必须严格遵守。**
|
|
|
|
## 项目概览
|
|
|
|
- **目标**:把 SwitchBot App 的「必测项」转成自动化用例,双端(Android + iOS)执行,并回写结果到 ONES 测试计划。
|
|
- **技术栈**:TypeScript + [Midscene.js](https://midscenejs.com)(`@midscene/*`)+ vitest;Android 走 Appium/UiAutomator2,iOS 走 WDA(XCUITest)。
|
|
- **App bundle**:iOS `com.wohand.wohand`(即 SwitchBot iOS App)。
|
|
- **被测设备**:SwitchBot 全品类 IoT(Bot / Plug / Camera / Lock / 灯类 / Hub / 温湿度计 / 扫地机 / 窗帘等)。
|
|
- 物理配对靠 **64 路继电器板**(`/dev/cu.usbserial-A50285BI` @115200,`utils/common/serial_controller.ts`)按对应通道触发设备进入配对。
|
|
|
|
## 目录结构(职责分层,严格遵守)
|
|
|
|
| 目录 | 职责 |
|
|
|---|---|
|
|
| `tests/<模块>/*.test.ts` | **用例脚本**。薄脚本:`describe/it` + 调 helper + 断言 + ONES 锚点(onesAdd/onesPlatform)。不放通用流程。 |
|
|
| `utils/common/*.helper.ts` | **通用可复用流程**(如 device-add / auth / room / scene / hub / curtain)。不含 vitest/断言。 |
|
|
| `utils/` | 跨层工具:`ones-sync.ts`(ONES 回写)、`wda-helper.ts`、`test-reporter.ts`、`agent.ts`。 |
|
|
| `drivers/` | 平台驱动:`android-driver.ts` / `wda-driver.ts` / `factory.ts` / `types.ts`。 |
|
|
| `config/` | 配置:`account/app/device/relay/wifi.config.ts`。 |
|
|
| `locators/` | 各品类元素定位。 |
|
|
| `scripts/` | 生成器 / soak / relay / ONES 同步等**工具脚本**(非用例)。临时探索脚本(`_*.ts`)**用完即删**。 |
|
|
| `docs/` | 交付报告(P0 回归报告等)。 |
|
|
| `test-plan/` | ONES 映射与回写参数(`must-test.manifest.ts`、`ones-writeback-params.json`)。 |
|
|
|
|
**铁律:用例 ≠ helper ≠ 工具脚本。** 通用动作沉到 `utils/common/`,用例只在 `tests/` 下组装调用 + 加 ONES 锚点。
|
|
|
|
## 工作方式规则(最重要)
|
|
|
|
0. **始终用中文回复**:本项目所有会话(含后续新会话)一律用中文回复。
|
|
|
|
1. **运行前先确认**:跑脚本 / 测试 / 设备自动化 / ONES 反写前,先列出待执行命令、输入文件和目标,**等用户确认再执行**。即使是 dry-run,只要会启动脚本或消耗时间也要先说明。
|
|
|
|
2. **提交/推送只按指令**:
|
|
- 不要每轮结束提醒提交;提交可攒几天一起,**只在用户说提交时才 commit**。
|
|
- `git push` **仅在用户明确说"推送/push"时执行**,否则攒着等指令。
|
|
- 该仓库 git 用 `/usr/bin/git`(Homebrew git 已坏),远程是 gitea。
|
|
|
|
3. **双端同步**:之后的优化/修改**需同时覆盖 Android 和 iOS**。改共享 helper 时确认两个 platform 分支都覆盖;改单端专属逻辑时评估另一端是否要等价改动。
|
|
|
|
4. **别改已调通的脚本**:用例失败时**先分清是环境问题还是脚本 bug**。环境问题(设备离线 / 首页设备过多 / 网络异常 / 继电器没触发)改环境,别改脚本。只有确证是脚本 bug 或确需优化才动代码。
|
|
|
|
5. **调试工作流**(详见 skill `debugging-ui-tests`):
|
|
- **不反复重启 App**:App 已在目标页就直接在当前页操作,复用状态。
|
|
- **不反复整跑用例**:只调**失败的那一步**,整跑确认留到全部修完。
|
|
- **摸 UI 用截图**:文本 dump 读不到的元素(自绘滚轮、图标、浮层)用截图看最有效。
|
|
- **不为找元素反复切换系统开关**(如关 WiFi→又开),既违反"不反复切换"又复现不稳定。
|
|
|
|
6. **查找不盲滚**:查设备/文案/卡片用「**找到即停 + 到底即停**」循环,不要盲滚固定 N 次。命中判据用可靠信号(iOS `visible=true` 且在 header 以下)。**唯一例外**:查添加入口品类(`selectDeviceCategory`)可用快速预滑提速。
|
|
|
|
7. **自动导航重试 5 次**:先自己尝试自动点击导航(最多 5 次),超过 5 次没进目标页才打印当前页面信息并请用户手动进入。
|
|
|
|
8. **过夜/无人值守走 nohup**:soak/回归用 `scripts/overnight-soak*.sh` / `run-*.sh` 独立进程,整夜不经过 Claude、不需授权。soak 主链路是纯确定性自动化(无 `aiAct`),几乎不烧 token;token 烧在"调脚本"阶段。别让 Claude 常驻盯跑。
|
|
|
|
## 添加用例(add)注意
|
|
|
|
- **首页校验关键字必须用全名**:`deviceKeyword`(首页 `source.includes`)和 `namePattern`(skip 检查)**不能用共享前缀**,否则串台误判通过(如 `'Keypad'` 会匹配到已加的 Keypad Vision Pro → 报"成功"但实际没加上)。同前缀型号(Keypad / Plug Mini / Floor Lamp / Strip Light / Hub)尤其注意。`skipScanStep:true` 时更致命。
|
|
- 症状:报告"添加成功"但首页实际没这设备 → 八成 deviceKeyword 太宽。
|
|
- **选品类大小写不敏感**:入口名照 App 实际文案填即可(`selectDeviceCategory` 已 `(?i)` 全字匹配,不退化成 contains)。**多级品类**逐级选(如 "Relay Switch" → 二级 "Relay Switch 1",在 `beforePairing` 钩子补点)。
|
|
- 通用添加流程在 `utils/common/device-add.helper.ts`(`addDeviceWithSerialPairing` / `addDeviceViaBLE`);注册表分平台(`added-devices.ios.json`)。
|
|
|
|
## ONES(事实源 + 结果回写)
|
|
|
|
- **事实源**:ONES 团队 `98Q19ZsW`(sz.ones.cn)。测试计划 `必测项-AI自动化` plan uuid `CQz9YCNX`;用例库 `App 必测项` lib uuid `EPfZfC9Y`(97 条)。`ones` 二进制在 `/Users/woan/local/bin/ones`。用 skill `onescli`。
|
|
- `ones testcase case list` 不返回 steps,要 `ones testcase case search --key <number>`。
|
|
- **结构**:添加 = 每设备一条(~73 条);控制 = 2 条超级用例按协议分组,**每 step = 一个单品核心控制**:
|
|
- 蓝牙控制 ONES 15975(uuid Lqpkx6mp,前置关 WiFi/开蓝牙)
|
|
- WiFi 控制 ONES 15974(uuid Vp7vuhbu,前置开 WiFi/关蓝牙)
|
|
- **结果回写必须走 `ones graphql` mutation,不要用 curl REST**(token 被打码,REST 必 401 且已废弃):
|
|
- case:`updateTestcasePlanCase(key: "testcase_plan_case-<plan>-<caseUUID>", result: "passed")`
|
|
- step:`updateTestcasePlanCaseStep(key: "testcase_plan_case_step-<plan>-<caseUUID>-<stepUuid>", step_result: "passed", actual_result: "...")`
|
|
- 取值 `passed|failed|skipped|to_do`;step 结果字段名是 `step_result`。
|
|
- GraphQL 不支持单请求多 mutation,按 caseUUID 聚合后连续多次写。
|
|
- 代码:`utils/ones-sync.ts` + `scripts/sync-ones-results.ts`;回写参数 `test-plan/ones-writeback-params.json`(`npm run gen:writeback-params`)。**plan UUID 是回写唯一变量**。
|
|
|
|
## P0 报告计数口径(硬规则)
|
|
|
|
- 只取**最后一轮**回归日志。
|
|
- **添加以最后结果为准**:重试后最终 PASS 即记通过(标注"第 N 次成功"),全程未 PASS 才记真失败,重试成功不计失败。
|
|
- **控制/其他 BLE 与 WiFi 各算一条、不去重**。
|
|
- **成功率 = 有效通过率**(分母排除 SKIP);**每平台各自一行,不算两端合计**。
|
|
- 脚本 `scripts/gen-p0-report.py`;报告放 `docs/测试报告_P0回归_YYYY-MM-DD.md`。
|
|
- ⚠️ **SOAK 跑期间不要并发跑其它 vitest**(共享 `reports/.results.json` 会污染合并 HTML)。
|
|
- ⚠️ SKIP 退出码 0 会被 soak 统计成 PASS → 可靠性结论必须**剔除 SKIP**(见继电器按压计数交叉验证)。
|
|
|
|
## iOS 特有(vs Android)
|
|
|
|
- **快速连接**:① `idevicepair pair`(手机点信任)② `ideviceinfo` 取 UDID / iOS 版本 ③ **`pkill -f iproxy`(清 8100 占用,关键)** ④ Appium XCUITest 连接,`wdaLocalPort=8200`,`platform_version` 与设备一致,`bundle_id=com.wohand.wohand`。换新机首次须在 Xcode 给 WebDriverAgentRunner 配 Team 签名并 Run 一次(否则 xcodebuild code 65)。完整指南见 `iOS连接修复指南.md`。
|
|
- **默认沿用 Android 配置**(WiFi / 账号);确需分开再设平台专用 env。
|
|
- **推进按钮叫 "Connect device"**(不是 Next);`tapNext` 已依次试 Next/Connect device/Continue/Done/Retry。
|
|
- **滚动用快速甩动**:`wda-helper.scrollDown` 慢速 swipe(0.5s)不滚(RN 首页当拖拽回弹)→ 必须 `duration=0.1`。这是很多 iOS "未找到设备"误判的根子。
|
|
- **有输入框的页,输完最后一个文本必须先收键盘再点下一步**(点键盘 Return/Done);**改名收键盘绝不 goBack**(会退页丢命名页)。
|
|
- iOS 元素法填 SSID 前**必须 clearText**(typeText 是追加不替换 → 变乱码连不上)。
|
|
- Android locator(`-android uiautomator`)在 `WDADriver.findElementRaw` 会自动转 iOS,写死的 Android locator 透明生效。
|
|
- iOS 找列表元素/文案优先用 WDA `scrollElement`(scroll-to-visible),避免坐标 swipe 误点。
|
|
- 提速:`utils/wda-helper.ts` `applySpeedSettings()` 关 `waitForIdleTimeout` 等(全局)。
|
|
|
|
## 相关 skill
|
|
|
|
- `debugging-ui-tests` — 调试 UI 用例前加载(规则总集 / iOS 坑位 / 查找不空滚 / 添加校验陷阱等)。
|
|
- `onescli` — 与 ONES 平台交互。
|
|
|
|
## 常用命令
|
|
|
|
```bash
|
|
npm test # vitest run 全量
|
|
vitest run tests/<模块> # 跑单模块
|
|
npm run gen:must-test # 生成必测项 manifest
|
|
npm run gen:writeback-params# 生成 ONES 回写参数
|
|
python3 scripts/gen-p0-report.py # 生成 P0 报告
|
|
```
|