文件
hyb-oa/.trellis/tasks/08-10-pwa-cross-version-cache-upgrade-validation/design.md
T

5.3 KiB

PWA 跨版本缓存升级验证:技术设计

1. 判定对象

本任务验证的是一个状态迁移,不是两个独立页面截图:

same loopback origin + same BrowserContext
  -> serve build A
  -> A page loads
  -> A SW ready + controller + precache proven
  -> close A page, retain context/profile
  -> atomically switch origin to build B
  -> revisit same protected route
  -> real SW update code runs
  -> document/controller/cache/session converge to B

任何直接从空 context 打开 B 的路径都只能是 harness sanity,不参与最终判定。

2. 产物生成

  • runner 在 OS 临时目录创建 build-a/、build-b/,不覆盖 client/dist。
  • 两次均从当前 checkout 构建,只注入不同的 synthetic 12 位 SHA。构建后读取 index.html 与生成资产,断言:
    • document/runtime 可观察到各自 SHA;
    • sw.js 存在;
    • A/B hashed JS/CSS 至少有一个差集。
  • runner 使用 trap/finally 精确清理本轮临时目录;失败日志只输出相对路径和计数。

选择同源码不同 SHA,而不是 checkout 历史 commit,原因是本轮要隔离“SW 缓存版本切换”这个变量。build SHA 会进入 core bundle 并改变 Workbox revision;历史代码差异会同时引入 API、路由、fixture 与依赖变量。

3. Loopback HTTP harness

Playwright spec 进程内启动只监听 127.0.0.1 的 ephemeral HTTP server,持有一个 activeBuild=A|B 状态:

  • /sw.js、/index.html 与 SPA navigation:从 active dist 读取;HTML/SW 使用 Cache-Control: no-cache。
  • hashed JS/CSS/image:从 active dist 读取并使用 immutable cache header。
  • /ready:返回 active synthetic build SHA,Cache-Control: no-store。
  • /api/auth/refresh、/api/me、navigation、notifications 与 portal 最小接口:返回合成 envelope,不触碰数据库。
  • 路径解析必须防止越出 active dist;未知静态文件 404,未知 API 返回受控合成结果。
  • switchToB() 为测试进程内同步状态切换,不暴露公网管理端点;同一 server/socket/origin 始终不变。

请求 ledger 只保留 method/path/status/build,并单独统计 JS/CSS 404、refresh/me 与 main-frame navigation。

4. 浏览器 witness

Phase A:建立旧 profile

  1. 创建唯一 BrowserContext,显式 serviceWorkers: "allow"。
  2. 安装 synthetic has_session cookie;API refresh 返回合成 token,/api/me 返回合成员工。
  3. 访问 /portal,确认 document SHA=A、route 保持 /portal。
  4. 等待 navigator.serviceWorker.ready;A 阶段允许且仅允许一次显式 page.reload(),使首次安装 worker 成为 controller。
  5. 断言 controller 存在、A precache/cache entry 存在;保存 A-only hashed asset 相对路径集合。
  6. 关闭 A page,但不关闭 context。

Phase B:部署后重访

  1. server 原位 switchToB(),同时切换 /ready 和所有静态文件。
  2. 在同一 context 创建新 page,重新访问 /portal。
  3. 从这一刻起禁止测试代码调用 reload/clear/unregister;只等待应用真实 autoUpdate/clientsClaim/更新回调。
  4. 记录 controllerchange,必要时通过 Chromium CDP ServiceWorker domain 对比 A/B active worker versionId;scriptURL 预期始终为同 origin /sw.js,不得把恒定 URL 误写成版本证明。
  5. 将 B 阶段 no-cache 获取到的 /sw.js 内容 SHA-256 与 build B 文件指纹对账,并联合断言 document SHA=B、B-only chunk 已成功加载、route 仍为 /portal、refresh/me 成功、controller 存在。
  6. 安全记录 hyb-oa:pwa-update-ready 触发次数;若 autoUpdate 流程实际进入 onNeedRefresh,保留阶段与后续行为证据。
  7. 读取 Cache Storage 的 URL pathname 白名单,断言 A-only JS/CSS 消失,B asset 已进入或已成功请求。
  8. 断言无 /login、无 JS/CSS 404、无 chunk/preload/page error、导航次数有上限。

5. 不使用 Playwright route mocking

本 witness 的核心就是观察 Service Worker 拥有网络请求时的真实行为。若用 page.route mock API,会重新引入上一任务已证明的 fixture 冲突。因此所有 synthetic API 都由 loopback HTTP harness 提供,浏览器和 worker看到的是同一真实 origin 响应。

6. 结果状态

状态 判据 后续
PASS A 前置成立,B 在禁止强刷条件下完整收敛,所有负向断言通过 固化 test-only harness
PRODUCT_FINDING A 前置成立,但 B 长期旧 shell、回登录、旧 chunk 404/残留或 reload loop 停止,另建 PWA 产品修复 task
PAUSED A controller/precache 无法稳定建立、构建产物不可区分或环境不支持 SW 记录恢复条件,不猜测产品结论

7. 风险与回滚

  • 只新增/修改 QA runner、E2E spec 与当前 task;client/src/pwa.ts、vite.config.ts 在验证阶段只读。
  • 所有 server、BrowserContext 和临时目录用 finally/trap 清理;失败后不得留下监听端口或 build 目录。
  • harness 若证明不了旧 profile 前置,删除/回滚本轮 test-side probe,但保留 PAUSED 证据。
  • 第一次 B witness 失败即为冻结点;不得通过手工或自动追加 reload/重访改变失败性质。只有先保存安全 ledger、回到规划并获批后,才允许开展新的诊断实验。
  • 不接触生产、数据库、部署脚本或真实浏览器 profile。