Puppeteer CSSCoverageOptions 深度解析CSS 覆盖率采集的配置项、源码实现与实战用法【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer本文围绕 Puppeteer API 文档中的 CSSCoverageOptions 接口 展开完整讲解该接口唯一的配置项resetOnNavigation的语义与默认值并结合 Coverage 源码实现 与 CSS 覆盖率测试用例剖析 Puppeteer 是如何通过 CDPChrome DevTools Protocol命令追踪页面中“被实际使用的 CSS 规则”的。读完本文你将能够独立编写 CSS 覆盖率采集脚本、理解覆盖率数据的生成原理并在跨导航场景下正确选择配置策略。CSSCoverageOptions 接口概述CSSCoverageOptions是 CSS 覆盖率的可配置选项集合对应文档描述为 “Set of configurable options for CSS coverage.”。其签名如下export interface CSSCoverageOptions属性说明属性修饰符类型说明默认值resetOnNavigationoptionalboolean是否在每次导航navigation时重置已采集的覆盖率数据true由startCSSCoverage文档与源码共同确认需要注意的一个细节文档属性表中 Default 列为空但 Coverage.startCSSCoverage 文档 明确写着 “defaults toresetOnNavigation : true”源码中也能印证——CSSCoverage.start() 对解构赋值提供了resetOnNavigation true的缺省值async start(options: {resetOnNavigation?: boolean} {}): Promisevoid { assert(!this.#enabled, CSSCoverage is already enabled); const {resetOnNavigation true} options; this.#resetOnNavigation resetOnNavigation; // ... }因此不传任何选项时等价于startCSSCoverage({resetOnNavigation: true})。CSSCoverageOptions 的使用入口startCSSCoverage()CSSCoverageOptions唯一的消费方是Coverage.startCSSCoverage()方法class Coverage { startCSSCoverage(options?: CSSCoverageOptions): Promisevoid; }参数options可选CSSCoverageOptions类型返回Promisevoid覆盖率采集启动后 resolve前置条件重复调用会触发断言失败源码 中的assert(!this.#enabled, CSSCoverage is already enabled)会在已启用时抛出 “CSSCoverage is already enabled”。典型的调用流程是“启动 → 操作页面 → 停止并取报告”import puppeteer from puppeteer; const browser await puppeteer.launch(); const page await browser.newPage(); // 1. 启动 CSS 覆盖率采集此处不传 optionsresetOnNavigation 取默认值 true await page.coverage.startCSSCoverage(); // 2. 导航并触发样式重算 await page.goto(https://example.com); // 3. 停止采集得到 CoverageEntry[] const coverage await page.coverage.stopCSSCoverage(); console.log(coverage.map(entry ({url: entry.url, ranges: entry.ranges}))); await browser.close();如果想保留跨导航的样式表数据则显式关闭重置await page.coverage.startCSSCoverage({resetOnNavigation: false});resetOnNavigation 的底层机制源码级解析resetOnNavigation的实现位于 CSSCoverage 类可以从源码结构看其工作原理分为三个阶段1. 启动阶段启用 CDP 追踪域startCSSCoverage()内部并行发送三条 CDP 命令await Promise.all([ this.#client.send(DOM.enable), this.#client.send(CSS.enable), this.#client.send(CSS.startRuleUsageTracking), ]);其中CSS.startRuleUsageTracking是核心——它让浏览器开始记录每条 CSS 规则是否“命中”过元素。同时启动时会订阅两个事件CSS.styleSheetAdded用于记录新加载样式表的 URL 与全文Runtime.executionContextsCleared则与resetOnNavigation直接相关。2. 导航阶段执行上下文清空触发重置resetOnNavigation的判定逻辑在#onExecutionContextsCleared回调中#onExecutionContextsCleared(): void { if (!this.#resetOnNavigation) { return; } this.#stylesheetURLs.clear(); this.#stylesheetSources.clear(); }当页面发生导航时浏览器会清空旧的执行上下文并发出Runtime.executionContextsCleared事件resetOnNavigation: true默认清空#stylesheetURLs与#stylesheetSources两个映射旧页面样式表不再出现在最终报告中resetOnNavigation: false直接return样式表数据跨导航累积最终stopCSSCoverage()会汇总所有加载过的样式表。测试文件 中有两组对照用例精确验证了这一行为describe(resetOnNavigation, function () { it(should report stylesheets across navigations, async () { // resetOnNavigation: false 时两次导航后仍返回 2 个样式表条目 await page.coverage.startCSSCoverage({resetOnNavigation: false}); await page.goto(server.PREFIX /csscoverage/multiple.html); await page.goto(server.EMPTY_PAGE); const coverage await page.coverage.stopCSSCoverage(); expect(coverage).toHaveLength(2); }); it(should NOT report scripts across navigations, async () { // 默认resetOnNavigation: true时导航后旧样式表被重置 await page.coverage.startCSSCoverage(); await page.goto(server.PREFIX /csscoverage/multiple.html); await page.goto(server.EMPTY_PAGE); const coverage await page.coverage.stopCSSCoverage(); expect(coverage).toHaveLength(0); }); });3. 样式表收集阶段只收录带 sourceURL 的样式表CSS.styleSheetAdded事件触发#onStyleSheet回调这里有一个重要的过滤条件async #onStyleSheet(event: Protocol.CSS.StyleSheetAddedEvent): Promisevoid { const header event.header; // Ignore anonymous scripts if (!header.sourceURL) { return; } const response await this.#client.send(CSS.getStyleSheetText, { styleSheetId: header.styleSheetId, }); this.#stylesheetURLs.set(header.styleSheetId, header.sourceURL); this.#stylesheetSources.set(header.styleSheetId, response.text); }没有sourceURL的样式表例如通过addStyleTag({content: ...})动态注入的内联style会被直接忽略。这与 stopCSSCoverage 文档 中 “CSS Coverage doesnt include dynamically injected style tags without sourceURLs” 的说明一致也被 “should ignore injected stylesheets” 用例 验证注入内联样式后覆盖率数组长度为 0。若想为注入的样式指定名称可在样式文本中加入/*# sourceURLnicename.css */注释——“should report sourceURLs” 用例 验证了此时报告的url会是nicename.css。4. 停止阶段规则命中聚合与不相交区间转换stopCSSCoverage()的调用链是发送CSS.stopRuleUsageTracking取回ruleUsage每条规则的startOffset/endOffset/used随后发送CSS.disable与DOM.disable关闭域最后按styleSheetId聚合for (const entry of ruleTrackingResponse.ruleUsage) { ranges.push({ startOffset: entry.startOffset, endOffset: entry.endOffset, count: entry.used ? 1 : 0, // 命中记 1未命中记 0 }); }聚合后的嵌套区间会经过 convertToDisjointRanges() 处理把带命中计数的嵌套区间通过“扫描线 命中计数栈”的算法转换为互不相交的“被使用”区间列表。例如 测试资产 simple.html 中的内联样式style div { color: green; } a { color: blue; } /style divhello, world/div只有div元素存在因此 “should work” 用例 断言最终 ranges 为[{start: 1, end: 22}]恰好覆盖div { color: green; }这段文本而未被使用的a { color: blue; }不出现在结果中。覆盖率报告结构CoverageEntrystopCSSCoverage()返回CoverageEntry[]接口定义见 Coverage.tsexport interface CoverageEntry { /** 样式表或脚本的 URL */ url: string; /** 样式表或脚本的完整文本 */ text: string; /** 被覆盖的区间以起止字符偏移表示 */ ranges: Array{start: number; end: number}; }几个值得注意的行为均可由 测试用例 佐证未使用的样式表也会报告ranges为空数组“should report stylesheets that have no coverage” 用例空样式表同样报告text为空字符串“should work with empty stylesheets” 用例多个样式表分别成条一个link对应一条CoverageEntry“should report multiple stylesheets” 用例动态后加载的样式表也能被捕获只要在采集期间触发CSS.styleSheetAdded即可“should work with a recently loaded stylesheet” 用例媒体查询被正确处理media块内被使用的规则会按其真实位置报出区间“should work with media queries” 用例。实战统计页面初始加载的 CSS 使用率Coverage 类文档 给出了一个官方示例同时开启 JS 与 CSS 覆盖率计算“初始执行代码占比”。这里把它裁剪为纯 CSS 版本用于量化某页面加载后实际生效的 CSS 字节数import puppeteer from puppeteer; const browser await puppeteer.launch(); const page await browser.newPage(); await page.coverage.startCSSCoverage(); // resetOnNavigation 默认 true await page.goto(https://example.com); const coverage await page.coverage.stopCSSCoverage(); let totalBytes 0; let usedBytes 0; for (const entry of coverage) { totalBytes entry.text.length; for (const range of entry.ranges) { usedBytes range.end - range.start - 1; } } console.log(CSS bytes used: ${((usedBytes / totalBytes) * 100).toFixed(2)}%); await browser.close();若页面由多个视图SPA 路由切换属于同一上下文、传统多页应用属于跨导航组成且你希望统计“整个会话”而非“单次导航”的 CSS 使用情况应显式传入{resetOnNavigation: false}并配合stopCSSCoverage()在会话结束时统一读取。与 JSCoverageOptions 的对比同文件中还定义了 JSCoverageOptions可对照理解CSSCoverageOptions的极简设计选项CSSJSresetOnNavigation有有reportAnonymousScripts无匿名样式表直接忽略有includeRawScriptCoverage无有useBlockCoverage无有也就是说CSS 侧只暴露了“是否跨导航重置”这一个自由度其余行为忽略无sourceURL的样式表、按规则命中区间输出是固定的。适用范围与注意事项协议支持从源码结构看Coverage与CSSCoverage均实现于packages/puppeteer-core/src/cdp/目录下依赖CSS.startRuleUsageTracking等 CDP 命令因此 CSS 覆盖率能力面向 CDP 协议Chrome/Chromium。重复启动会报错startCSSCoverage()未stop前再次调用会抛出CSSCoverage is already enabled。匿名样式表被忽略无sourceURL的动态内联样式不进入报告需要时可加/*# sourceURLxxx.css */注释让其可被命名。导航重置语义resetOnNavigation: true时stopCSSCoverage()只反映“最近一次导航之后”加载的样式表跨导航累积请显式传false。与 Istanbul 生态互通如需将结果转换为 Istanbul 格式Coverage 文档提到可参考puppeteer-to-istanbul工具见 Coverage 文档。小结CSSCoverageOptions虽然只有一个可选属性resetOnNavigation默认true但它决定了覆盖率报告的时间边界单次导航还是整个会话。理解这一点再配合源码中CSS.startRuleUsageTracking→CSS.styleSheetAdded→CSS.stopRuleUsageTracking的完整调用链就能把 Puppeteer 的 CSS 覆盖率能力准确嵌入到自己的性能分析、死代码清理或测试流水线中。关键文件参考接口文档docs/api/puppeteer.csscoverageoptions.md方法文档docs/api/puppeteer.coverage.startcsscoverage.md、docs/api/puppeteer.coverage.stopcsscoverage.md源码实现packages/puppeteer-core/src/cdp/Coverage.ts测试用例test/src/coverage.test.ts测试资产test/assets/csscoverage/simple.html、test/assets/csscoverage/multiple.html【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考