diff --git a/docs/五轮多Agent评审迭代-复盘与追溯.md b/docs/五轮多Agent评审迭代-复盘与追溯.md new file mode 100644 index 0000000..446a7f6 --- /dev/null +++ b/docs/五轮多Agent评审迭代-复盘与追溯.md @@ -0,0 +1,204 @@ +# 五轮多 Agent 评审迭代 · 复盘与追溯 + +> 日期:2026-08-30 | 执行:ZCode 主控 + PM/开发/测试三类子 agent | 产出:35 项修复(App 24 文件 +2134/-409,后端 3 文件 +65) +> 关联提交:主仓 `0b5d151`、子仓 `meida_backend@00ef56b`(均已本地提交,未 push) +> 过程产物:`.scratch/multiagent/`(CONTEXT.md、R1~R5 五轮 PM/测试报告、checks/checks.mjs、shots-r1~r5 截图档案、FINAL-REPORT.md) + +本文目标:**完整还原这次多 agent 迭代的机制、每一轮的决策链、所有修复的技术细节与踩坑**,供后续追溯与复用。真机自动化测试单独成篇:见《真机闭环自动化测试-实践指南.md》。 + +--- + +## 1. 任务与总体设计 + +### 1.1 原始需求 + +用户原话要点:了解项目(端侧模型/后端/PDF 报告/试衣间/发型设计);开三个 agent——PM 视角 review、开发 agent 按反馈开发并提建议、测试 agent 尽量自动化测试;**多 agent 迭代 5 轮**;最后用户验收。 + +### 1.2 多 agent 编排模型 + +``` +主控(ZCode 会话) + ├─ 基线验证(构建门禁 / 后端单测 / 设备探测)—— 不交给子 agent,自己先跑 + ├─ 写 .scratch/multiagent/CONTEXT.md(共享上下文,所有子 agent 必读) + ├─ Round N:PM agent(只读)→ 开发 agent(改码+门禁)→ 测试 agent(门禁+静态+真机) + │ ↑ 每轮把上一轮测试发现(Nx' 问题)+ 遗留池喂给下一轮 PM + └─ 终局:测试发现的 P0 回归由主控亲手热修(不再开一轮,控制成本) +``` + +关键设计决策: + +1. **共享上下文文件而不是长 prompt**。CONTEXT.md 固化项目结构、验证命令(全部主控亲测过可用)、硬约束(禁 commit/push、不跑花钱的 smoke、不开集成测试、最小 diff、代码约定)。每轮 agent prompt 只写"本轮主题 + 读哪些文件 + 输出到哪",避免 prompt 膨胀也避免 agent 复述项目背景浪费上下文。 +2. **角色硬隔离**。PM agent 只许读源码、唯一可写的是自己的报告文件;开发 agent 不许碰后端(除非该轮显式授权一处,如 R3 的 hint 改口、R4 的模板 meta);测试 agent 可修 `.scratch` 下的检查脚本(测试基建)但不能改产品代码。 +3. **每轮有主题,避免转圈**。R1 全局走查 → R2 AI 功能域 → R3 PDF 链路+一致性 → R4 结果可靠性+遗留池裁决 → R5 终局修复+内测验收清单。R3 起 PM 的职责从"找新问题"过渡到"裁决遗留池 + 定修复方案"。 +4. **报告落盘编号体系**。问题编号 #1~#28 全局连续;测试新发现用 N1、N2'… 前缀;文件名固定 `R{n}-{pm|test}.md`。这让第 5 轮 PM 能产出完整的 40 项遗留池终审表。 +5. **门禁前移**。开发 agent 的任务书里就写死"BUILD SUCCESSFUL 是硬门禁,失败必须修到过",测试 agent 复跑做独立复核——两道验证互相独立。 + +### 1.3 基线(迭代前主控亲测) + +- App 构建门禁:`cd app && cmd //c .build-gate.bat` → BUILD SUCCESSFUL(~1 分钟,增量更快) +- 后端单测(**必须 WSL**,Windows 侧 uv 管不了 WSL 建的 venv;**必须 `--extra dev`** 才有 pytest): + `wsl -e bash -lc "cd /mnt/d/meida/meida_backend && uv run --extra dev pytest tests/unit --no-cov -q"` → 574 passed 基线 +- 真机:`hdc list targets` → `88Z9K26525009283`(华为真机,有 PIN 锁) + +--- + +## 2. 逐轮还原(决策链与证据) + +### Round 1:全局走查(16:10–16:44) + +**PM 输入**:核心旅程走查(登录→首页→报告→PDF;试衣间/发型/我的/对话),6 维度(流程完整性/三态/文案/配额/隐私合规/一致性)。 +**PM 输出**(R1-pm.md): +- P0 ×3:#1 报告唯一入口在 AppStorage(重启即丢,后端 `GET /api/v1/reports` 现成未接);#2 doRegenerate 丢弃响应新 report_id(消耗 30 天 1 次的配额却永远看旧报告);#3 人脸照片无单独知情同意(违反合规方案 CR-01/FR-ACC-08) +- P1 ×3:#4 失败重试复用同 request_key(幂等重放原样失败→死循环);#5 消耗配额前无提示(违反"禁止静默扣减");#6 技术黑话 8 处 +- P2 ×6:#7 会话过期无出路 ~ #12 对话无快捷问题 + +**开发**(10 文件,新增 ReportListTab.ets / FacePhotoConsent.ets): +- #1:报告 Tab 从"建设中"占位换成真列表(组件级复用,不算新路由页,绕开"无底部 Tab 锁死用户"的约定冲突) +- #2+#4:采纳新 report_id;三处(create/regen/export)FAILED 后清幂等键换新键 +- #3+#5:同意弹窗(会话级)+ 四个消耗点事前确认(复用现有 quota 接口,额度 0 置灰+恢复时间) +- **计划外重大发现**:后端任务状态是小写(`succeeded`/`failed`),App 全按大写比较——**轮询完成检测恒假、"导出 PDF"按钮从未真正出现过**。真机注释里"轮询完成不自动跳转"就是它的症状。统一 toUpperCase() 归一修复。 + +**测试**(R1-test.md):门禁绿;建 `checks.mjs` 静态检查 1~7(跨页 import 禁令/路由注册/大小写/幂等键清理/同意门控/黑话/前后端路由比对);真机被 PIN 锁阻塞(只完成装包+启动);#1~#5 验收 ✅;新发现 N1~N4。**放行**。 + +### Round 2:AI 功能域(16:46–17:18) + +**PM 输出**(R2-pm.md,#13~#20,无 P0):#13 对话单轮失忆(M4-b 范围,只登记);#14 缺「AI 生成」标识(CR-05 法定,2025-09-01 已施行);#15 发送失败丢输入无重试未带幂等键;#16 试衣生成期锁屏即丢结果;#17 结果只活在签名 URL;#18 发型页列表态/保存误报;#19 调试页处置;#20 同意弹窗承诺无工程背书。 +**开发**(4 文件): +- ChatPage 四合一:AI 标识常驻(列表与输入区之间固定层)、4 个快捷问题 chips、额度 0 置灰+reset_at 恢复时间、失败回填+失败气泡重试+request_key(失败保留/重试复用/成功换新)、500 字上限 +- TryonPage:`setWindowKeepScreenOn(true)`(结束/取消/销毁恢复)、onBackPress 挽留确认、取消出口(Phase A 直连无服务端任务,文案如实说"不涉及额度扣减")、genSeq 防迟到响应串扰;TryonApi 加 cancelChangeCloth +- TaskPollingPanel:连续 3 次网络错误=「放弃轮询」不再上抛 onFailed(宿主幂等键不清,额度未退),面板内给"稍后在报告列表查看"引导;仅后端终态 FAILED 走失败流程 +**测试**:12 项静态检查 11 PASS(#6 黑话范围外单列);真机仍 PIN 锁;6 项验收全 ✅;新发现 N5(UTC 时区差 8h,波及 5 处)/N6(压缩期取消不彻底)/N7(额度耗尽不同步)。**放行**。 + +### Round 3:PDF 链路收尾(17:20–19:40,测试一度被中断后重跑) + +**PM 输出**(R3-pm.md,#21~#26 + 遗留池首轮裁决): +- P1 ×3:#21 PDF 下载前不删旧文件(OHOS download 不能覆盖已存在——**同报告第二次导出/重试必失败,且发生在额度已扣之后**);#22 loadReport 只清 regen 不清 export 幂等键(导出→重生成→再导出=IDEMPOTENCY_CONFLICT 死路,且后端 hint 说"任务中心"——App 不存在的页面);#23 PDF 失败态无出路+loadDocument 挂载时序风险+FR-RPT-12 分享长图(V1 P0)反而不做却先做了 V2 的 PDF(范围倒挂登记) +- 裁决:本轮修 #6 清尾/#7/N5/N2/#10 最小集;R4 修 #17/#18/N7/N6/N4/#24/#25;不修项给理由 +**开发**(17 文件 + 新增 Theme.ets;后端唯一授权改动 errors.py 两处 hint): +- #21 下载前 accessSync→unlinkSync;#22 loadReport 清两个幂等键;#23 PdfView 常驻挂载+遮罩式加载/失败态+重新加载按钮+分享另存文案拆分 +- #7:ApiTypes 加 `isAuthError()`(40100~40105),四处错误态加「重新登录」出路 +- N5:formatIsoTime 改 `new Date(s)` 本地时区解析(原手工截串);连带 ProfileContent 7 处时间归正 +- #6 十处逐条替换(含 PDF 文件名内嵌报告 ID 改「形象报告 vN」);N2 报告 Tab onChange+onPageShow 双触发刷新;#10 主色收敛 `core/Theme.ets`(15 文件替换两套主色)+返回控件统一+Tab3 文案 +**测试**(R3-test.md,重跑后完成):**真机解锁,PDF 全链首次完整走通**——登录→报告 Tab→导出确认→轮询(无黑话)→服务端渲染 350KB→下载→PdfView ready→另存→分享取消静默;**二次导出同报告(旧文件在场)成功——#21 unlink 修复实证**(旧代码必 2300037);19/19 静态检查;#21/#22/#23/…验收 ✅;新发现 N1'~N7'(PDF 元数据缺 AI 声明等)。**放行**。 + +### Round 4:结果可靠性(19:42–21:35) + +**PM 输出**(R4-pm.md):10 项修复清单(#17 试衣转存为首位,含"下载前 unlink"同款坑提醒;#18 与 #17 统一方案;N2'-① PDF meta;N1' 提示自清;#24 **修正 R3 的"6 卡"为 4 卡真实消耗集**——tryon/hairstyle 直连不经后端配额,上卡会恒显满额假象;#25 登录页内测口径;N7(正确码 40301,R3 写的 42910 是短信频控);#19 debug 守卫——发现门禁产物恒 debug,包裹≠隐藏,但 T18-REPORT 未产出前不能彻底删;#10 尾差)+ **40 项遗留池终审表**(已修 22/本轮 10/R5 修 3/登记不修 5)。新登记 #27(结果图无水印=Phase B 放量红线)、#28(T18-REPORT 未产出)。 +**开发**(8 文件 + 后端模板/单测): +- #17:生成成功即下载 `filesDir/results/tryon_last.jpg`(unlink 重入);展示/保存走本地副本(`fileUri.getUriFromPath`);aboutToAppear 回看「上次结果」卡;保存三态(photoAccessHelper 返回空数组=取消/异常=失败,d.ts:3570 判别);转存失败降级 URL+提示;N6 genSeq 复查加在压缩后 changeCloth 前 +- #18:发型列表三态(listLoaded 复位支持重试)、保存三态、结果落 hair_last.jpg;ImageCodec 抽 writeBase64File 助手,新增 base64ToPersistFile(filesDir,自动建目录),cacheDir 版保留作兜底 +- N2'-①:report.html.j2 加 author/description/generator meta(用现成 report_id/version/date_text 零新数据;**不伪造 27 位编号**——后端 compliance/service.py 明示 fail-close"half-way is worse");+2 单测(WeasyPrint 把 Info 字典放压缩对象流,测试用 zlib 解压后断言 `/Author` 等标记,不新增 pypdf 依赖) +- #24/#25/N7/#19/#10尾差 按清单落地;**BuildProfile 踩坑**:虚拟路径 import 不解析,改直引 `build/default/generated/.../BuildProfile`(clean 构建亦可用,任务顺序有保证,但 IDE 升级有脆性——已记录) +**测试**(R4-test.md,两段完成):前任 agent 真机实操 58 张截图(含相机拍照选图、飞行模式、杀 App、登出重登、PDF 另存)后**上下文超限挂掉**;接任 agent 不重复操作、只分析证据+跑门禁:三门禁绿、#18 列表三态/#24/#25/#19/N2'-① 真机或单测实证;**但发现 R4N1 [P0]:真机上 photoUris 已送达 App 而 humanUri 永不更新(await 链静默死亡)、cameraPicker resultUri 恒空——试衣/发型照片链真机全断**(存量缺陷,此前轮次照片链从未在真机走通过所以没暴露)。**有条件放行**。 + +### Round 5:终局(21:40–23:10) + +**PM 输出**(R5-pm.md): +- **R4N1 根因排序**(排除法):三个会进 catch 的假设被"从未出现失败 statusText"排除;H1(最可能)= `describe()` 内 `await getImageInfo()` 挂起不回调(ImageSource 懒加载+真机媒体路径,官方 FAQ 给过 getImageInfoSync 替代方案);H2 = picker Promise 提前 resolve 空/续体丢失(拍照路径"resolve 但 resultUri 空"实证此形态)。**事实修正**:报告页照片是"即将开放"预告没有接选图代码,修复面收窄为两页 +- **最小改动方案**:收口 ImageCodec 单文件——A 裁 describe(width/height 全仓零消费,纯风险零收益);B select/pick 套 Promise.race 超时;C createImageSource(uri) URI 直建;拍照降级预案预授权 +- R5 清单 4 项:R4N1 / N4 同意持久化+撤回 / N5' f2 换用 / statusText 1 行;**内测发布验收清单**(A12+B16+C8+D5 条) +**开发**(两任 agent:第一任被用户熄屏打断留下半成品,第二任核对发现四项其实已全做,补 checks 断言 25~28 + 跑门禁): +- ImageCodec:A/B/C 全落地 + MEIDA_IMG 打点 11 处;FaceConsentStore 镜像 AuthStore 的 preferences 先例(`{granted, version:'v1', grantedAt}`,init hydrate 校验版本,revoke 清空);ProfileContent 授权管理行+撤回确认框;ReportResultPage/ReportListTab 换 f2 LoadingView/ErrorView(保留 #7 重新登录出路);TryonPage 两处 statusText 清空 +**测试**(R5-test.md):三门禁绿、契约零越界;A1~A12 真机:**选图+拍照送达修通**(`gallery: select end, 1 uri(s)` 带真实 URI、`camera: pick end, resultUri length=63`)、N4 全链(杀 App 不弹/撤回后重现)、N5' 断网闭环、0 崩溃;换装/发型真实生成各 1 次止步压缩失败未触网(服务零消耗);**发现 R5 回归**:R5N2 [P0] `image.createImageSource('file://media/…')` 真机返回 undefined→toJpegBuffer 抛 TypeError→生成链全断(修复 C 引入);R5N1 [P1] TryonPage `@Builder PhotoSlot` 按值传参不响应 @State→预览永不显示(发型页直引写法正常)。**结论:未达内测门槛,差两个小修复**。 + +### 终局热修(主控亲手,23:10–23:40) + +- **R5N2**:ImageCodec 加 `openSource()`——先 `image.createImageSource(uri)`,真机返回空则 `fs.openSync(uri, READ_ONLY)` → `createImageSource(file.fd)`(华为官方文档模式);`closeSource()` 在 finally 里 release+关 fd(成功/失败都回收) +- **R5N1**:`@Builder PhotoSlot($$: PhotoSlotArgs)`(显式 interface,ArkTS 严格模式不许裸对象字面量作类型),调用点改对象字面量传参——按引用、@State 变化即刷新 +- 踩坑复现:第一次构建失败(arkts-no-obj-literals-as-types),补 interface 后绿;**install -r 不杀进程**——验证时发现跑的还是旧代码(ps 看启动时间),`aa force-stop` + 重启后才是新包 +- 真机实证:60s 超时兜底可感知(两次触发「读取照片失败」+hilog)、cameraPicker resolve 正常(退出=空 URI 取消语义)、N4 跨 force-stop 不重弹、0 崩溃;合成点击进不了"安全访问"选择器(设备限制,详见真机指南文档)——预览刷新与压缩链留一分钟人工验收 +- 静态检查项 26 更新为 openSource 双路口径后 **28/28 PASS**;提交 `0b5d151`(主仓)+ `00ef56b`(子仓) + +--- + +## 3. 修复全量清单(35 项速查) + +| # | 问题 | 轮次 | 修复要点 | 验证 | +|---|---|---|---|---| +| 1 | 报告重启即丢 | R1 | ReportListTab 接 GET /reports 替换占位 Tab | 真机 ✅ | +| 2 | 重新生成显示旧报告 | R1 | 采纳响应新 report_id | 静态 ✅ | +| 3 | 人脸照片无知情同意 | R1→R5 | 弹窗→preferences 持久化+版本+撤回 | 真机 ✅ | +| 4 | 失败重试幂等死循环 | R1 | FAILED 清 requestKey 换新键(三处) | 静态 ✅ | +| 5 | 静默扣配额 | R1 | 四消耗点确认框(代价+剩余+置灰+恢复时间) | 真机 ✅ | +| 6 | 技术黑话 10 处 | R3 | 逐处替换文案表 | 真机抽证 ✅ | +| 7 | 会话过期无出路 | R3 | isAuthError+四处「重新登录」 | 静态 ✅ | +| 10 | 主色两套/返回三套 | R3 | Theme.ets 唯一来源,15 文件替换 | 静态 ✅ | +| 12 | 对话无快捷问题 | R2 | 4 chips 对应需求文档四类意图 | 真机 ✅ | +| 14 | 缺 AI 生成标识 | R2 | 列表与输入区之间固定层常驻 | 真机 ✅ | +| 15 | 发送失败丢输入 | R2 | 回填+重试+request_key | 静态+契约 ✅ | +| 16 | 生成期锁屏丢结果 | R2 | keepScreenOn+挽留+取消+genSeq | 静态 ✅ | +| 17 | 试衣结果只活在 URL | R4 | filesDir/results 转存+回看卡+离线保存 | 静态 ✅/真机待人工 | +| 18 | 发型页三态缺失 | R4 | 列表三态+保存三态+结果转存 | 真机(列表/错误态)✅ | +| 21 | PDF 二次下载必败 | R3 | 下载前 unlink | **真机实证** ✅ | +| 22 | 导出幂等冲突死路 | R3 | loadReport 清双幂等键+后端 hint 改口 | 静态 ✅ | +| 23 | PDF 失败态无出路 | R3 | 常驻挂载+重试+文案拆分 | 真机 ✅ | +| 24 | 配额卡信息架构 | R4 | 4 张真实消耗卡+名称对齐 | 真机 ✅ | +| 25 | 登录页临时性呈现 | R4 | 内测徽章+账号标识话术+断网指引 | 真机 ✅ | +| N1 | 轮询网络误判失败 | R2 | 放弃≠终态,不清幂等键 | 静态 ✅ | +| N2 | 报告列表不刷新 | R3 | Tab 切换+页面重现双触发 | 真机 ✅ | +| N3/N7 | 额度 0 体验 | R2/R4 | 置灰+恢复时间;40301 同步 | 真机 ✅ | +| N4 | 同意不持久化 | R5 | preferences+撤回入口 | 真机 ✅ | +| N5 | UTC 时区差 8h | R3 | new Date 本地解析,7 处归正 | **真机实证** ✅ | +| N6 | 压缩期取消不彻底 | R4 | genSeq 复查移到 changeCloth 前 | 静态 ✅ | +| N2'-① | PDF 缺 AI 元数据 | R4 | 模板 meta+2 单测(zlib 解压断言) | 单测 ✅ | +| N1' | 保存提示常驻 | R4 | 3s 自清(白名单防误清) | 真机 ✅ | +| N5' | f2 组件零采用 | R5 | 两页换用,保留重新登录出路 | 真机(断网)✅ | +| #19 | 调试页暴露 | R4 | BUILD_MODE_NAME 守卫+「内部调试」文案 | 真机 ✅ | +| R4N1 | 真机照片链断裂 | R5 | 裁 describe+超时兜底+URI 直建 | 真机(送达)✅ | +| R5N1 | @Builder 不刷新 | 热修 | $$ 对象接口按引用传参 | 构建+机制 ✅/待人工点选 | +| R5N2 | createImageSource 空 | 热修 | openSource 双路(URI→fd 回退) | 构建+机制 ✅/待人工点选 | +| 状态大小写 | 导出按钮从未可达 | R1 | toUpperCase 归一(多处) | 真机(PDF 链)✅ | + +(#8/#9/#11/#13/#20/#27/#28 等=登记不修或后置,理由见 R4-pm.md 遗留池终审表与 FINAL-REPORT.md 第五节。) + +--- + +## 4. 关键技术知识点(学习重点) + +1. **幂等键生命周期**(request_key):发起前生成→失败保留→重试复用→成功作废;页面加载新实体(loadReport)时要清掉旧实体的键,否则跨版本操作撞 IDEMPOTENCY_CONFLICT。前后端契约:`ChatRequest.request_key` 描述即"相同键不会重复扣配额"。 +2. **ArkTS `@Builder` 传参语义**:原始类型按值传参不响应 @State;要响应式必须 `$$` + 显式 interface 对象字面量(且 ArkTS 严格模式禁止裸对象字面量作类型声明——会报 arkts-no-obj-literals-as-types)。 +3. **image kit 双口径**:`createImageSource(uri)` 对 `file://media/…` 在部分真机 ROM 返回空(不是抛错!);官方稳妥路径是 `fs.openSync(uri, READ_ONLY)` + `createImageSource(fd)`,fd 需调用方在 release 后关闭。`getImageInfo()` 在媒体 URI 上有整类"挂起不回调"风险(官方 FAQ 承认),能不用就不用、必须用就加超时。 +4. **OHOS request.download 不能覆盖已存在文件**(错误码 2300037)——下载前必须 accessSync→unlinkSync。 +5. **`install -r` 不杀进程**:验证新包前必须 `aa force-stop` + 重启,否则测的还是旧代码(用 `ps -ef` 的启动时间戳甄别)。 +6. **系统 picker 安全访问模式**:合成点击(uitest)可能完全无效;picker 空结果收场后可能残留不可见蒙层吞输入,`aa force-stop com.huawei.hmos.photos` 恢复。详见真机指南。 +7. **后端纪律**:Prompt 固定前缀不许含易变内容(缓存单价差 30 倍);DeepSeek 峰谷双价成本落库不重算;compliance fail-close(宁可不做标识也不伪造编号)。 +8. **WeasyPrint PDF 元数据在压缩对象流里**,测试断言要先 zlib 解压再 grep,裸字节 grep 不到。 + +--- + +## 5. 过程踩坑与复盘(下次怎么做得更好) + +### 踩坑 + +| 坑 | 后果 | 对策 | +|---|---|---| +| 测试 agent 真机实操 58 张截图+几十个 dump 全进上下文 | agent 上下文超限挂掉("Invalid string length") | 接任 agent 只分析**精选 16 张**关键帧;dump json 只 grep 不整读;视觉结论必须与 dump 文本/源码交叉验证(视觉模型对中文文案有幻觉) | +| R3 测试执行中被外部打断 | 报告没写、checks.mjs 半成品 | 重派时明确"接续半成品:核对→补完→产出报告",半成品价值不浪费 | +| 真机 PIN 锁连续三轮挡住交互 | 只能装包+启动+hilog | 第四轮用户解锁后一次清偿三轮欠账;**环境依赖要早发现早向用户求助** | +| 主控热修后直接装机验证 | 测到旧进程行为,一度误判修复无效 | 流程固化:install → **force-stop → start** → 验证(已写进真机指南) | +| agent 汇报"全做了"与实际半成品 | — | 第二任开发 agent 先 `git diff` 核对半成品清单再动手,避免重复或漏做 | +| Git Bash 路径转义吃掉 `/data/local/tmp` | hdc file recv 找不到文件 | `export MSYS_NO_PATHCONV=1`(详见真机指南) | + +### 做得好的 + +- **门禁双保险**(开发自跑+测试复跑)+ **静态检查资产滚动累积**(7→12→19→24→28 项,每轮修复都固化成断言,防回归) +- **PM 裁决制**:R3 起不再堆新问题,而是对遗留池逐条给"修/不修/排哪轮"+理由,5 轮收敛而不是发散 +- **开发 agent 被授权质疑 PM**:如 R4 把 R3 的"配额 6 卡"修正为 4 卡(假象比缺卡更糟)、R5 PM 修正"报告页照片受影响"的事实错误——角色对抗出真知 +- **红线写在任务书里**:换装/发型真实生成各 ≤1 次、对话 ≤2 条、不跑花钱 smoke——全程服务成本近零 +- **编号体系全局连续**,第 5 轮能产出完整 40 项终审表,最终报告有据可查 + +### 复用这套流程 + +1. `CONTEXT.md` 模板:项目结构/已验证命令/硬约束/角色权限——换项目只需改这一份 +2. 每轮 prompt 三段式:必读上下文(文件路径)→ 本轮主题与任务(含验收标准出处)→ 输出格式与落盘文件名 +3. 测试 agent 任务书必带:门禁复跑命令原文、静态检查脚本路径、"新问题喂下一轮 PM"的交接语义 +4. agent 挂掉的恢复策略:不重做,派"接任者"核对半成品→补完→出报告 + +--- + +## 6. 遗留事项(放量前) + +- **一分钟人工验收**(热修两项):试衣间选图看预览即时显示;点「开始试穿」看生成进入 +- **评审会落 owner**:#11 认同/修正+预约留资、#13 对话多轮上下文(M4-b)、FR-RPT-12 分享长图、FR-ACC-04 手机号登录、#20 即删背书、#27 结果图水印、#28 T18-REPORT +- **环境债**:BuildProfile 直引生成物路径偏脆(IDE 升级风险);picker 空结果蒙层(ROM 级,已记规避法) +- WSL 后端/worker/PG/Redis 仍在运行(供真机联调,停法见 FINAL-REPORT.md 第七节) diff --git a/docs/真机闭环自动化测试-实践指南.md b/docs/真机闭环自动化测试-实践指南.md new file mode 100644 index 0000000..49ac971 --- /dev/null +++ b/docs/真机闭环自动化测试-实践指南.md @@ -0,0 +1,290 @@ +# 真机闭环自动化测试 · 实践指南 + +> 日期: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`:`/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 shell "power-shell wakeup" # 亮屏(无锁时直接到桌面) +hdc -t 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 `(多设备安全);单设备可省。`` = 88Z9K26525009283。 + +### 2.1 装包与启停(★ 有一个致命细节) + +```bash +# 构建并安装 +cd /d/meida/app && cmd //c .build-gate.bat +hdc -t install -r "D:\meida\app\entry\build\default\outputs\default\entry-default-signed.hap" + +# 启动 +hdc -t shell "aa start -a EntryAbility -b com.xiangsilian.app" + +# ★★★ 验证新代码前必须重启进程:install -r 只替换包文件,不杀正在运行的进程! +# aa start 对已运行的 App 只是拉到前台——你测的还是旧代码(R5 热修当场踩过) +hdc -t shell "ps -ef | grep xiangsilian | grep -v grep" # 看进程启动时间戳甄别新旧 +hdc -t shell "aa force-stop com.xiangsilian.app" # 杀 +hdc -t shell "aa start -a EntryAbility -b com.xiangsilian.app" # 再启 → 这才是新包 + +# 杀图库(清"安全访问选择器"的残留蒙层,见 §5.3) +hdc -t shell "aa force-stop com.huawei.hmos.photos" +``` + +**标准节奏:build → install -r → force-stop → start → 验证**。 + +### 2.2 输入注入 + +```bash +hdc -t shell "uitest uiInput click 576 2340" # 点击(像素坐标) +hdc -t shell "uitest uiInput longClick 576 2340" # 长按(注意:是 longClick 不是 longPress) +hdc -t shell "uitest uiInput doubleClick 576 2340" +hdc -t shell "uitest uiInput swipe 576 2000 576 800 600" # 滑动(velocity 200~40000) +hdc -t shell "uitest uiInput keyEvent Back" # 返回键(也支持 Home/Power/数字) +hdc -t shell "uitest uiInput inputText 576 1200 hello" # 对已聚焦输入框打字 +``` + +打错命令时 uitest 会打印完整 usage(比猜语法快)。 + +### 2.3 观察通道一:截图 + +```bash +hdc -t shell "snapshot_display -f /data/local/tmp/shot.jpeg" +hdc -t file recv /data/local/tmp/shot.jpeg "D:\meida\.scratch\shot.jpeg" +``` + +截图按轮次留档(`shots-rN/序号-场景.jpeg`),既是证据也供事后分析(R4 测试挂掉后,58 张截图让接任 agent 零重复完成判定)。 + +### 2.4 观察通道二:UI 树(精确定位靠它) + +```bash +hdc -t shell "uitest dumpLayout" # 不带 --p,输出到 /data/local/tmp/layout_.json +# 输出文件名在 stdout:"DumpLayout saved to:/data/local/tmp/layout_85596362629.json" +hdc -t file recv /data/local/tmp/layout_.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 shell "hilog -r" # 清缓冲(测前清一次,判定干净) +hdc -t 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 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 手操"升级为"一条命令跑回归"