为什么 8 阶段(e2e + 真机补编译层盲区)
何时读这篇:当你想搞懂「为什么是 8 阶段」「编译过了为什么还不能直接声称修完」时。对应 SKILL.md 的 §8 阶段验证 与 §本地开发:哪些验证必要。
本 skill 验证 8 阶段(构建 / 类型 / lint / 单测 / e2e 功能 / 真机 / 安全 / diff)。其中 1-4 是编译层(前置),5 e2e + 6 真机是核心完成线。这页讲为什么必须补 5/6,不能只靠 1-4。
编译层(1-4)证明什么、不证明什么
| 阶段 | 证明 | 不证明 |
|---|---|---|
| 1 构建 | 代码能编译、依赖能解析 | 产物运行起来对不对 |
| 2 类型 | 类型签名一致 | 运行时值对不对 |
| 3 lint | 代码风格 / 静态规则 / 依赖无环 | 逻辑对不对 |
| 4 单测 | 单元逻辑(隔离)对 | 组合起来、真实环境下对不对 |
1-4 全过 ≠ 功能可用。编译层只检查「代码本身」,不检查「代码跑起来」。
e2e 功能验证(阶段 5)补什么
e2e(Playwright / 等价)驱动应用,断言功能结果:
- Reader:打开 PDF → canvas 像素非空 +
textLayerStatus ≠ unknown(真渲染 + 文字层检测成功)。 - 交互:点击设置 → 面板真的 open;mode 切换 → 对应 panel 出现。
- 服务:请求 → 响应 body 内容正确(不只 status 200)。
e2e 抓的是「功能结果」——编译层抓不到的「代码跑起来对不对」。
真机验证(阶段 6)补什么
dev server e2e(localhost)还不够。真实运行时(Tauri WKWebView / Web build 产物 / 服务 staging)行为可能不同:
- 桌面:WKWebView 原生行为 ≠ dev server(Chromium);worker / 协议 / 路径在 prod 才暴露。
- Web:dev server 热重载 ≠ build 产物(打包后路径/worker/分包不同)。
- 服务:本地 mock ≠ staging(真实 DB / 网络 / 凭据)。
真机抓的是「真实运行时行为」——dev e2e 抓不到的 prod-only 问题。
完成线 = e2e + 真机过(不只编译层)
| 场景 | 1-4 过 | 5 e2e 过 | 6 真机过 | 完成? |
|---|---|---|---|---|
| 改类型/重构 | ✅ | 可省(无功能变) | 可省 | ✅(纯类型) |
| 改功能逻辑 | ✅ | ❌ | — | ❌(e2e 没过) |
| 改功能逻辑 | ✅ | ✅ | ❌ | ❌(真机没过,可能有 prod-only bug) |
| 改功能逻辑 | ✅ | ✅ | ✅ | ✅(完整) |
功能改动必须 5 + 6 都过(或 6 有充分 NOT_RUN 原因),才算 behavior-complete。
反例(只靠编译层的坑)
- 改 reader worker 加载:typecheck / build / lint / 单测全过 → 声称「修完」→ 实机打开 PDF「文字层未知」(
textLayerStatus卡 unknown)→ 崩。编译层全绿,功能实际没修好。只有 e2e(打开 PDF 断言 textLayerStatus)能抓到。 - 改拖入逻辑:编译层全过 → 实机拖文件无反应(系统拖拽不走 HTML5
dataTransfer)。只有真机(或 mock 事件 e2e)能抓到。
这两类坑,1-4 抓不到,5/6 能抓。这就是 8 阶段的理由。
回到 SKILL.md:日常改功能到底该跑哪些 → 见 §本地开发:哪些验证必要(最低清单);Tauri 项目的 e2e/真机具体做法 → 见
references/e2e-practice.md与references/lessons-from-practice.md教训 3/7。