网页交付前真实渲染验证
为什么必须有这道关
2026-10-01 实测事故:茶云堂技能商城的首页弹窗,HTML 里写了 hidden,但 CSS 里 .modal{display:grid} 把浏览器默认的 [hidden]{display:none} 顶掉了(作者样式表优先于 UA 样式表,与选择器权重无关)。结果弹窗常驻显示、遮罩把整页蒙住、点 × 也关不掉。
为什么没查出来:当时用的验证脚本是 Node + JS DOM mock,只执行 JS、不计算 CSS 层叠。它报了"31 张卡片全渲染、无异常",全是真话,但页面根本不能用。
> 结论:DOM mock 验证 ≠ 页面可用。凡 HTML 交付,必须过真浏览器。
第 0 步:确认可用的浏览器
for p in "/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" \
"/c/Program Files/Microsoft/Edge/Application/msedge.exe" \
"/c/Program Files/Google/Chrome/Application/chrome.exe"; do
[ -f "$p" ] && echo "找到: $p"
done
本机只有 msedge,无 Chrome。Playwright 的浏览器缓存已被清掉,不要指望它。
第 1 步:首屏渲染截图
EDGE="/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
"$EDGE" --headless=new --disable-gpu --no-sandbox --hide-scrollbars \
--window-size=1280,880 --virtual-time-budget=4000 \
--screenshot="C:/out/shot.png" "file:///C:/path/page.html"
要点:
- 中文路径的 file:// 不保险 → 先把待测 HTML 复制到纯 ASCII 路径再截。
- --virtual-time-budget 给足(3000–5000),否则 JS 还没渲染完就截了。
- 输出会有两条 ERROR:...QQBrowser... / fallback_task_provider 噪音,无害,看最后一行 written to file 即可。
- 截图尺寸不一定等于 --window-size(受系统 DPI 缩放影响),别据此判断失败。
第 2 步:看截图(关键,不能省)
把 PNG 读出来用眼睛核对,逐条问:
- 有没有遮挡物——空白框、灰蒙层、半透明遮罩?(= hidden / z-index 出问题)
- 该出现的内容出现了吗?(卡片数、统计数字、按钮字样)
- 有没有溢出、错位、字被裁掉?
- 配色是不是本意(深色主题要深底浅字,浅色主题要浅底深字)?
第 3 步:交互态验证(静态首屏看不出弹窗好坏)
在 </body> 前注入一段自动点击,再截一次:
t = open(page, encoding='utf-8').read()
hook = """
<script>
setTimeout(function () { var c = document.querySelector('.card'); if (c) c.click(); }, 1200);
</script>
</body>"""
open(test_page, 'w', encoding='utf-8').write(t.replace('</body>', hook, 1))
配 --virtual-time-budget=5000 截图。要看到:弹窗打开、内容完整、关闭按钮在、主按钮 href 正确。
只有"默认状态干净" + "点开状态正常"两条都过,才算真过。
第 4 步:线上站点的额外陷阱(本机缓存会骗你)
部署后用本机浏览器验证线上站,必须指定全新配置目录。不指定时 Edge 无头模式会复用你日常的浏览器配置,命中磁盘缓存里的旧文件 → 看起来像"部署没生效",其实是本地缓存旧。
EDGE="/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
rm -rf "$TEMP/_edgeverify"
"$EDGE" --headless=new --user-data-dir="$TEMP/_edgeverify" --incognito \
--disable-gpu --no-sandbox --hide-scrollbars \
--window-size=1280,880 --virtual-time-budget=6000 \
--screenshot="C:/out/live.png" "https://你的域名/"
判据:全新配置的截图 = 本地预期结果 → 线上真的好了;本机默认配置截图仍是旧的 → 只是用户浏览器缓存旧,让他 Ctrl+F5 强刷即可,别误判成发布失败。
curl 也会骗你:CDN 按请求头(Accept / User-Agent / Accept-Encoding)分多个缓存条目,不加头可能拿到"假旧"或"假新"。要按真实浏览器形态复测:
curl -s -I -L \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0" \
-H "Accept: text/css,*/*;q=0.1" \
-H "Accept-Encoding: gzip, deflate, br, zstd" \
-H "Referer: https://你的域名/" \
"https://你的域名/assets/style.css" | grep -i "last-modified\|content-length\|cache-status"
看 Last-Modified 是否等于本次发布时间;Eo-Cache-Status: HIT 且 Last-Modified 是旧时间 = 边缘还没刷新,等一会儿再测(实测几十秒内会自愈)。
多节点跳变(2026-10-01 复现,最容易误判):CDN 有多个边缘节点,各自缓存、各自过期。同一次发布后,裸 URL 在几分钟内会在"旧 / 新"之间反复横跳(实测 live 计数 15s=18 → 30s=23 → 45s 起又回到 18,约 105s 才稳定为新版)。更坑的是:curl 与浏览器会命中不同节点 —— 本次 curl 已返回新版 23,同一时刻 Edge 全新配置渲染同一 URL 仍拿到旧版 18。
判定口径(务必照此,否则会把成功当失败):
| 现象 | 结论 |
|---|---|
源站(URL 加随机参数 ?nocache=$RANDOM)返回新版 |
✅ 发布成功,CDN 只是还没全量刷新 |
| 裸 URL 单次拿到旧版 | ⚠️ 不可作为"发布失败"依据,继续轮询 |
| 浏览器单次渲染拿到旧版 | ⚠️ 同上,不代表失败 |
轮询脚本(每 15s 一次,最多 2–3 分钟,稳定为新版即停):
for i in $(seq 1 12); do
L=$(curl -s -m 15 https://你的域名/assets/data.js | grep -c '新版特征串')
echo "[$((i*15))s] $L"
[ "$L" = "期望值" ] && { echo "→ CDN 已刷新"; break; }
sleep 15
done
两个必查陷阱(命中即事故)
陷阱 1:hidden 被 display 类样式覆盖
症状:页面正中一个空白圆角框,整页发灰发虚,点 × 关不掉。
根因:<div class="modal" hidden> + .modal{display:grid}。作者样式表压过 UA 的 [hidden]{display:none}。
每页必加的兜底(放在 CSS 最顶部):
[hidden]{display:none!important}
顺带自查:所有用 .hidden = true/false 切换的元素,其样式里有没有 display: 声明?有就必须上兜底。
陷阱 2:Windows .url 快捷方式带 BOM
症状:双击弹出「此 Internet 快捷方式的目标""无效」。
根因:用 encoding='utf-8-sig' 写 → 文件头 EF BB BF → 首行变成 \ufeff[InternetShortcut] → Windows 认不出段落名 → URL= 读成空。
写法:.url 内容全是 ASCII,必须 encoding='ascii' + newline='\r\n'。
交付前字节级校验(必做):
raw = open(url_path, 'rb').read()
assert raw[:3] != b'\xef\xbb\xbf', '.url 带 BOM,双击会报目标无效'
assert raw.startswith(b'[InternetShortcut]'), '.url 首行不对'
assert (b'URL=' + online.encode()) in raw, '.url 缺 URL 行'
assert all(b < 128 for b in raw), '.url 含非 ASCII'
交付前清单(逐条打勾,缺一不可)
- [ ] 有浏览器可用,路径确认过
- [ ] 首屏截图已生成,并且已目视核对(不是只看退出码)
- [ ] 交互态截图已生成(弹窗/抽屉/菜单能正常开)
- [ ] 全站搜一遍
display:与hidden的冲突,兜底规则已加 - [ ] 所有
.url/ 快捷方式产物做了字节级校验 - [ ] 若同步线上:线上文件与本地逐字节 diff,确认改动真的上去了
- [ ] 线上验证用全新浏览器配置截图(本机默认配置会命中磁盘缓存,导致误判成"没发布")
- [ ] 临时测试文件、截图、临时浏览器配置目录已清理,只留正式产物
与其它验证手段的分工
| 手段 | 能查 | 查不出 |
|---|---|---|
node --check |
语法错 | 一切逻辑与样式 |
| JS DOM mock 跑 JS | 数据条数、渲染数量、运行时异常 | CSS 层叠、遮挡、布局 |
| curl 状态码 | 站点是否活着 | 页面是否可用 |
| 真浏览器截图 | 遮挡、布局、样式、交互态 | 需要人眼配合,无法全自动断言 |