- 复盘:编排设计、逐轮决策链、35 项修复全量清单、关键技术点、踩坑与复用流程 - 真机指南:闭环模型(注入/截图/dumpLayout/hilog 三通道)、命令手册、坐标库、 安全访问选择器等 7 条踩坑、成本红线、标准测试脚本骨架
291 lines
15 KiB
Markdown
291 lines
15 KiB
Markdown
# 真机闭环自动化测试 · 实践指南
|
||
|
||
> 日期:2026-08-30 | 适用:Windows 开发机 + Git Bash + HarmonyOS 真机(本项目实测机:`88Z9K26525009283`,HarmonyOS,DevEco SDK 在默认安装路径)
|
||
> 本文是《五轮多Agent评审迭代-复盘与追溯.md》的姊妹篇:那篇讲"做了什么",这篇讲**"真机自动化闭环到底怎么搭、每条命令怎么用、哪些坑会让人白干一小时"**,目标是照抄即可复现。
|
||
|
||
---
|
||
|
||
## 0. 什么是本文说的"闭环"
|
||
|
||
```
|
||
注入输入(uitest uiInput) ──→ App/系统界面变化
|
||
↑ │
|
||
│ 观察三通道:
|
||
│ ① 截图 snapshot_display(视觉证据,留档)
|
||
│ ② UI 树 uitest dumpLayout(结构化文本,可 grep 坐标)
|
||
│ ③ hilog(App 内打点,判定逻辑走到哪一步)
|
||
│ │
|
||
判定(截图→视觉模型描述 / UI 树→精确文本+坐标 / hilog→时序事实)
|
||
└──── 决定下一次注入 ←──────────┘
|
||
```
|
||
|
||
三通道缺一不可:**截图**给人看和留档,**UI 树**给机器精确定位(视觉模型对中文文案有幻觉,坐标和文字必须以 UI 树为准),**hilog** 区分"界面没变"是"代码没执行"还是"执行了但渲染没刷新"(本次迭代靠它定位了照片链 P0)。
|
||
|
||
---
|
||
|
||
## 1. 环境准备(一次性)
|
||
|
||
### 1.1 工具链路径
|
||
|
||
```bash
|
||
# 每个新 Git Bash 会话先执行(建议写进 ~/.bashrc)
|
||
export PATH="$PATH:/c/Program Files/Huawei/DevEco Studio/sdk/default/openharmony/toolchains"
|
||
export MSYS_NO_PATHCONV=1 # 关键!见 §5.1
|
||
```
|
||
|
||
- `hdc`:`<DevEco>/sdk/default/openharmony/toolchains/hdc`
|
||
- 构建门禁:`cd /d/meida/app && cmd //c .build-gate.bat`(产物 `entry/build/default/outputs/default/entry-default-signed.hap`,已签名)
|
||
|
||
### 1.2 设备
|
||
|
||
```bash
|
||
hdc list targets # 应输出序列号,如 88Z9K26525009283
|
||
```
|
||
|
||
**设备必须解锁亮屏**。锁屏(PIN)会挡住一切交互注入——本次迭代 R1~R3 三轮测试全被 PIN 锁阻塞,只完成了"装包+启动+hilog 无崩溃"这层。屏幕熄灭后可用:
|
||
|
||
```bash
|
||
hdc -t <SN> shell "power-shell wakeup" # 亮屏(无锁时直接到桌面)
|
||
hdc -t <SN> shell "uitest uiInput keyEvent 2" # 上滑/唤醒手势(keyEvent 2 = 电源键语义场景之一,按设备实测)
|
||
```
|
||
|
||
有 PIN 的设备远程解不了锁(BouncerView 挡住),**需要人手动解锁**。这是当前方案的硬边界。
|
||
|
||
### 1.3 后端(App 登录/配额/报告链路需要)
|
||
|
||
后端跑在 WSL(Windows 侧 uv 管不了 WSL 建的 venv,必须进 WSL):
|
||
|
||
```bash
|
||
# 拉起(make up 起 PG/Redis → migrate → run;详见 meida_backend/README.md)
|
||
wsl -e bash -lc "cd /mnt/d/meida/meida_backend && make up && make migrate && make run"
|
||
|
||
# 健康检查(后端实际监听 0.0.0.0:8666)
|
||
wsl -e bash -lc "curl -s -m 2 http://127.0.0.1:8666/api/v1/health || echo DOWN"
|
||
```
|
||
|
||
**真机连后端**:真机访问开发机的局域网 IP(App 登录页可填服务地址;`DEV_AUTH_ENABLED=true` 时用 dev-token 登录)。Windows 侧验证转发是否通:`curl http://127.0.0.1:8666/api/v1/health`。
|
||
|
||
单测(不连库不发网络,安全):
|
||
```bash
|
||
wsl -e bash -lc "cd /mnt/d/meida/meida_backend && uv run --extra dev pytest tests/unit --no-cov -q"
|
||
# 注意 --extra dev:pytest 在 dev 依赖组里,不带会 spawn 失败
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 核心命令手册
|
||
|
||
以下全部带 `-t <SN>`(多设备安全);单设备可省。`<SN>` = 88Z9K26525009283。
|
||
|
||
### 2.1 装包与启停(★ 有一个致命细节)
|
||
|
||
```bash
|
||
# 构建并安装
|
||
cd /d/meida/app && cmd //c .build-gate.bat
|
||
hdc -t <SN> install -r "D:\meida\app\entry\build\default\outputs\default\entry-default-signed.hap"
|
||
|
||
# 启动
|
||
hdc -t <SN> shell "aa start -a EntryAbility -b com.xiangsilian.app"
|
||
|
||
# ★★★ 验证新代码前必须重启进程:install -r 只替换包文件,不杀正在运行的进程!
|
||
# aa start 对已运行的 App 只是拉到前台——你测的还是旧代码(R5 热修当场踩过)
|
||
hdc -t <SN> shell "ps -ef | grep xiangsilian | grep -v grep" # 看进程启动时间戳甄别新旧
|
||
hdc -t <SN> shell "aa force-stop com.xiangsilian.app" # 杀
|
||
hdc -t <SN> shell "aa start -a EntryAbility -b com.xiangsilian.app" # 再启 → 这才是新包
|
||
|
||
# 杀图库(清"安全访问选择器"的残留蒙层,见 §5.3)
|
||
hdc -t <SN> shell "aa force-stop com.huawei.hmos.photos"
|
||
```
|
||
|
||
**标准节奏:build → install -r → force-stop → start → 验证**。
|
||
|
||
### 2.2 输入注入
|
||
|
||
```bash
|
||
hdc -t <SN> shell "uitest uiInput click 576 2340" # 点击(像素坐标)
|
||
hdc -t <SN> shell "uitest uiInput longClick 576 2340" # 长按(注意:是 longClick 不是 longPress)
|
||
hdc -t <SN> shell "uitest uiInput doubleClick 576 2340"
|
||
hdc -t <SN> shell "uitest uiInput swipe 576 2000 576 800 600" # 滑动(velocity 200~40000)
|
||
hdc -t <SN> shell "uitest uiInput keyEvent Back" # 返回键(也支持 Home/Power/数字)
|
||
hdc -t <SN> shell "uitest uiInput inputText 576 1200 hello" # 对已聚焦输入框打字
|
||
```
|
||
|
||
打错命令时 uitest 会打印完整 usage(比猜语法快)。
|
||
|
||
### 2.3 观察通道一:截图
|
||
|
||
```bash
|
||
hdc -t <SN> shell "snapshot_display -f /data/local/tmp/shot.jpeg"
|
||
hdc -t <SN> file recv /data/local/tmp/shot.jpeg "D:\meida\.scratch\shot.jpeg"
|
||
```
|
||
|
||
截图按轮次留档(`shots-rN/序号-场景.jpeg`),既是证据也供事后分析(R4 测试挂掉后,58 张截图让接任 agent 零重复完成判定)。
|
||
|
||
### 2.4 观察通道二:UI 树(精确定位靠它)
|
||
|
||
```bash
|
||
hdc -t <SN> shell "uitest dumpLayout" # 不带 --p,输出到 /data/local/tmp/layout_<id>.json
|
||
# 输出文件名在 stdout:"DumpLayout saved to:/data/local/tmp/layout_85596362629.json"
|
||
hdc -t <SN> file recv /data/local/tmp/layout_<id>.json 本地.json
|
||
```
|
||
|
||
注意:这台 ROM 上 **`uiautomator` 不存在**,用 `uitest dumpLayout`;`--p 指定路径`在该 ROM 上不生效(recv 报 no such file),让它默认落盘再按 stdout 报的文件名取。
|
||
|
||
解析(Git Bash 下用 node,比 python 省事——Windows 可能没装 python):
|
||
|
||
```bash
|
||
node -e "
|
||
const d=JSON.parse(require('fs').readFileSync('本地.json','utf8'));
|
||
function walk(n,out){ if(n&&typeof n==='object'){ if(n.text) out.push(n.text+' | '+(n.bounds||'')); for(const k in n) walk(n[k],out);} else if(Array.isArray(n)) n.forEach(x=>walk(x,out)); return out;}
|
||
console.log(walk(d,[]).filter(t=>!t.startsWith(' |')).slice(0,30).join('\n'));
|
||
"
|
||
```
|
||
|
||
`bounds` 格式 `[x1,y1][x2,y2]`,点击坐标取中心 `(x1+x2)/2, (y1+y2)/2`。系统页面(相机、选择器)的节点可能整块渲染(照片网格只有一个大 Image 节点),此时只能靠截图估坐标。
|
||
|
||
### 2.5 观察通道三:hilog(逻辑时序的事实来源)
|
||
|
||
```bash
|
||
hdc -t <SN> shell "hilog -r" # 清缓冲(测前清一次,判定干净)
|
||
hdc -t <SN> shell "hilog -x | grep MEIDA_IMG | tail -5" # 过滤本项目打点
|
||
```
|
||
|
||
**App 侧要有打点体系**才有这一通道。本项目 `ImageCodec.ets` 里 `MEIDA_IMG` 域 11 处打点(select 前后/URI 数量/解码/打包/各 stage 失败/超时放弃),照片链 P0 的定位与修复验证全靠它:
|
||
|
||
```
|
||
gallery: select begin
|
||
gallery: select end, 1 uri(s), first=file://media/Photo/… ← 送达
|
||
gallery select not settled in 60000 ms, give up ← 超时兜底触发
|
||
camera: pick end, resultUri length=63 ← 拍照成功
|
||
jpeg: info 4000 x 3000 (120 ms) → pixelMap decoded → packed 284531 bytes ← 压缩链通
|
||
```
|
||
|
||
新功能开发时**先埋打点再上真机**,是这套闭环能自动判定的前提。
|
||
|
||
### 2.6 崩溃监控
|
||
|
||
```bash
|
||
hdc -t <SN> shell "hilog | grep -iE 'com.xiangsilian.app.*(crash|FATAL|JS)'"
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 视觉判定子环(AI 读图)与防幻觉纪律
|
||
|
||
截图拿回来后的判定链:
|
||
|
||
1. `Read` 截图 → 得到 CDN URL
|
||
2. `analyze_image(URL, "具体问题")` → 视觉模型描述(问坐标、问状态、问按钮位置)
|
||
3. **交叉验证**:涉及中文文案/精确状态时,以 UI 树 dump 的 `text` 字段为准——实测视觉模型会把「加载中」读成「错误」、把没勾选说成已勾选(R4/R5 各有一次教训)
|
||
|
||
提问要具体("第二列第一行的照片有没有被勾选?左上角有没有圆圈对勾?"),不要开放式("这页面有什么问题?")。
|
||
|
||
---
|
||
|
||
## 4. 已固化的坐标库(本机实测,换机/换分辨率需重测)
|
||
|
||
屏幕 1152×2520。坐标会随版本漂移,**用前先 dumpLayout 校验**。
|
||
|
||
| 场景 | 元素 | 坐标(中心) |
|
||
|---|---|---|
|
||
| 首页 | 试衣间卡片 | (440, 850) |
|
||
| 首页 | 报告 Tab / 我的 Tab | dumpLayout 取 |
|
||
| 试衣间 | 全身照·相册 / 拍照 | (635, 1176) / (730, 1176) |
|
||
| 试衣间 | 上衣·相册 / 拍照 | (382, 1930) / (477, 1930) |
|
||
| 试衣间 | 开始试穿 | (576, 2306) |
|
||
| 同意弹窗 | 不同意 / 同意并上传 | (350, 1622) / (802, 1622) |
|
||
| 系统相机 | 快门 | (576, 2340) |
|
||
| 系统相机 | 拍后确认 √ / 取消 ✕ | (1045, 2345) / (100, 2345);不同布局下 √ ≈ (1038, 2360) |
|
||
| 照片选择器 | 网格 3 列,列中心 x | 192 / 576 / 960 |
|
||
| 照片选择器 | 第一行 y | 975(有安全横幅时;横幅占位会推移) |
|
||
| 照片选择器 | 关闭 ✕ | (810, 212) |
|
||
| 保存对话框 | 确认键(右上 ✓ 图标) | dumpLayout 取(R3 已注记) |
|
||
|
||
---
|
||
|
||
## 5. 踩坑全记录(每条都真实花过时间)
|
||
|
||
### 5.1 Git Bash 路径转义(最高频)
|
||
|
||
现象:`hdc file recv /data/local/tmp/x.jpeg` 报 `Error opening file: no such file, path: C:/Program Files/Git/data/local/tmp/x.jpeg`——MSYS 把 `/data/...` 当 Windows 路径转换了。**引号包不住**。
|
||
解法:会话开头 `export MSYS_NO_PATHCONV=1`。
|
||
|
||
### 5.2 install -r 不杀进程
|
||
|
||
见 §2.1。症状:新修复"无效"、行为与旧版一致。`ps -ef` 看进程启动时间戳即可识破。
|
||
|
||
### 5.3 系统选择器的"安全访问"模式(当前最大边界)
|
||
|
||
三个事实:
|
||
1. **合成点击可能对选择器完全无效**:照片既不勾选也不进预览(截图前后对比+UI 树无变化+hilog 无 select end 三方证实)。同为安全模式的系统保存对话框则可以点。疑似与 ROM 的反自动化保护或残留状态有关。
|
||
2. **picker 空结果收场后可能残留不可见蒙层**,吞掉后续对选择器的一切注入;`aa force-stop com.huawei.hmos.photos` 可恢复(R5N3 记录,本次复现并验证了恢复手段)。残留的 `filePicker` 进程(`kill -9` 报 Operation not permitted)杀不掉,但它自 R4 测试成功期就存在,不是阻断因素。
|
||
3. 结论:**凡关键路径经过"安全访问选择器"的,闭环会在这里断**,最后一步交人工(本次 R5 热修的预览刷新/压缩链就停在这一点,已在一分钟验收清单)。绕行思路(未验证):入口改用 `photoAccessHelper.PhotoAccessHelper` 授权+应用内自绘选择器,或用相机路径(A3 可自动化)覆盖测试场景。
|
||
|
||
### 5.4 相机自动化的模式面板
|
||
|
||
点快门附近区域可能拉出"拍照模式"抽屉挡住操作,且抽屉上的点击会切模式。解法:keyEvent Back 收起,重新对准坐标点快门。相机路径里"拍完→√确认"的坐标在同机不同布局下有两版(§4),点完必须截图确认真的出了相机。
|
||
|
||
### 5.5 PIN 锁与熄屏
|
||
|
||
熄屏打断的不只是测试——**还打断了正在跑的 agent**(R5 开发 agent 就是被用户熄屏操作打断成半成品)。长测试期间请保持亮屏解锁(开发者选项可设"充电时不锁定")。
|
||
|
||
### 5.6 hilog 缓冲冲刷
|
||
|
||
相机/图库会产生海量日志,把 `MEIDA_IMG` 冲掉。重要判定**先 `hilog -r` 清缓冲再操作,操作完立即取**;超时兜底类日志(60s 后才打)要等足时间再取。
|
||
|
||
### 5.7 agent 侧的坑
|
||
|
||
- **上下文超限**:R4 测试 agent 塞了 58 张截图+几十个 dump 后挂掉(Invalid string length)。纪律:单 agent 分析截图 ≤20 张、dump 只 grep 不整读、每完成一个场景就落盘中间结论。
|
||
- **视觉幻觉**:见 §3,一律与 UI 树交叉验证。
|
||
- **hdc 命令语法差异**:`longPress` 不存在(是 `longClick`);`uiautomator` 不存在(是 `uitest dumpLayout`)——打错会打印 usage,照着改即可。
|
||
|
||
---
|
||
|
||
## 6. 红线与成本控制(写给下一轮测试 agent)
|
||
|
||
- 换装/发型**真实生成各 ≤1 次**(Phase A 直连自有线上服务,虽不走 DeepSeek 计费但要省服务资源);失败不重试烧服务,记录跳过原因
|
||
- 对话 ≤2 条(agent_chat 走 DeepSeek 真实计费,单条成本极低但有成本)
|
||
- **绝不跑** `make smoke`(真实 DeepSeek 全链路,仓库 README 标注"唯一花钱的入口")
|
||
- 后端集成测试(tests/integration)默认跳过,不强行开启(需要 PG/Redis)
|
||
- 测试基建(`.scratch/multiagent/checks/checks.mjs`)可修,产品代码不可改;发现产品 bug 报告给下一轮,不越权修
|
||
|
||
---
|
||
|
||
## 7. 一轮标准测试的完整脚本骨架(可抄)
|
||
|
||
```bash
|
||
SN=88Z9K26525009283
|
||
export PATH="$PATH:/c/Program Files/Huawei/DevEco Studio/sdk/default/openharmony/toolchains"
|
||
export MSYS_NO_PATHCONV=1
|
||
H() { hdc -t $SN shell "$@"; }
|
||
|
||
# 0) 门禁
|
||
(cd /d/meida/app && cmd //c .build-gate.bat | tail -3) # 期待 BUILD SUCCESSFUL
|
||
wsl -e bash -lc "cd /mnt/d/meida/meida_backend && uv run --extra dev pytest tests/unit --no-cov -q | tail -2"
|
||
node /d/meida/.scratch/multiagent/checks/checks.mjs | tail -2 # 期待 28/28 PASS
|
||
|
||
# 1) 装新包并重启进程
|
||
hdc -t $SN install -r "D:\meida\app\entry\build\default\outputs\default\entry-default-signed.hap"
|
||
H "aa force-stop com.xiangsilian.app"; sleep 1
|
||
H "aa start -a EntryAbility -b com.xiangsilian.app"; sleep 4
|
||
|
||
# 2) 逐场景:清日志 → 注入 → 观察
|
||
H "hilog -r"
|
||
H "uitest uiInput click 440 850"; sleep 3 # 进试衣间
|
||
H "snapshot_display -f /data/local/tmp/s.jpeg"
|
||
hdc -t $SN file recv /data/local/tmp/s.jpeg ".scratch/scene.jpeg"
|
||
H "uitest dumpLayout" # stdout 给文件名 → recv → node 解析定位
|
||
H "hilog -x | grep MEIDA_IMG | tail -5"
|
||
|
||
# 3) 判定交叉验证 + 留档,进入下一场景……
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 当前状态与待办
|
||
|
||
- 已通:装包/重启/启动、页面导航与所有 App 自有 UI 交互、截图/dump/hilog 三通道、PDF 全链(含系统保存对话框)、飞行模式、杀 App 回看、登出重登、同意弹窗、断网错误态
|
||
- 半通:系统相机(可自动拍+确认,偶发模式面板干扰)
|
||
- 不通:照片选择器勾选(§5.3)——最后一公里人工,或改应用内自绘选择器
|
||
- 改进方向:把 §7 骨架固化成 `node` 脚本(场景→步骤→断言三段式),配 `MEIDA_IMG` 式打点规范推广到报告域/对话域,即可从"agent 手操"升级为"一条命令跑回归"
|