Files
cms-website/docs/evidence/phase4-preview.md
T
浅念 c64f6ac48a test(evidence): 阶段4端到端预览验收
Plan 4 Task 8:真实 hesupipe 全链(register 副本复用 → import → publish
→ build → preview)临时库端到端验收,断言 21/21 PASS + 浏览器时刻 6 截图
无破版无横向溢出。新增 scripts/e2e-real.mjs(chain/serve 两子命令)与
docs/evidence/phase4-preview.md + phase4-assertions.json + screenshots/。

验收内修补(预览服务器,均有回归测试锁定):
- Location 头 ASCII 化:非 ASCII 码位按 UTF-8 百分号编码——原样 setHeader
  触发 ERR_INVALID_CHAR 崩 500 或 latin1 mojibake(1746 redirect 全量
  校验显影 2+3 例);manifest 原串保持权威,测试 2b 锁定。
- CSP 放行内联样式:Astro 5 scoped 样式内联 <style> 被 default-src 'self'
  拦截(浏览器时刻显影,HTTP 级不可见);测试 6 断言同步。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 14:33:55 +08:00

13 KiB
Raw Blame History

阶段 4 真实 hesupipe 端到端验收证据(Plan 4 · Task 8 —— 浏览器时刻)

verdict:PASS(断言总表 21 项脚本断言全过 + 浏览器时刻 6 截图无破版无横向溢出;两处验收内修补(预览服务器 Location 头 ASCII 化、CSP 放行内联样式)均有回归测试锁定,见 §5;观察项未触发主题补规则,见 §5.1)

项 值
日期 2026-10-22
环境 Windows 11 · Node v24.13.0 · pnpm 10.7.1 · tsx 直吃 .mjs 编排脚本(@sf/* 经根 tsconfig paths 解析,与 sf CLI 同机制)
数据库 本项目 PG(docker sf-postgres,postgres:16,localhost:5434)临时库 sf_phase4_hesupipe(验后已 DROP,见 §7)
站点/输入 复用阶段 3 同源副本(storage/incoming/ 的 WXR + 媒体 + 侧车;storage/ 不入库)——site hesupipe-real、origin=https://hesupipe.com、locale=en
全链 job import 9d82df35-5c8a-4345-8156-3319c7ac9d67(succeeded_with_issues,51,123.8ms 首跑)· publish 192 → upToDate · build 619c440c-f579-4647-99dc-2840dcd36f19(succeeded,11,885.4ms 首跑)· validation run 6dc3776b-7bd5-4199-aa95-2db8e8a5ea41 PASS(min-gates-v1,p0=0 p2=0)
产物面 dist 1029 文件(190 html + 836 媒体 + sitemap.xml + robots.txt + 404.html)· route-manifest 2772 行(html 190 / redirect 1746 / asset 836)· dist SHA-256 6883e70a…621832
断言明细 docs/evidence/phase4-assertions.json(脚本逐断言落盘,verdict=PASS 21/21)
截图 docs/evidence/screenshots/phase4-{mobile-375,desktop-1440}-{home,product-detail,news-detail}.png ×6(§3)
编排脚本 scripts/e2e-real.mjs(chain 全链+断言 / serve 截图会话常驻预览,两个子命令)

1. 全链命令原文(仓库根 D:\project\cms-one,Git Bash)

# 临时库(阶段 3 的 sf_phase3_hesupipe 已在其任务末 DROP;本任务新建同构库)
docker exec sf-postgres psql -U siteforge -d postgres -c "CREATE DATABASE sf_phase4_hesupipe;"
DATABASE_URL="postgres://siteforge:siteforge_dev@localhost:5434/sf_phase4_hesupipe" pnpm --filter @sf/api db:migrate

# 全链 + 断言(register 副本复用 → import → publish dry-run/confirm → build → 进程内预览
# 断言 ①-⑤⑦⑧⑨ + SQL/产物计数对拍 → docs/evidence/phase4-assertions.json)
DATABASE_URL_TEST="postgres://siteforge:siteforge_dev@localhost:5434/sf_phase4_hesupipe" \
  pnpm exec tsx scripts/e2e-real.mjs chain --site hesupipe-real

# 截图会话(serve:对既有 succeeded build 起常驻预览 127.0.0.1:4571,Playwright MCP 对其取证)
DATABASE_URL_TEST="postgres://siteforge:siteforge_dev@localhost:5434/sf_phase4_hesupipe" \
  pnpm exec tsx scripts/e2e-real.mjs serve --build 619c440c-f579-4647-99dc-2840dcd36f19 --port 4571

互斥纪律(Task 5 备忘):链路执行期间零并发测试/零并发构建(vitest 全套在链路完成后单独跑,见 §5.3);astro 计时探针冻结输入重跑(SF_BUILD_INPUT 指向 job input 目录,前后清主题暂存 dist)。

2. 断言总表(预期 vs 实测 vs 判定)

预期来源:Plan 4 Task 8 节 + 快线 §8.7。①-⑤⑦⑨ 与计数对拍由 chain 子命令逐项断言(明细 JSON 同步落盘);⑥⑧ 在浏览器时刻/计时节单独取证(§3/§4)。

# 断言项 预期 实测 判定
①a 首页 / HTTP 200 + 真实内容 200;站点名在体 + 最新 3 条新闻标题在体(Chile/Tanzania/Burkina Faso 三篇实题) PASS
①b 编码内容页 /how-to-choose-pe-pipe%ef%bc%9f/ 同上 200;标题"Before choosing a PE pipe, be sure to confirm the purpose"在体 PASS
①c 产品分类列表 /product-category/hdpe-pipe-fittings/ 同上 200;term 名 + 14 个产品标题在体 PASS
①d 产品详情 /product/ecological-wooden-door/ 同上 200;标题"ECOLOGICAL WOOD DOORS"在体 + 12 个本地化媒体引用 PASS
①e 新闻列表 /news/ 同上 200;最新 5 条标题在体 PASS
①f 新闻详情 /2023-pipe-market-analysis/ 同上 200;标题 + 日期"2023-11-19 13:32:01"在体 PASS
② 图片本地化抽检 10 张 根相对引用 + dist 文件存在 + preview 200 + assets.json 在册 10/10:全部 /wp-content/uploads/… 根相对、dist 文件在、HTTP 200(11KB-1MB 实尺寸)、assets.json 全在册 PASS
③ SEO 三件套 ×5 页 title/description 非空 + canonical === origin+路径 5/5 逐页精确(含编码路径 canonical …/how-to-choose-pe-pipe%ef%bc%9f/ 原样保持) PASS
④a 未命中 404 404 + noindex status=404 + X-Robots-Tag: noindex, nofollow(自定义 404.html) PASS
④b 301 抽检 ?attachment_id= 原串 3 + 路径形态 2,逐条 301 + Location 与 manifest 原串一致 5/5:全 301、locationMatch 全真(ASCII 位置串逐字节等;非 ASCII 原串的头域派生形见 §5.2) PASS
⑤a sitemap.xml(盘读) 条目 = manifest indexable html 数 loc=190 = indexable=190 PASS
⑤b robots.txt(盘读) 存在 + Sitemap 行指向 origin 存在,Sitemap: https://hesupipe.com/sitemap.xml PASS
⑥ 浏览器时刻(375/1440 ×3 页) 无横向溢出 + 视觉无破版 scrollWidth 360≤375 / 1425≤1440 ×6 组合全过;6 截图样式生效无破版(§3) PASS
⑦a html 全量 190 行全 200 ok=190/190(keep-alive 并发 8;content-type=text/html; charset=utf-8) PASS
⑦b redirect 全量 1746 行逐条 301 + Location 一致 ok=1746/1746(含 24 条 ?attachment_id= exact 行按原串发请求;非 ASCII Location 修复后全过,§5.2) PASS
⑧ 计时对账 分钟级预算内 + 与阶段 3 基线同量级 全链首跑 ~64s(import 51.1s + build 11.9s);复跑链断言 ~15s(§4) PASS
⑨ 门禁 validation_runs PASS job=succeeded · run PASS · min-gates-v1 · p0=0 p2=0 · issues=[] PASS
计数 content_entries 192(129+48+15) SQL=192 {news_article:129, product:48, basic_page:15} PASS
计数 published 指针 192 SQL=192 PASS
计数 routes 2772(190/1746/836) SQL 分组逐桶吻合 PASS
计数 media ready 836 SQL=836 PASS
计数 audit content.publish ≥1 =1(confirm 写入) PASS
计数 构建产物面 dist≈1029 distFileCount=1029 · mediaCopied=836 · sitemapEntries=190 PASS

3. 浏览器时刻(Playwright MCP × 常驻预览 127.0.0.1:4571)

三页取样(与 ① 断言页一致):首页 /、产品详情 /product/ecological-wooden-door/(12 媒体引用最多)、新闻详情 /2023-pipe-market-analysis/。每页双视口截图(整页)。

截图 视口 scrollWidth(≤视口) 视觉判定
screenshots/phase4-mobile-375-home.png 375×812 360 样式生效(标题层级/列表/图片自适应),无破版
screenshots/phase4-mobile-375-product-detail.png 375×812 360 产品标题 + 木门样图横排自适应缩放,无溢出无破图
screenshots/phase4-mobile-375-news-detail.png 375×812 360 文章标题/日期/正文排版正常,头图自适应
screenshots/phase4-desktop-1440-home.png 1440×900 1425 桌面版式正常,新闻列表/产品分节无错位
screenshots/phase4-desktop-1440-product-detail.png 1440×900 1425 图廊 + 描述正文排版正常
screenshots/phase4-desktop-1440-news-detail.png 1440×900 1425 标题/日期/正文/头图排版正常
  • 溢出判据:document.body.scrollWidth <= window.innerWidth(六组合全 false 无横向溢出;360/1425 与视口的 15px 差是滚动条槽位)。
  • 控制台仅 1 条噪声:/favicon.ico 404(源数据无 favicon,路由表无此行——如期 404,装饰性,不修,§5.4)。

4. 耗时分解(vs 基线)

阶段 首跑实测 对账
register(快照副本复用) 服务内 1,750ms(wxr 15.2 / media_bundle 1,288.2 / manual_url_list 21.0——哈希命中即复用,内容寻址副本不再重拷) 首登 19.3s(阶段 3)的重哈希路径,符合内容寻址预期
import 51,123.8ms 阶段 3 基线 51,768.0ms(+0.7% 机噪,同量级)
publish(dry-run + confirm) 首跑 192 发布;复跑 dry-run 87ms + confirm 77ms(upToDate=192 幂等) —
build 总 11,885.4ms 分相 marks:snapshot=450ms / building=11,240ms / validating+report=183ms
└ astro 纯构建探针 冻结输入重跑三次 4,567 / 5,650 / 7,486ms building 相与探针差 ≈ 媒体复制 836 文件(~164.5MiB)+ 主题运行器编排(快照装载/产物整理),量级自洽
全量冒烟(190 html + 1746 redirect) 1,225ms keep-alive 并发 8,quiet 服务器
复跑链(幂等命中) register 1.8s + import 复用 94ms + publish 164ms + build 复用(不重跑,读原报告) 断言二次全过(21/21)

分钟级预算内:全链首跑 ~64s(含发布);验收复跑 ~15s。

5. 观察项兑现(验收内修补与判定)

5.1 主题分发观察项:未触发

计划预告的三类分发观察项在真实 190 路由集上均未出现,无需补主题规则:

  • /news/ 兄弟特例:无冲突(/news/ 列表行唯一);
  • 嵌套分类前缀:数据集 product_cat 无嵌套(10 个分类全一级);
  • 裸 /product-category/ 前缀:10 个归档页均有 term 且有产物(hdpe-pipe-fittings 14 产品为最大者);
  • /products/ 走 basic_page 内容页(阶段 3 既有 exact 冲突事实的延续,generic 模板正常渲染)。

5.2 预览服务器修补一:Location 头 ASCII 化(真实缺陷,HTTP 级全量校验显影)

⑦b 首轮 1739/1746:manifest location 含非 ASCII 码位(全角括号(1)、间隔号·)时,res.setHeader('Location', 原串) 触发 Node ERR_INVALID_CHAR 崩 500(2 例)或被按 latin1 写出 mojibake ·(3 例)。修补:apps/preview-server/server.mjs 新增 asciiLocation()——非 ASCII 码位按 UTF-8 百分号编码(与 WP/nginx 形态一致,大写 hex),控制字符同路编码(头注入面封死);manifest 原串保持权威,HTML 迁移页内嵌转义原文。断言侧比较改用同规则镜像(asciiLocationMirror),预期口径更新为"manifest 原串的 RFC 9110 头域 ASCII 派生形"。回归测试:tests/integration/preview-server.test.ts 测试 2b(非 ASCII redirect 行 → 301 + 编码 Location + 迁移页原文)。修后 1746/1746。

5.3 预览服务器修补二:CSP 放行内联样式(浏览器时刻显影,HTTP 级不可见)

截图会话首开时页面在浏览器里裸奔(无样式)——Astro 5 把 scoped 样式内联为 <style> 元素,default-src 'self' 拦掉全部内联样式(控制台 CSP violation)。HTTP 级断言看不到此面(样式不在头域语义内)。修补:CSP 常量改为 default-src 'self'; style-src 'self' 'unsafe-inline'(仅样式面放开;脚本/连接/图片仍走 default-src)。回归测试:测试 6 断言更新(/ 与 404 双路径)。修后 6 截图全部样式生效。

5.4 不修项(如实记录)

  • /favicon.ico 404:源数据无 favicon、路由表无行,预览 404 是正确行为;控制台一条装饰性噪声。
  • 集成测试复跑:pnpm vitest run tests/integration/preview-server.test.ts 14/14 过(含 2b/6),与链路互斥执行。

6. 差异分析(如实记录,无硬凑)

  1. 编码 html 路由仅 1 条(vs 计划假设的编码内容页更多):数据集 ~54 条含中文/全角的 permalink 全部是 attachment 降级 redirect 行(媒体文件目标),有内容的编码路由只有 /how-to-choose-pe-pipe%ef%bc%9f/ 一条——①b 取样即为该条,编码链路(routes.json → dist 文件名 → manifest → 预览匹配逐字节对齐)在其身上完整验证;⑦a 190 行全 200 亦覆盖全部编码形态。
  2. urlRewrite unresolved=317 非死链:该计数是"body 内 src/href 有可比对路径但未命中 ready 媒体索引"的引用数——主体是站内页面链接(<a href="https://hesupipe.com/…">,非媒体、本就不在改写索引内,保持原样正确)+ 外站热链(cn-hdpe.com 等 19+)+ 已知缺失媒体 19 与 WP 尺寸变体 2(阶段 3 §5.2/5.3 同源)。门禁 ①internal-links 对根相对链接全量校验 0 死链(p0=0);dist 实测 460 个根相对图片引用全部命中产物文件。
  3. build 复用路径的 durationMs:复用命中返回的是原 job 行的 durationMs(11,875.1ms,非本次调用墙钟)——服务口径"未重跑、未改行",产物/计数读原 job 报告,断言不受影响。
  4. ** astro 探针三次波动 4.6-7.5s**:同冻结输入,机噪区间;取区间而非单点呈报。

7. 清理与保留声明

  • serve 会话与临时库:截图会话(端口 4571)已停;docker exec sf-postgres psql -U siteforge -d postgres -c "DROP DATABASE sf_phase4_hesupipe WITH (FORCE);" 验后执行。本项目开发库 siteforge 全程零写入(所有命令经 DATABASE_URL_TEST 指临时库)。
  • 保留:storage/incoming/ 副本(阶段 3 已声明,约 345MB,gitignore);artifacts/jobs/619c440c…/(dist + route-manifest + 报告,artifacts/ 不入库,本地留档);docs/evidence/screenshots/*.png ×6 入库。
  • cms 源侧(D:\project\cms)本任务零访问(输入全用阶段 3 副本)。