Lighthouse CI:把页面审计变成可重复发布门禁
Lighthouse CI 的价值不在生成一个彩色分数,而在把固定页面、Chrome 运行环境、审计结果、预算断言和报告交付串成可重复流水线。页面身份或采集环境不稳定时,多跑几次只会得到更精确的噪声。
collect、assert 与 upload 是三种不同责任
Lighthouse CI 通过 collect 启动或连接站点并采集 Lighthouse 报告,通过 assert 对类别、audit 或预算作出通过/拒绝结论,再由 upload 把结果保存到文件系统、临时存储或 LHCI Server。三段之间以 .lighthouseci/ 中间目录交换数据,因此该目录既是调试证据,也可能包含内部 URL、网络信息和页面细节。
静态站点适合使用 staticDistDir,应用服务则由 startServerCommand 启动并等待可用。采集 URL 必须明确,不能依赖爬虫偶然找到页面;登录重定向、地区、A/B 实验、语言、主题和 cookie 都可能让同一个字符串 URL 实际加载不同页面。
npm install --save-dev @lhci/cli
npm ls @lhci/cli lighthouse
npx lhci --versionCLI 固定在项目依赖和 lockfile 中,由 npm ci 安装。Chrome 或 Chromium 的版本、路径、系统依赖、字体、sandbox、CPU 与网络条件应写入 runner 基线。随手增加 --no-sandbox 虽可能绕过启动问题,却改变进程权限边界,只能在经过隔离评估的专用 runner 上使用。
第一份配置先固定页面身份和证据出口
下面的配置构建静态站点,采集两个稳定入口,显式指定运行次数、类别与报告去向。性能指标允许合理波动,可访问性关键 audit 则使用更严格的聚合。具体阈值应来自项目已批准的基线和用户目标,不照抄别人的数字。
// lighthouserc.cjs
module.exports = {
ci: {
collect: {
staticDistDir: './dist',
url: [
'http://localhost/',
'http://localhost/docs/getting-started.html',
],
numberOfRuns: 3,
settings: {
onlyCategories: ['performance', 'accessibility'],
preset: 'desktop',
},
},
assert: {
assertions: {
'categories:accessibility': ['error', {
minScore: 1,
aggregationMethod: 'pessimistic',
}],
'largest-contentful-paint': ['error', {
maxNumericValue: 2500,
aggregationMethod: 'median',
}],
'cumulative-layout-shift': ['error', {
maxNumericValue: 0.1,
aggregationMethod: 'median',
}],
},
},
upload: {
target: 'filesystem',
outputDir: './test-results/lighthouse',
},
},
};npm run build
npx lhci autorun --config=./lighthouserc.cjs本地文件输出便于先确认报告内容与敏感边界。准备接入 LHCI Server 或其他存储前,应先确定认证、访问控制、处理区域、保留、删除、备份和退出导出。临时公共存储只适合公开演示页,不能承载登录后的内部应用报告。
分数、audit 与预算解决的问题不同
类别分数是多个 audit 的加权聚合,适合看总体趋势,但可能掩盖某个关键规则。具体 audit 断言直接约束 button-name、image-alt、LCP 或 CLS 等信号,更适合发布门禁。资源预算则约束脚本、图片、总字节和请求数,能在指标尚未明显恶化前阻止体积增长。
可访问性分数为 1 只说明参与评分的自动审计通过,不是 WCAG 合规证明。性能分数还会受到硬件、网络模拟、Chrome 调度与页面第三方内容影响。架构师需要把确定性语义问题、统计型性能指标和人工任务拆开:前者可用 pessimistic 阻断,后者用 median 与趋势控制,人工任务不应被任何分数替代。
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 250 },
{ "resourceType": "image", "budget": 500 },
{ "resourceType": "total", "budget": 900 }
],
"resourceCounts": [
{ "resourceType": "third-party", "budget": 8 }
]
}
]预算文件应与站点配置一起评审。数字需要说明测量预设、页面模板和业务假设;一次简单首页和一个富交互控制台不能共享同一份盲目阈值。新增第三方脚本时,资源预算、隐私审查和故障降级应在同一次变更中完成。
聚合方法决定门禁会不会隐藏失败
多次运行需要明确如何聚合。optimistic 选择更容易通过的结果,适合某些探索性场景,却可能把一次确定的可访问性失败覆盖掉;pessimistic 保留最差结果,适合不应波动的语义 audit;median 适合受环境噪声影响的性能趋势。配置中不写聚合方法,相当于把关键决策留给默认值和未来版本。
同一类别里的规则也不必共享策略。可访问性类别和关键 audit 可以 pessimistic;LCP、CLS 等性能指标可以用三次或更多运行的 median;资源体积通常来自构建产物,应该是确定性断言。门禁失败时要保存每次运行,而不是只留聚合值,否则无法区分单次环境抖动与页面系统性退化。
正反金丝雀证明整条门禁真的工作
正例只能说明一份页面在当前配置下通过。反向金丝雀要故意引入一个已知错误,并确认 lhci autorun 返回非零。金丝雀与业务页面使用同一 lockfile、Chrome、配置和构建入口,只改变预期缺陷。
<!doctype html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>反向样本</title></head>
<body>
<main>
<h1>登录</h1>
<img src="avatar.png">
<button type="button"><span aria-hidden="true">→</span></button>
</main>
</body>
</html>Copy-Item tests/lhci/accessibility-bad.html dist/accessibility-bad.html
if npx lhci autorun --config=./tests/lhci/lighthouserc.bad.cjs; then
echo "negative Lighthouse CI canary was not rejected" >&2
exit 1
fi坏页面若得到满分,先核对采集 URL、dist 是否更新、Chrome 是否被重定向、onlyCategories 和 audit 名称是否正确、assert 是否读取了本次报告。反向作业不能靠 || true 吞掉失败;包装器只有在 LHCI 确实拒绝坏页面时才能返回成功。
应用服务器与认证状态是最常见的失真来源
动态站点需要让 startServerCommand 的进程生命周期、端口与 readiness 可观察。只看到“无法连接”时,应同时保存服务端启动日志、LHCI collect 日志和实际响应;固定 sleep 会在快机器浪费时间,在慢机器继续失败。构建、启动和审计三段退出码必须独立保留。
登录后的页面更复杂。把长期账号密码写进 Puppeteer 脚本会扩大凭证暴露面,验证码和多因素认证也会让采集脆弱。更稳妥的路线是为测试环境提供短期低权限会话,由专用前置步骤获取并限制传播;复杂多角色状态可交给 Playwright 主文中的浏览器上下文管理,再让 LHCI 聚焦公开入口与少量稳定 URL。可访问性细粒度状态扫描则回到 axe-core 主文。
module.exports = {
ci: {
collect: {
startServerCommand: 'npm run preview -- --host 127.0.0.1',
startServerReadyPattern: 'Local:',
startServerReadyTimeout: 30000,
url: ['http://127.0.0.1:4173/'],
},
},
};服务启动正则要匹配真实输出,并在升级构建工具时纳入回归。端口冲突、IPv4/IPv6 绑定和 base path 都应从日志直接验证,不要把首页 404 的报告误认为目标页面审计。
报告、上传和服务器需要独立安全设计
Lighthouse 报告可能包含页面 URL、网络请求、DOM 线索、截图和第三方域名。文件系统输出进入 CI artifact 时,应按项目权限控制下载并设置短保留;LHCI Server 则需要独立身份、TLS、备份、清理和升级责任。上传 token 只在受信作业中短期注入,不写入配置、命令历史或公开日志。
报告体积大致随 URL、运行次数、类别和保留周期相乘。PR 失败证据保留到缺陷解决后的短窗口,主分支趋势保留较长,成功运行可只保留摘要与服务器链接。清理动作按 run id 和明确目录执行,不能用模糊通配符删除仓库内的预算和配置。
第三方托管会增加数据处理方和退出依赖。接入前要确认数据区域、子处理方、保留与删除、SSO、审计日志、导出格式和合同退出。工具退出时必须能导出历史趋势、预算、断言和页面清单,而不是只剩一个无法迁移的私有分数。
升级与回滚应比较审计集合和运行基线
Lighthouse 与 LHCI 升级可能改变 Chrome 要求、audit、权重、默认设置和报告结构。升级分支先用旧 lockfile 运行固定页面和正反金丝雀,再更新 CLI 与 runner 镜像,比较类别、audit、数值、运行时间、报告大小和错误文本。分数变化不能直接解释为页面退化,也不能直接批量放宽阈值。
Remove-Item -LiteralPath .lighthouseci -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath test-results/lighthouse -Recurse -Force -ErrorAction SilentlyContinue
npm ci
npm run build
npx lhci autorun --config=./lighthouserc.cjs回滚需要同时恢复 lockfile、Chrome/runner 镜像、配置和预算。只回滚 @lhci/cli 却复用新版生成的 .lighthouseci/ 报告,会产生混合证据。清理后再次运行正例、反例和代表页面,确认旧门禁恢复可解释。
长期治理从发布风险倒推采集矩阵
每个 URL 应有页面 owner、业务重要度、认证状态、采集预设、预算和报告去向。PR 跑少量快速、稳定且高价值页面,主分支扩大模板和功能面,夜间任务再覆盖低频页面与更多运行次数。重试必须保留第一次失败,隔离页面要有问题单和退出期限。
团队指标不只看平均分,还应看预算拒绝次数、首次失败率、页面缺采集时长、报告字节、runner 分钟、上传失败和超期例外。Lighthouse CI 真正提供的是一条可重复的页面审计发布链:能证明采集的是目标页面,断言按明确策略拒绝风险,报告没有越过数据边界,升级后仍可比较,失败时能恢复到已验证基线。
