ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Puppeteer CSSCoverage.start() 深度解析:如何用 DevTools 协议追踪页面实际使用过的 CSS

Puppeteer CSSCoverage.start() 深度解析:如何用 DevTools 协议追踪页面实际使用过的 CSS Puppeteer CSSCoverage.start() 深度解析如何用 DevTools 协议追踪页面实际使用过的 CSS【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer本篇聚焦 Puppeteer API 参考文档 CSSCoverage.start() 所描述的方法签名与参数并结合 CSS 覆盖率核心实现 与 覆盖率测试用例 展开讲清楚start(options)何时调用、resetOnNavigation的默认行为如何影响跨导航统计、方法内部通过 Chrome DevTools ProtocolCDP启动了哪些能力以及它与page.coverage.startCSSCoverage()公开入口、stop()结果消费之间的关系。读完后你可以独立实现一套未使用 CSS 检测流程并理解其底层协议链路与边界行为。方法签名与参数官方文档给出的CSSCoverage.start()签名为class CSSCoverage { start(options?: {resetOnNavigation?: boolean}): Promisevoid; }参数类型说明options{ resetOnNavigation?: boolean }可选每次导航时是否重置已收集的覆盖率数据返回值为Promisevoid当底层 CDP 命令全部下发成功后 resolve。虽然签名来自 API 参考文档但需要注意CSSCoverage类本身是 Puppeteer 内部按页面对象封装的组件开发者日常使用的公开入口是Page.coverage属性见 Page 抽象定义实际调用形式为// options 参数类型即 CSSCoverageOptions await page.coverage.startCSSCoverage({resetOnNavigation: false});Coverage类的 startCSSCoverage 实现 只是把options原样转发给内部的CSSCoverage.start(options)两者行为完全一致。参数类型定义对应 TypeScript 接口定义在 Coverage.ts 的 CSSCoverageOptionsexport interface CSSCoverageOptions { /** * Whether to reset coverage on every navigation. */ resetOnNavigation?: boolean; }resetOnNavigation 的默认值与生效机制从源码看start()中对选项做了展开默认值处理Coverage.ts#L358-L361async start(options: {resetOnNavigation?: boolean} {}): Promisevoid { assert(!this.#enabled, CSSCoverage is already enabled); const {resetOnNavigation true} options; this.#resetOnNavigation resetOnNavigation; ... }也就是说resetOnNavigation不传时默认为true这与 API 参考文档中可选参数的表述互补文档说明它可选源码补充了它的默认值语义。其生效时机是浏览器执行上下文被清空时页面导航触发Runtime.executionContextsCleared事件对应处理逻辑#onExecutionContextsCleared(): void { if (!this.#resetOnNavigation) { return; } this.#stylesheetURLs.clear(); this.#stylesheetSources.clear(); }resetOnNavigation: true默认每次导航后此前页面收集的样式表记录全部清空stop()只返回当前页面的覆盖率resetOnNavigation: false跨导航累计适合测量 SPA 或多页应用中整个会话用到了哪些样式。这一行为有明确的测试佐证test/src/coverage.test.ts#L297-L316describe(resetOnNavigation, function () { it(should report stylesheets across navigations, async () { 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 () { await page.coverage.startCSSCoverage(); // Enabled by default. await page.goto(server.PREFIX /csscoverage/multiple.html); await page.goto(server.EMPTY_PAGE); const coverage await page.coverage.stopCSSCoverage(); expect(coverage).toHaveLength(0); // 默认 true导航后数据被重置 }); });start() 内部的协议链路start()并不只是一句开启开关从 Coverage.ts#L358-L380 可以看到它依次完成四件事防重入断言assert(!this.#enabled, CSSCoverage is already enabled)。重复调用start会直接抛出CSSCoverage is already enabled断言错误因此典型用法是start 一次 → 操作页面 → stop 一次而不是每步都 start。状态复位与监听器注册清空#stylesheetURLs、#stylesheetSources两张映射表样式表 ID 到 URL、ID 到源码文本并通过DisposableStack管理两个 CDP 事件监听器CSS.styleSheetAdded页面每加载一张样式表都会触发回调 onStyleSheet 会通过CSS.getStyleSheetText命令拉取完整样式文本并缓存Runtime.executionContextsCleared如上文所述驱动resetOnNavigation逻辑。并行下发三条 CDP 命令await Promise.all([ this.#client.send(DOM.enable), this.#client.send(CSS.enable), this.#client.send(CSS.startRuleUsageTracking), ]);其中CSS.startRuleUsageTracking是关键——它让浏览器开始统计每条 CSS 规则是否被实际匹配应用rule usage tracking。stop()阶段正是通过CSS.stopRuleUsageTracking取回这份ruleUsage数据逐条转换为{startOffset, endOffset, count: used ? 1 : 0}的区间记录Coverage.ts#L408-L433。 4. 由于start是async方法且末尾await Promise.all([...])await start(...)返回即代表追踪已就绪此时再导航不会遗漏样式表。样式表收集的两条隐性规则结合onStyleSheet实现有两个容易踩坑的边界行为均被 coverage.test.ts 中的用例固化没有sourceURL的样式表会被忽略。回调开头即if (!header.sourceURL) return;因此通过page.addStyleTag({content: ...})注入的内联样式不会出现在覆盖率报告中测试用例 should ignore injected stylesheets 断言coverage长度为 0。需要按 URL 归组统计时样式必须来自真实文件不过源码支持sourceURL注释测试用例 should report sourceURLs 验证了带sourceURL的样式会以自定义名称如nicename.css上报。样式文本拉取失败会被静默记录。若页面在拉取文本前已经导航离开CSS.getStyleSheetText可能抛错此时仅通过 loggerDEBUG_PREFIXES.error输出错误日志而不中断整体流程保证覆盖率统计的健壮性。与 stop() 配套使用完整工作流start()只负责开始追踪结果消费由stop()完成。stop()返回CoverageEntry[]每个条目的结构定义在 Coverage.ts#L21-L34export interface CoverageEntry { url: string; // 样式表或脚本的 URL text: string; // 样式表或脚本的完整内容 ranges: Array{start: number; end: number}; // 被使用的字符区间半开区间 }ranges是由CSS.stopRuleUsageTracking返回的嵌套规则区间经 convertToDisjointRanges 函数做扫描线交集计算后得到的互不重叠区间可以直接用text.substring(range.start, range.end)取回被用到的 CSS 片段。官方文档类注释Coverage.ts#L95-L116给出的标准用法如下将 JS 与 CSS 覆盖率合并计算总使用率// Enable both JavaScript and CSS coverage await Promise.all([ page.coverage.startJSCoverage(), page.coverage.startCSSCoverage(), ]); // Navigate to page await page.goto(https://example.com); // Disable both JavaScript and CSS coverage const [jsCoverage, cssCoverage] await Promise.all([ page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage(), ]); let totalBytes 0; let usedBytes 0; const coverage [...jsCoverage, ...cssCoverage]; for (const entry of coverage) { totalBytes entry.text.length; for (const range of entry.ranges) usedBytes range.end - range.start - 1; } console.log(Bytes used: ${(usedBytes / totalBytes) * 100}%);注意usedBytes累加时用了range.end - range.start - 1因为ranges区间是起始包含、结束不包含的偏移约定与测试断言text.substring(range.start, range.end)一致按字符数统计时要减 1。行为边界一览来自测试用例test/src/coverage.test.ts 的CSSCoverage用例集基本覆盖了start()后的所有关键场景场景测试用例断言结果基础追踪simple.html1 个条目区间{start:1, end:22}对应div { color: green; }多样式表multiple.html返回 2 个条目分别对应 stylesheet1.css / stylesheet2.css零命中样式表unused.html条目存在但ranges为空数组未使用的 CSS 以空区间体现媒体查询media.html按规则分别统计产生多个离散区间空样式表empty.htmltext为空字符串的条目仍然返回延迟加载的样式表recently loaded stylesheetstart 之后动态link注入的样式表也会被纳入动态注入内联样式addStyleTag 注入不产生任何条目无 sourceURL复杂综合页面involved.html与 golden 文件 csscoverage-involved.txt 逐字比对使用建议与限制调用时序startCSSCoverage()必须在page.goto()等导航之前await完成否则首屏加载的样式表不会被收集start之后页面每加载一张样式表都会触发一次CSS.getStyleSheetText拉取对大量样式表的页面会有一定的额外协议流量开销。CDP 专属能力整条链路DOM.enable、CSS.enable、CSS.startRuleUsageTracking都基于 Chrome DevTools Protocol因此该功能只在走 CDP 连接的浏览器Chrome/Chromium 系下可用。与 Istanbul 生态衔接Coverage类注释中说明若需将结果转换为 Istanbul 可消费格式可参考社区工具 puppeteer-to-istanbul仓库内 browsers API 文档 与 coverage.startcsscoverage 文档、CSSCoverage 类文档、stop() 文档 可作为本主题的延伸阅读。小结CSSCoverage.start()是 Puppeteer CSS 覆盖率追踪链路的起点一个可选的resetOnNavigation默认true决定跨导航是否累计数据方法内部通过注册CSS.styleSheetAdded、Runtime.executionContextsCleared监听并下发CSS.startRuleUsageTracking等 CDP 命令把样式表收集与规则命中追踪两条数据流同时挂起最终由stop()汇聚成{url, text, ranges}报告。理解了 Coverage.ts 中这 30 行左右的start实现再对照 coverage.test.ts 的边界用例就能准确预测resetOnNavigation、内联样式、延迟加载样式等场景下的实际输出。【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进