# PWA 跨版本缓存升级验证:技术设计 ## 1. 判定对象 本任务验证的是一个状态迁移,不是两个独立页面截图: ```text 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。