Files
STM32F4-Base/docs/UI设计/UI_自动化测试流程.md
T
super_agent_bot bbfb47ebd0
CI / 静态门禁 (pre-commit) (pull_request) Failing after 6m26s
CI / 主机侧单测 (MSVC) (pull_request) Failing after 43s
docs(ui): 新增 board-ui-nav 唯一入口技能并在 AGENTS 布防(#69)
板端交互看图/注键/开页一律走 scripts/board_ui_nav.py,禁止另写
一次性注键脚本:#38 调试中因临时脚本盲注 d*9 走错页复犯(#69),
盲走事故当时已立"不猜步数"铁律但入口没布防。技能写明 --where/
--to/--goto/--selftest-quick 用法、STATUS/GOTO 语义与拒绝码、
恢复配方、台架锁边界与 GOTO 权限边界。

UI_自动化测试流程.md:§7.1 记 #69 复犯案例与像素路径存量误判基线
(记录查询 6 叶在 selftest 闭环里恒红是存量,勿当 #69 回归);
§7.3 补 --where/--goto;§7.4 记 #69 收口(剪贴弹窗依赖已由
STATUS depth 顶掉,KNOWN_UNREACHABLE 销项待台架复核);
新增 §7.6 STATUS/GOTO 契约、判稳改造与门控说明。
2026-11-02 22:22:15 +08:00

248 lines
21 KiB
Markdown
Raw 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.
# UI 自动化测试流程(模拟器回归)
> **用途**:所有涉及 UI 的改动(菜单结构 / 内容页 / 字体 / 样式 / 几何)改完代码后,按本文跑一遍全部门禁再提交。脚本已入库,直接调用即可,无需临时重写。
> **定位**:`UI_界面制作流程.md` P2/P3 门禁的可执行版本;设计阶段(P0 规格 / P1 编码)走 `UI_界面制作流程.md`。
---
## 一、前置条件
| 项 | 说明 |
|---|---|
| 模拟器已构建 | 仓库根目录:`& "simulator\build_sim.bat"`(自动探测本机 VS2019/2022/2026 生成器,见 `simulator/README.md` §3.2) |
| Python | 统一入口 `.venv\Scripts\python.exe`(AGENTS.md「Python 环境」);两个回归脚本纯标准库,任意 python3 均可,为保持一致一律用 .venv |
## 二、流程总览
```powershell
# 仓库根目录执行(python 统一用 .venv,见 AGENTS.md「Python 环境」)
cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警告)
.venv\Scripts\python.exe simulator/tests/check_menu_rows.py # 1) 菜单 8 状态像素回归 + 池基线门禁
.venv\Scripts\python.exe simulator/tests/check_content_pages.py # 2) 内容页黄金参考回归 + 往返池门禁
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 3) Keil 构建(BUILD_OK,0 错 0 警)
.venv\Scripts\python.exe scripts/check_coding_standard.py # 4) 代码规范机械检查(PASS)
```
| 步 | 脚本/命令 | 门禁内容 | 预期输出 |
|---|---|---|---|
| 1 | `simulator/tests/check_menu_rows.py` | 菜单 8 状态:选中行白带 / 非选行黑底白字 / 框体 1px 边框 / 往返像素一致(MENU-STATE-03==MENU-STATE-01、MENU-STATE-06==MENU-STATE-01、MENU-STATE-07==MENU-STATE-02)+ MENU-STATE-00 池基线 | `PASS: 8 状态全部断言通过(8 图,池基线 5368 B)` |
| 2 | `simulator/tests/check_content_pages.py` | ① 文本级断言(字形模板匹配,正向 444 条 + 反向 45 条,独立于 `ref/`)→ ② 66 张截图与 `ref/` 黄金参考**逐字节**比对 + 开页-关页往返池水位 | `PASS: 66 内容页 + 往返池水位全部通过`(文本断言先于像素比对,失败即中止) |
| 3 | `keil-build` skill(`keil_build.py`) | MCU 端编译(UI 源码双端共用,改一端必须过另一端) | 退出码 0 = BUILD_OK |
| 4 | `scripts/check_coding_standard.py` | 代码规范 V1.0 机械检查 | PASS |
> **文本级断言层**(2026-09-10 新增):`check_content_pages.py` 内置字形模板匹配——直接解析
> `ui_font_cn_12.c` 的 cmap/glyph_dsc/bitmap,把期望字符串(标题/标签/值/页脚,含新增易缺字文案)
> 渲染成墨点集合,在截图对应 y 带内做精确匹配(LVGL 基线对齐)。价值:**缺字显示空白 / 标签写错**
> 这类回归不再只靠像素位图(那需要刷参考,且无视觉后端时难以判读);文本断言不依赖 `ref/`,且
> **先于**像素比对运行,失败即中止(不误刷参考)。文本断言覆盖确定文案,像素参考仍负责几何/样式/
> 光标反显等其余视觉。规模与覆盖面(2026-10-26 实跑 `check_content_pages.py` 取数):文本断言 **310 条**、覆盖当时 40 页中的
> **37 页**——`pwd` / `pwe` / `pwc` 三张密码态只进像素比对、未登记文本断言,改这几页的文案没有文本层兜底。
> 2026-10-28 再取数(接线道,`PAGES` 60 项时实测):正向 **425 条** / 反向 **36 条**、覆盖 60 张中的
> **57 张**(仍缺那三张密码态)。更正留痕:上一句登记的是「2026-10-26 实跑取数 310 条」,而当时脚本
> 的真值是**正向 380 / 反向 29**——历史行按当时冻结不改数,差值只在这里说明,避免下一个人再抄一遍旧数。
**退出码约定**:两个脚本均为 0=PASS / 1=断言失败 / 2=环境错误(exe 缺失、参考缺失)。
失败时脚本打印差异明细(文本断言未命中项 / ink runs 对照 / 像素差数),内容页脚本保留现场截图于临时目录并提示路径。
## 三、内存水位门禁常量
| 常量 | 所在脚本 | 当前值 | 含义 |
|---|---|---|---|
| `MENU_POOL_BASELINE` | check_menu_rows.py | **5368 B** | 菜单根框建完的 LVGL 池 used(x64 模拟器) |
| `POOL_AFTER_ROUNDTRIP` | check_content_pages.py | **5448 B** | 开页-关页往返后水位 = 基线 + 一次性残留 80 B |
> 水位历史:4784 →(2026-09-14 待机模块常驻 +656)5440 / 5520 →(2026-10-11 裁剪 LVGL 控件类 −72)5368 / 5448。
> 后一次漂移已核实为**有意且更优**:像素逐字节未变(**裁剪当时黄金参考只有 11 页**,在裁剪**前**生成、裁剪后全部一致;该集合现已扩至 66 张),
> 且一次性残留仍是 80 B(5448 − 5368)→ 泄漏特征未变,只是 `lv_theme_apply` 不再为已不存在的控件类登记样式条目。
**漂移处理**:菜单/页面结构或样式变更导致门禁失败时,先确认新值合理(残留分解见
`菜单页面设计.md` §9.5),再更新脚本常量,并在上面的常量表里把当前值与它的实测来源写清(改了什么是正文说了算,过程查 `git log`)。**禁止为过门禁盲目改常量**——
无法解释的漂移 = 内存回归 bug。
## 四、黄金参考(入库)与目检 PNG(本地产物)
`simulator/tests/ref/*.bin`(像素黄金参考,66 张 × 3200 B)**随仓库入库**;派生的 `.png`
目检图仍是本地产物(`.gitignore`)。入库的理由:比对是**逐字节**的,基线不入库时任何新克隆
或新工作树跑 `ui-golden` 都必然 FAIL(缺基准),这条门禁就只在"有老副本的机器"上真实存在。
可复现性实测(2026-10-22):两份各自独立构建的 `lvgl_sim`(不同构建目录、相隔 11 天的代码)
对同一批 11 个 `.bin` **SHA-256 全部一致** → 渲染是 CPU 侧确定性输出,基线可以进仓库。
换机后**不需要**再生成基准;只有有意改视觉时才 `--update`(改动会进 `git diff`):
```powershell
.venv\Scripts\python.exe simulator/tests/check_content_pages.py --update # 生成 ref/*.bin
.venv\Scripts\python.exe simulator/tests/regen_ref_png.py # 生成 ref/*.png(--scale 6 --invert)
```
有意改视觉时的流程:
1. **先肉眼确认新效果正确**:窗口模式运行 `simulator\build\Debug\lvgl_sim.exe`,或刷新后跑
`regen_ref_png.py` 看图对照 mockup;
2. 刷新参考:`.venv\Scripts\python.exe simulator/tests/check_content_pages.py --update`(重写 `ref/*.bin`);
3. **重生成目检 PNG**:`.venv\Scripts\python.exe simulator/tests/regen_ref_png.py`。
`.png` 是本地调试产物、**不会**随 `.bin` 自动更新——不重跑则 PNG 仍是旧渲染(缺字/错位,看似"乱码")。
默认 `--scale 6 --invert`(深字白底便于目检);`--no-invert` = 白字黑底(与设备一致);
4. **提交 `ref/*.bin` 的改动**(它已入库,等于改基准):`git diff --stat simulator/tests/ref` 必须
**误刷保护**:`--update` 会**先跑文本级断言**,断言失败即中止,避免把缺字/错标签的渲染"刷成"新基准。
**何时不该更新**:未有意改视觉而某页 FAIL → 是 bug(缺字、几何漂移、样式回归),修代码而不是刷参考。
菜单状态无黄金参考文件——其断言是结构性的(白带占比/边框/往返一致),不依赖入库位图。
## 五、误报排查
| 现象 | 常见原因与处置 |
|---|---|
| 多页大面积像素差异 | 字体重生成(`ui_font_cn_12.c` 变化)→ 确认字形正确后 `--update` 刷参考 |
| 单行/单字差异 | 几何或文案变更 → 先判断是否有意修改;无意 = bug |
| 池水位门禁失败但像素一致 | 样式/结构变更(如动态样式转常量、增删框体)→ 确认新值合理后更新常量并登记 |
| **只有 `netcfg_save` 一页失败**(保存行值列是 `ERR` 而非 `OK`),其余 26 页全过 | **不是代码回归,是工作目录被污染**:模拟器按**当前工作目录**读写 `sim_config.ini`,而 `--cfgtest` 超长守卫的场景 2 会伪造一份 4000 B 纯注释"雷"文件、并断言"拒写后原文件不动"——**连通过路径也会把它留在原地**。之后每次运行都加载它 → 外科手术保存要在 4000 B 注释后补写受管段 → 输出必超 `CFG_FILE_MAX_LEN` → 引擎**按设计拒写** → 保存行显示 ERR,与"工厂默认态"的黄金参考对不上。处置:两套回归已改为**在空临时目录里跑**(传 `cwd=tmp`),`--cfgtest` 已加外壳无论成败一律删除伪造档;手工跑 `lvgl_sim` 时删掉当前目录的 `sim_config.ini` 即恢复。**禁止为此刷黄金参考**(参考是对的) |
| `找不到 lvgl_sim.exe` | 模拟器未构建或 build/ 被删 → 先按 §一 重建 |
| 终端输出中文乱码 | PowerShell 代码页渲染问题,不影响判定(看 PASS/FAIL 与退出码) |
## 六、与其他文档的关系
- `UI_界面制作流程.md`:P0 需求录入 + 设计规格 → P1 规格→代码 → **P2 本文回归** → P3 MCU 上机目检 → P4 归档
- `菜单页面设计.md` §9:内容页对象树 / 几何表 / 内存预算(本文数字的来源)
- `simulator/README.md` §3.5/§3.6:无头模式(`--keys`/`--screenshot`)与回归脚本说明
## 七、板端有状态导航判据(真机侧,`scripts/board_ui_nav.py`)
> 前五节都在模拟器里跑;模拟器与固件同一份 UI 源码,像素判据在 PC 上就成立。本节管另外
> 一半:**真机上把页面走到、并证明走对了**——这一步只有真机需要(模拟器侧把键序直接喂给
> 无头进程,状态是确定的)。
### 7.1 为什么必须闭环(实测 2026-10-25 大窗)
- RemoDispBus 的 **KEY 应答是空帧、不携带任何状态**:发了 5 个键不等于到了第 5 层。
- 靠预置键序盲走,9 个「应当到达」的态里只有 3 个真是那个态:
| 留档帧 | 文件名声称 | 实际是 | 墨量 | 证据 |
|---|---|---|---|---|
| 00 / 01 / 02 | 根菜单 / 装置信息 / 交流量 | 名副其实 | 3226 / 3476 / 3346 | 认屏一致 |
| 03_dc | 直流量 | 菜单级联(整屏无页标题) | 4961 | [blindwalk_cascade](../测试说明/测试数据/boot_log_data/20261025_uinav_blindwalk_cascade.png) |
| 04_yx / 05_yxp2 / 06_soe | 遥信量两页 / SOE记录 | **密码输入页** | 512 / 515 / 512 | [blindwalk_pwd512](../测试说明/测试数据/boot_log_data/20261025_uinav_blindwalk_pwd512.png) |
| 07_vparam | 参数查看页 | 网口设置页 | 3774 | 标题带命中「网口设置」 |
| 08_vset | 定值查看页 | 装置信息页 | 3474 | 标题带命中「装置信息」 |
那三帧的 512 墨量曾被记成「神秘空白态」;与黄金参考 `shot_pwd.bin`、`shot_pwe.bin`(墨量
同为 512)逐字对得上之后可以定性:**走带 `#P` 注记的叶子时,第一屏是要密码,不是内容页**
——写路径判据(批 5)因此必须把「过密码」当显式状态处理,口径见 §7.5。
- 结论:判据脚本只能信画面。脚本因此**每按一键重新认屏**再决定下一个键,且「到达」只由
**页面身份**裁定;中途任何假设都写进轨迹,绝不当成结论。
- **2026-11-02 #69 复犯与收口**:修 #38(回放页枚举恒空)的会话里又走了弯路——当时没有走
本脚本,而是临时手搓 `board_walk*.py` 盲按,落错页后反复重来,浪费大量调试时间。四条根因
(协议无身份回读 / `sleep(0.30)` 抓帧拿到 N−1 屏 / 待机·光标·被裁弹框三个物理陷阱 / 技能
路由缺位)与成熟项目对照(accessibility 树优先于截图、auto-wait 断言、可复用工作流)见
issue #69 评论区;前两条已在代码里收口(§7.6 的 STATUS 与排空双帧),后两条靠
**`board-ui-nav` skill** 的「唯一入口」纪律兜住。
### 7.2 认屏的三条判据(精确字形匹配,复用同一份 `ui_font_cn_12`)
| 态 | 判据 | 为什么这样判 |
|---|---|---|
| 内容页 | 标题带命中 + 该页黄金行文本至少一条 + 页码行「第 N 页,共 M 页」 | 只认标题会把两屏认成一屏(「参数」既是菜单项也是页名);页码让「遥信量第 2 页 / 共 5 页」成为可比对的事实 |
| 菜单 | 在白底横条(反白选中行)覆盖的行范围内、**整幅宽度**找反白标签;长标签优先,命中框被包住者丢 | 选中行是黑字白底,只按正常极性永远读不出选中项;「参数定值」那行必然同时落进子串「参数」「定值」,不去重就凭空多两列 |
| 密码页 | 「用户密码」四个字里中三个 | 整串匹配实测差约 1 px 打不中;数字格随输入变,不参与判定 |
另两条实测纪律(理由写死在代码注释里,此处不重述):
- **收级联列按 `l`、不按 `s`**:菜单态按 `s` 不收列,用 `ssssssss` 回根正是深页错位的来源之一;
- 菜单树的 `#P` 注记可能写在 `#page=` **之前**(`网口设置 #P #page=net_cfg`),解析顺序错了就
剥不掉,那一叶的页面 id 也永远认不出来。
### 7.3 用法与门禁档位
```powershell
# 认屏快检:66 张黄金参考(+ 若有台架留档帧),秒级,不需要构建产物
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --selftest-quick
# 完整自检:再加全绑定页闭环导航(需模拟器可执行文件,约 30 s)
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --selftest
# 台架窗口:一条命令走完全部绑定页并逐页存证
# ⚠ 真机动作必须显式确认已持 bench 锁,否则脚本直接拒绝(不碰网络,退出码 3)
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --walk --i-hold-bench-lock
# 只走一页并抓图;--trace 落步迹、--dump-fail 落「认错的那一屏」,--outdir 改存证目录
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --board --to yx --grab D:/tmp/yx \
--i-hold-bench-lock --trace D:/tmp/uinav_trace.json
# 我在哪(固件 STATUS 身份 + 像素双口径)/ bench 固件直开页(#69,见 §7.6)
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --where --i-hold-bench-lock
& '.venv\Scripts\python.exe' scripts/board_ui_nav.py --goto wave_play --grab D:/tmp/wx \
--i-hold-bench-lock
```
- 提交钩子 `uinav-selftest` 跑的是**快检档**(`--selftest-quick`)。为什么不挂完整档:闭环
那一段要模拟器可执行文件——它是构建产物,新工作树里没有,正是同一天 `mem-numbers` 缺
`.map` 那课的形状。脚本对「缺可执行文件」报 FAIL 并给可执行提示,不「跳过当通过」;
合入前 / 上板前请手工跑一次完整档(快检 ≠ 闭环,两者结论不同,别互相顶替)。
- 板端后端只连 7003 取图注键,**不含串口、烧录、复位**;`--board` / `--walk` 必须另加
`--i-hold-bench-lock` 才会去连——没给就直接拒绝(退出码 3,不碰网络)。这条不是防御性
设计,是**同一天踩出来的**:跑 `--walk` 想看一眼默认存证目录打印成什么,脚本就照着连上
板子注入了按键,而那时台架属于验收窗的准备方。「必须持锁」写在文档里防不住「我只是跑
一下」,所以把确认做成命令行参数:要动真机,就得在命令行里留下字面证据(脚本不代取锁,
也不替人判断「应该没人在用」)。
- **存证不进工作树**:`--walk` 的帧默认落 `ci-artifacts/uinav/walk/`(版本控制之外),
`--outdir` 可改;给的路径若落在版本控制内(且不在 `ci-artifacts` 之下),脚本**直接拒绝
执行**并退出 3。理由与提交门禁同一条:产物一旦写进工作树,`git status` 就不再是判据,
而「记得删」只防得住记得的人。
- 步迹只在 `--trace <路径>` 给出时落盘;认错那一屏只在 `--dump-fail <目录>` 给出时留存——
两者都指到 `ci-artifacts/` 下,窗口结束把工作目录交回来时工作树仍是干净的。
- 台架留档帧目录在 gitignore 内、不入库:缺就整段跳过,不算失败——但脚本把「没跑」与
「跑过且通过」分开打印,不把前者写成后者。
### 7.4 已知限度(别把它当万能闸)
1. **根列选中落在最后两行时,子菜单弹框会被屏幕裁掉**:选带不在可扫窗口里,列数会漏计。
脚本对策是「枚举 + 验终点」——末列先盲按 N 次下移再进(第 N 次尝试),成败仍只由页面
身份裁定,所以它会大声报错而不是静默走错页。实测 `自检记录` / `运行记录` 两页在模拟器
上 5 次枚举内没吃到,现场帧见
[popup_clipped](../测试说明/测试数据/boot_log_data/20261025_uinav_popup_clipped.png);
**真机上那层弹框究竟画在哪,留台架窗口取证后再定修法**——这是取证题,不是编码题。
#69 之后:真机(新固件)侧该漏计已被 STATUS 的 `depth` 事实顶掉——"回根"直接认
`depth==0`,不再依赖数得到列数;`KNOWN_UNREACHABLE` 两条按对称设计留待台架走到后销项。
2. 同一标题的多个显示态(网口设置的 只读 / 编辑 / 保存)在分类里都是同一个页面 id
`net_cfg`;要区分得看行内文本,写路径判据需要时再加。
3. 认屏复用黄金参考的文案:改了页面文案而没同步 `simulator/tests/` 的文本断言表,本节脚本
会一起红。那是**有意的耦合**(两处必须同时改),不是脚本脆弱。
4. 快检档只证「认屏认得对」,不证「导航走得对」;后者只有完整档的闭环那一段会响。
5. **纯像素路径对"子列标签 ≡ 页标题"的级联会误判**:记录查询子列里的 事故记录 / 操作记录 /
报警记录 / 启动记录 / 遥控记录 / 运行报告 等标签与对应页标题同字,开子列那一屏会被判成
PAGE——`--selftest` 闭环因此对 记录查询 下 6 叶(五本记录 + 录波回放)恒报「进入 记录查询
后不是菜单态而是 PAGE」(2026-11-02 对 master 基线复跑确认为**存量**,非 #69 引入;
`clear`/`reset` 两页的"失败"是执行类硬闸的刻意拒绝,另计)。真机新固件侧 STATUS 会把它
纠成 MENU(trace 里留"像素与 STATUS 不一致"痕);模拟器侧无 STATUS,这 8 项维持红灯直至
像素判据补上级联上下文——**别把它们当 #69 的回归**。
### 7.5 过密码的口径(与黄金回归同源,只有一套走法)
查看密码 = 出厂 `000`(直接 `e` 确认);修改密码 = `123`,键序 `u,r,u,r,u,r,e`,与
`simulator/tests/check_content_pages.py` 的 `netcfg_edit` 态**完全一致**——刻意不另发明一套走法
(两套走法迟早分叉,而分叉的那一边往往是真机)。导航器**默认只过查看密码**:判据需要写
路径权限时必须显式传 `modify=True`,**不因「能进去」就自动升格成可写**——只读判据偷偷拿到
修改权限,等于把判据的边界让给了实现。
### 7.6 身份回读 STATUS 与直开页 GOTO(#69,2026-11-02)
旧协议四个命令(KEEP/INIT/KEY/LCDMEM)里没有一个是"我是谁",闭环只能靠像素反推——本节
判据的全部不确定性都源于此。#69 在 7003(RemoDispBus)上补了两条,两端共享同一份契约头
`Drivers/BSP/UI/ui_net_sink.h`(**布局以该文件注释为唯一权威**,此处只记语义):
| 命令 | 语义 | 生产固件 | bench 固件 / 模拟器 |
|---|---|---|---|
| STATUS(4) | 直读固件导航态:kind(MENU/PAGE/STANDBY,待机先判)+ `depth/path/sel`(`ui_menu.c` 的 `s_*` 快照,经 `ui_menu_get_nav_state()`)+ 当前内容页 id(`ui_content_page_current_desc()`) | ✅ 支持(只读、零画面副作用) | ✅ |
| GOTO(5) | 按页 id 直开页:查 `g_ui_page_map`,以查看权限 `UI_PERM_VIEW` 开页并回节点号 | ❌ 编译期闸外,恒回 status=4(不支持) | ✅ 受理(闸 = `DTU_BENCH` 或 `UI_REMO_ALLOW_GOTO`) |
- **主机侧优先问、像素兜底**:`Navigator` 每次认屏先 STATUS、后像素,两边打架以 STATUS 为准
并在 trace 留痕;老固件不回 STATUS 时打印一次并永久退回纯像素路径。`--where` 现场打印双
口径;`--goto <页 id>` 直开后仍走与 `--to` 相同的终点双验(STATUS + 像素),拒绝码 1 无此
页 / 2 执行类页 / 3 开页失败 / 5 待机态。
- **KEY 后稳帧已代码化**(规则①不再是纸面纪律):`key()` = 读掉 ACK → 排空到帧流静默
≥900 ms(上限 5 s)→ 连取两帧比对,不等重试至多 8 次并在 trace 标 `stable=false`
(时钟页秒针恒在走,属预期)。
- **GOTO 的边界**:只用于调试取证——它按查看权限直达,**不弹密码**,因此不是"过密码"的旁路
写路径;`clear`/`reset` 主机与固件双侧默认拒绝(直开即到达可执行态,没有第二段确认的
余地)。MCU 侧要拿到 bench 能力须临时给 `MDK-ARM/STM32F407-Demo.uvprojx` 加 `DTU_BENCH`
宏烧录,测完**必须还原宏并重烧产品构建**(uvprojx 抖动不入库,同构建节律)。
- **留档帧目录已并一**:`WALK_DIR` 与 `--walk` 存证目录同指 `ci-artifacts/uinav/walk/`
(#69 前指 `ci-artifacts/uiwalk/`,走的新帧 BOARD_TRUTH 段永远读不到,静默"跳过(无
留档)")。