# 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 `。 - **结构**:添加 = 每设备一条(~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--", result: "passed")` - step:`updateTestcasePlanCaseStep(key: "testcase_plan_case_step---", 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 报告 ```