在前端开发界,长期存在着一种根深蒂固的偏见: “单元测试(Unit Test)属于后端接口,端到端测试(E2E)属于复杂的大型单页应用(SPA)或电商交易链路;至于纯静态博客或工具站点,HTML 都是构建期直接吐出来的,肉眼看一眼没崩,还需要写什么自动化测试?”
然而,本站(QCSunny Lab)在早期曾遭遇过一起非常尴尬的静默死按键事故:
在一次常规的代码解耦与目录重构中,一个选择器 #calc-panel-basic 发生了残留。构建工具一路绿灯打包通过,生成的 HTML 页面结构完好、排版优雅、SEO 元数据挑不出任何毛病 —— 但在客户端浏览器运行时,底层的绑定脚本因找不到该 DOM 节点直接静默抛出未捕获异常。
全站核心的科学计算器所有按钮整整瘫痪了一周,才被一位热心读者的邮件指出!
静态站由于缺乏服务端动态异常上报通道(尤其是在本站恪守“零外部遥测、零埋点收集”的隐私原则下),一旦在浏览器端发生逻辑断裂,往往比有监控大盘的动态服务更加难以察觉。
为此,我们在持续集成(CI)流水线中植入了目前拥有 150 余个高标准用例的 Playwright 真实浏览器 E2E 自动化质量门禁。本文完整复盘这一工程防线的底层设计考量。
1. 门禁法则一:测真实产物(dist/),绝不测 Dev Server
绝大多数开发者配置 Playwright 时,习惯将 webServer 指向本地的开发服务器(如 astro dev 或 vite):
// 典型但脆弱的配置
webServer: {
command: 'npm run dev',
port: 3000,
}
为什么这在工程上是不可接受的?
- 构建差异(Dev vs Prod Divergence):开发服务器运行的是未压缩的 ESM 模块,CSS 是通过 JS 动态注入的;而上线后的生产产物经过了 Rollup 语法摇树(Tree-shaking)、CSS 文件物理提取、HTML 静态预渲染和 Brotli 极致压缩。在 dev 下正常运行的代码,完全可能在生产打包时因为对象属性可达性误判而产生破坏;
- 静态资产脱节:Sitemap 索引、
llms.txt、根据标题动态生成的 1200×630 OG 分享图以及离线 Service Worker 缓存,仅在构建期由 Integration 插件写入磁盘。
本站铁律:
E2E 必须且只能在执行完 npm run build 之后,基于轻量 HTTP 服务器对 dist/ 物理目录启动测试。线上用户访问的是什么字节,测试套件就验证什么字节。
2. 门禁法则二:不可见缺陷的自动化断言
在现代 Web 标准下,许多致命缺陷根本不会在控制台报错,必须通过无头浏览器的真实排版几何引擎进行捕获:
1. 纯几何防重叠与移动端零横向溢出(375px 铁律)
在 CSS 中,var(--w-page) 变量名一旦拼写错误,整条声明会被浏览器静默丢弃并回退到未定义状态。只断言“属性名是否存在”完全拦不住这种 Bug。
我们在测试中直接通过 getBoundingClientRect() 钉死实际渲染几何:
- 在 375px 窄屏视口(模拟 iPhone 基础尺寸)下,巡检全站近百个页面,断言页面主体最大宽度绝不超过 375px(彻底杜绝横向滚动条);
- 验证导航栏 SVG 图标与 H1 字标的渲染垂直中线偏差 (拦截 CSS flex 基线对齐因字体 descender 空白导致的细微错位);
- 针对房贷走势图等脚本动态生成的 SVG,逐个提取
<text>元素的真实外接矩形,断言文本两两之间重叠面积必须严格 。
2. 双语视图绝对互斥校验
全站采用 html[data-lang] 属性驱动的中英文纯净双语体系。
如果一位开发者在编写新工具或新文章时,不小心在英文视图中漏掉了一个中文段落,或者在中文视图中硬编码了英文短语,肉眼逐页巡检极其容易眼花。
我们的 E2E 测试会驱动真实页面在两种语言之间来回切换:
- 切换为英文时,页面可见文本中的汉字密度必须严格低于阈值(允许个别如“市斤”、“等额本息”等作为被解释对象的中文保留);
- 遍历所有表单生成的下拉框
<option>、按钮提示title、占位符placeholder以及读屏辅助aria-label,断言它们跟随属性变动即时完成全量热替换。
3. KaTeX 公式防逃逸与零第三方渗透
本站博客大量包含高等数学、算法与密码学公式。为了保证极致性能与网络主权,我们严格锁死 KaTeX 本地依赖,严禁请求任何外部公共 CDN。
门禁中专门设立了针对公式渲染的硬性断言:
- 拦截页面发出的一切网络请求,一旦出现任何非同源的第三方域名资源,测试立即熔断报警;
- 字体文件必须且仅允许加载 3 个本地精简子集化 woff2 文件(总大小控制在 48 KB 内);
- 散落美元号逃逸防护:正文中的货币符号(如
\$0.14)或代码变量(如\$PATH)若未按规范转义为\$,会被 KaTeX 误判定为未闭合的数学公式,导致大段正常文字被斜体歪斜排版。测试套件会扫描全站渲染后的 DOM 树,确保没有任何未被转义的美元符号触发意外排版。
3. 门禁法则三:产物级性能预算(Performance Budget)
代码体积的膨胀就像房间里的灰尘,如果不加以强制约束,随着迭代一定会逐渐失控。我们将《静态站的字节账》中推导出的性能指标直接写成测试断言:
// e2e/perf-budget.spec.ts 片段
test('pure content pages must ship 0 bytes of first-party JS', async ({ page }) => {
const scripts: string[] = [];
page.on('request', req => {
if (req.resourceType() === 'script' && req.url().includes('/_astro/')) {
scripts.push(req.url());
}
});
await page.goto('/blog/static-site-byte-ledger/');
// 纯文章正文页严禁加载任何第一方客户端 JS 分块!
expect(scripts).toHaveLength(0);
});
同时,测试套件还会对物理打包产物进行离线测量:
- 46 个工具页共享的
main核心分块,经 Brotli 压缩后必须严格 ; - 嵌入 HTML 的首屏轻量内联搜索索引,边际体积必须严格 。 一旦有开发者引入了臃肿的重量级第三方库,CI 会在合并代码前直接拉响红灯,守死性能底线。
4. 测试不是负担,而是敢于频繁重构的底气
262 个 E2E 测试用例在本地并行执行仅需数分钟,在 GitHub Actions CI 容器中也只需约 2 分钟。
正是这套严丝合缝的自动化测试门禁,让我们在面对底层算法重构、CSS 全局度量衡重写以及高频发布新博文时,能够拥有**“只要 CI 是绿的,我就敢打包上线”**的从容与底气。