ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Playwright Coverage:前端自动化测试覆盖率精准统计实战指南

2026/8/2 19:56:26 拓冰建站 浏览量
Playwright Coverage:前端自动化测试覆盖率精准统计实战指南

1. 项目概述:为什么我们需要关注测试覆盖率?

在软件开发的日常里,我们写自动化测试脚本,看着它们一个个通过,心里总会踏实不少。但一个更尖锐的问题常常被忽略:我们的测试,到底覆盖了多少代码?是只测了“主干道”,还是连那些容易出错的“犄角旮旯”也照顾到了?这就是测试覆盖率要回答的问题。它就像一个代码的“体检报告”,能直观地告诉你,哪些逻辑被执行过,哪些地方还是一片空白,从未被测试触及。

传统的覆盖率工具,比如 Istanbul(现在常以nycbabel-plugin-istanbul的形式出现),在 Node.js 后端或纯 JavaScript 单元测试领域是绝对的主力。它们通过代码插桩(Instrumentation)来统计执行情况,原理成熟,报告详尽。然而,当我们把目光投向现代前端应用,尤其是那些重度依赖浏览器 API、复杂用户交互的单页应用(SPA)时,传统工具就显得有些力不从心了。你很难用它们去准确统计一次完整的用户点击、滚动、输入操作背后,到底执行了前端源码中的哪些行、哪些分支。

这正是 Playwright Coverage 登场的时候。它不是另一个独立的覆盖率库,而是 Playwright 这个强大的浏览器自动化框架原生提供的能力。它直接“钻进”了浏览器内部,在页面加载和脚本执行时进行插桩,从而能够精准地收集到在真实浏览器环境中用户交互所触发的代码覆盖率数据。这对于前端工程师和测试工程师来说,意义重大——我们终于可以不再“盲测”,而是能清晰地看到自动化测试对前端代码的覆盖情况,从而有的放矢地补充测试用例,提升产品质量。

简单来说,如果你在用 Playwright 做 UI 自动化测试,并且关心你的测试是否足够全面,那么 Playwright Coverage 就是你工具箱里不可或缺的一件利器。它能帮你从“测试通过了”的满足感,走向“测试充分了”的底气。

2. 核心原理与架构拆解:浏览器内的代码插桩

要理解 Playwright Coverage 的强大之处,得先弄明白它和传统覆盖率工具的根本区别。这背后的核心在于“执行环境”和“插桩时机”。

2.1 传统覆盖率工具的局限

像 Istanbul 这样的工具,通常在构建阶段或测试运行前对源代码进行插桩。它会往你的代码里插入大量的计数语句,用来记录每行代码、每个函数、每个分支是否被执行。这个过程发生在 Node.js 运行时环境。对于后端 API 测试或纯逻辑的单元测试,这很完美。但是,对于前端代码:

  1. 代码分割与动态加载:现代前端应用大量使用动态import()和路由懒加载。构建时插桩的代码,在浏览器运行时可能只加载了一部分,传统的覆盖率报告无法区分“未加载”和“加载了但未执行”。
  2. 源代码映射(Source Map)问题:生产环境的前端代码通常经过压缩、混淆。即使收集到了覆盖率数据,映射回人类可读的源代码也是一大挑战,需要完整的 Source Map 链支持。
  3. 浏览器环境特异性:某些代码路径只在特定浏览器或特定用户交互下才会执行。在 Node 环境运行的覆盖率工具无法模拟这些。

2.2 Playwright Coverage 的工作原理

Playwright Coverage 采取了截然不同的路径。它利用了 Chrome DevTools Protocol (CDP) 或类似浏览器调试协议提供的ProfilerRuntime领域的能力。当你启动覆盖率收集时,Playwright 会向浏览器发送指令,开启对 JavaScript 和 CSS 覆盖率的追踪。

其工作流程可以概括为以下几个关键步骤:

  1. 启动收集:通过page.coverage.startJSCoverage()startCSSCoverage()命令,指示浏览器开始记录所有新加载的脚本和样式表的覆盖率信息。
  2. 浏览器内插桩:浏览器接收到指令后,会在其 JavaScript 引擎(如 V8)内部,对每一段即将被解析执行的脚本进行实时插桩。这个插桩过程对开发者完全透明,且发生在最贴近代码执行的位置。
  3. 执行测试:你的 Playwright 测试脚本照常运行,模拟用户点击、输入、导航等操作。所有在这些操作中被执行到的 JavaScript 代码块和应用的 CSS 规则,都会被浏览器内部的计数器记录下来。
  4. 获取结果:测试执行完毕后,调用page.coverage.stopJSCoverage()stopCSSCoverage()。此时,浏览器会将收集到的原始覆盖率数据(包含代码内容、URL、源码映射关系以及每个函数的执行范围)返回给 Playwright。
  5. 数据解析与报告生成:Playwright 返回的是原始数据。通常,我们需要使用像istanbulc8这样的工具库来处理这些数据:合并多次运行的结果、利用 Source Map 反解到源代码、计算覆盖率指标(行覆盖率、分支覆盖率等),并生成 HTML 或 LCOV 格式的报告。

注意:这里有一个关键点。Playwright 本身只负责“收集”覆盖率原始数据,不负责“生成”人类可读的报告。报告生成需要额外工具。这是一个常见的理解误区。

这种架构的优势非常明显:

  • 准确性高:直接反映浏览器真实执行路径,包括动态加载的代码。
  • 支持 CSS 覆盖率:这是很多传统工具不具备的,可以分析样式表的使用情况,用于优化 CSS 代码。
  • 与测试流程无缝集成:覆盖率收集本身就是测试脚本的一部分,易于在 CI/CD 流水线中自动化。

3. 环境准备与基础配置

在开始动手之前,我们需要搭建好 playground。假设你已经有一个 Node.js 项目,并且已经用 Playwright 写了一些测试。如果没有,跟着下面的步骤快速初始化。

3.1 项目初始化与 Playwright 安装

首先,确保你的项目根目录下有package.json。然后安装 Playwright 及其浏览器。

# 初始化项目(如果尚未初始化) npm init -y # 安装 Playwright 测试运行器及相关依赖 npm install --save-dev @playwright/test # 安装 Playwright 浏览器(Chromium, Firefox, WebKit) npx playwright install

Playwright 官方推荐使用@playwright/test这个测试运行器,它集成了断言、测试并行化、报告等多种功能,比直接用playwright库更便捷。

3.2 覆盖率报告生成工具的选型

如前所述,我们需要一个工具来处理 Playwright 收集的原始数据。主流选择有两个:

  1. v8-to-istanbul+nyc:这是较传统的组合。v8-to-istanbul能将 V8 覆盖率格式转换为 Istanbul 格式,然后由nyc生成报告。
  2. c8:一个更现代、零配置的替代品。它内部封装了v8-to-istanbul,直接读取 V8 格式的覆盖率数据,调用 Istanbul 生成报告,API 非常简洁。

对于新手和大多数项目,我强烈推荐使用c8,因为它省去了大量配置。

npm install --save-dev c8

安装完成后,你的package.jsondevDependencies应该包含@playwright/testc8

3.3 编写第一个带覆盖率的测试用例

让我们从一个最简单的例子开始。创建一个测试文件tests/example.spec.js

import { test, expect } from '@playwright/test'; test('访问首页并检查标题', async ({ page }) => { // 1. 在测试开始前,启动覆盖率收集 await Promise.all([ page.coverage.startJSCoverage(), page.coverage.startCSSCoverage() ]); // 2. 执行你的测试步骤 await page.goto('https://playwright.dev'); await expect(page).toHaveTitle(/Playwright/); // 3. 在测试结束后,停止收集并获取数据 const [jsCoverage, cssCoverage] = await Promise.all([ page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage() ]); // 4. 打印收集到的条目数(后续会替换为生成报告) console.log(`JavaScript 覆盖率条目: ${jsCoverage.length}`); console.log(`CSS 覆盖率条目: ${cssCoverage.length}`); });

这个测试做了四件事:启动收集、导航到页面、断言标题、停止收集并打印数据。现在运行它:

npx playwright test tests/example.spec.js

你会看到测试通过,并在控制台输出类似JavaScript 覆盖率条目: 15的信息。这证明覆盖率数据已经成功收集。然而,这些原始数据对我们来说像天书,下一步就是让它们变成直观的报告。

4. 集成 c8 生成可视化覆盖率报告

有了原始数据,我们使用 c8 来生成报告。c8 可以直接包装你的测试命令。

4.1 配置 package.json 脚本

package.jsonscripts部分添加以下命令:

{ "scripts": { "test": "playwright test", "test:coverage": "c8 playwright test" } }

现在,运行npm run test:coverage,c8 会先启动覆盖率收集环境(设置NODE_V8_COVERAGE环境变量),然后执行playwright test,最后在测试结束后自动生成报告。

但是,等等!直接这样运行会发现生成的报告里可能没有我们前端代码的覆盖率。这是因为默认情况下,c8 收集的是 Node.js 进程(即你的测试运行器)的覆盖率,而不是 Playwright 控制的浏览器内部的覆盖率。我们需要将 Playwright 收集的数据“喂”给 c8。

4.2 将 Playwright 覆盖率数据传递给 c8

我们需要修改测试代码,将收集到的覆盖率数据写入到 c8 能识别的目录。c8 会读取process.env.NODE_V8_COVERAGE目录下的.json文件。我们可以在测试结束后,将 Playwright 的覆盖率数据转换成 Istanbul 格式并写入该目录。

首先,安装必要的转换工具:

npm install --save-dev v8-to-istanbul

然后,创建一个公共的测试设置文件(如tests/coverage-fixture.js)或在一个全局的setup文件中处理。这里为了清晰,我们创建一个辅助函数模块tests/coverage-helper.js

// tests/coverage-helper.js import { createCoverageMap } from 'istanbul-lib-coverage'; import { createSourceMapStore } from 'istanbul-lib-source-maps'; import { convert } from 'v8-to-istanbul'; /** * 处理并保存 Playwright 收集的覆盖率数据 * @param {Array} jsCoverage - page.coverage.stopJSCoverage() 返回的数据 * @param {Array} cssCoverage - page.coverage.stopCSSCoverage() 返回的数据 * @param {string} coverageDir - c8 覆盖率输出目录,通常为 process.env.NODE_V8_COVERAGE */ export async function saveCoverage(jsCoverage, cssCoverage, coverageDir) { if (!coverageDir) { console.warn('NODE_V8_COVERAGE 环境变量未设置,跳过覆盖率保存。'); return; } const fs = await import('fs'); const path = await import('path'); const coverageMap = createCoverageMap({}); const sourceMapStore = createSourceMapStore(); // 处理 JavaScript 覆盖率 for (const entry of jsCoverage) { // 过滤掉浏览器扩展、内联脚本等不需要的源 if (!entry.url.startsWith('http') || entry.url.includes('chrome-extension')) { continue; } try { // 使用 v8-to-istanbul 转换数据 const converter = convert(entry, 0, { source: entry.source }); const istanbulCoverage = converter.toIstanbul(); coverageMap.merge(istanbulCoverage); } catch (error) { console.error(`转换覆盖率数据失败 (${entry.url}):`, error.message); } } // 将合并后的覆盖率数据写入文件 const finalCoverage = sourceMapStore.transformCoverage(coverageMap); const coverageFile = path.join(coverageDir, `playwright-coverage-${Date.now()}.json`); fs.writeFileSync(coverageFile, JSON.stringify(finalCoverage)); console.log(`覆盖率数据已保存至: ${coverageFile}`); }

接着,修改我们的测试文件,使用这个辅助函数:

// tests/example.spec.js import { test, expect } from '@playwright/test'; import { saveCoverage } from './coverage-helper.js'; // 导入辅助函数 test('访问首页并检查标题', async ({ page }) => { await Promise.all([ page.coverage.startJSCoverage(), page.coverage.startCSSCoverage() ]); await page.goto('https://playwright.dev'); await expect(page).toHaveTitle(/Playwright/); const [jsCoverage, cssCoverage] = await Promise.all([ page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage() ]); // 将覆盖率数据保存到 c8 指定的目录 await saveCoverage(jsCoverage, cssCoverage, process.env.NODE_V8_COVERAGE); });

4.3 运行并查看报告

现在,再次运行覆盖率测试命令:

npm run test:coverage

命令执行完毕后,你会在项目根目录下看到一个名为coverage的新文件夹。里面最重要的就是index.html。用浏览器打开它:

open coverage/index.html # Mac # 或 start coverage/index.html # Windows # 或直接双击文件

你将看到一个清晰的 HTML 报告,展示了所有被检测到的 JavaScript 文件的覆盖率情况,包括行覆盖率、语句覆盖率、分支覆盖率和函数覆盖率。你可以点击进入具体文件,看到每一行代码是否被测试覆盖(绿色表示已覆盖,红色表示未覆盖,黄色表示部分覆盖如分支语句)。

实操心得:第一次集成时,最常见的坑就是NODE_V8_COVERAGE目录不存在或权限问题。确保你的coverage目录可写。另外,如果测试中打开了多个标签页或浏览器上下文,需要对每个page对象单独启动和停止覆盖率收集,最后合并数据。

5. 高级配置与实战优化技巧

基础流程跑通后,我们会遇到更实际的问题:如何只收集我项目源码的覆盖率?如何合并多次测试运行的结果?如何集成到 CI/CD?下面分享一些实战中提炼出的配置和技巧。

5.1 精准过滤:只收集目标源码的覆盖率

默认情况下,Playwright 会收集页面加载的所有脚本的覆盖率,包括第三方库(如 React、Vue、jQuery)和浏览器内置 polyfill。这会导致报告噪音极大,我们真正关心的是自己编写的业务代码。

解决方法是在启动覆盖率收集时传入配置选项resetOnNavigation: falsereportAnonymousScripts: false,并在转换数据时进行 URL 过滤。

优化后的启动方式:

await page.coverage.startJSCoverage({ resetOnNavigation: false, // 页面导航时不重置数据,便于SPA测试 reportAnonymousScripts: false, // 不报告匿名脚本(如 eval 代码) });

saveCoverage辅助函数中加强过滤:

// 在循环处理 jsCoverage 时,添加更精确的过滤 for (const entry of jsCoverage) { // 示例:只收集来自特定域名和特定路径的代码 const targetOrigin = 'https://your-app.com'; const targetPathPattern = /\/src\//; // 只收集 /src/ 目录下的代码 if (!entry.url.includes(targetOrigin) || !targetPathPattern.test(entry.url)) { continue; // 跳过非目标源码 } // ... 后续转换和合并逻辑 }

更常见的做法是结合构建工具。如果你的前端代码使用 Webpack 或 Vite,它们会生成 Source Map。v8-to-istanbul可以利用 Source Map 将编译后的代码位置映射回源代码位置。确保你的测试环境加载的代码包含了正确的 Source Map 链接(通常开发模式默认包含)。

5.2 合并多次测试运行的覆盖率数据

一个完整的测试套件通常包含很多个测试文件,每个文件可能启动多个测试。我们需要在所有测试运行结束后,生成一个统一的覆盖率报告,而不是每个测试单独一份。

方案一:使用 Playwright 的全局 Setup 和 Teardown@playwright/test支持在配置文件中设置globalSetupglobalTeardown。我们可以在globalSetup中启动全局的覆盖率收集(虽然更推荐在每个测试中独立控制),在globalTeardown中统一处理和保存所有数据。但这种方法对并行测试支持不友好。

方案二:使用 c8 的合并功能(推荐)c8 本身支持合并多个.json覆盖率文件。我们只需要确保每个测试进程都将自己的覆盖率数据输出到同一个目录(即process.env.NODE_V8_COVERAGE),并且文件名不同(如上例中用时间戳区分)。当所有测试进程结束后,c8 会自动读取该目录下所有的.json文件,合并它们并生成最终报告。

这就是为什么我们在saveCoverage函数中使用Date.now()来生成唯一文件名。在 CI 环境中,确保这个目录是共享的或者最后被汇总到一起。

playwright.config.js 配置示例:

// playwright.config.js import { defineConfig } from '@playwright/test'; export default defineConfig({ // 设置测试输出目录,方便定位 outputDir: 'test-results', // 全局超时等配置... use: { // 所有测试的上下文选项 }, // 如果你有需要在所有测试结束后执行的逻辑,可以配置 teardown // globalTeardown: require.resolve('./global-teardown'), });

5.3 CI/CD 集成与阈值设定

在持续集成环境中,我们不仅需要生成报告,还希望覆盖率不达标时能“卡住”流水线,防止代码质量回退。

步骤 1:生成 LCOV 格式报告许多 CI 平台(如 GitLab CI, Jenkins)或代码托管平台(如 GitHub, GitLab)支持集成 LCOV 格式的覆盖率报告,并在 Merge Request 中显示覆盖率变化。

package.json中为 c8 增加参数:

{ "scripts": { "test:coverage:ci": "c8 --reporter=lcov --reporter=text-summary playwright test" } }

--reporter=lcov会生成coverage/lcov.info文件,--reporter=text-summary会在控制台输出一个简洁的摘要。

步骤 2:设置覆盖率阈值c8 支持通过--lines--functions--branches--statements参数或配置文件.c8rc.json来设置最低覆盖率阈值。

创建.c8rc.json文件:

{ "all": true, "include": ["src/**/*.js"], // 指定要检查的源码 "exclude": ["**/*.test.js", "**/*.spec.js", "dist/**", "node_modules/**"], "reporter": ["html", "lcov", "text-summary"], "lines": 80, // 行覆盖率不低于80% "functions": 70, // 函数覆盖率不低于70% "branches": 60, // 分支覆盖率不低于60% "statements": 80 // 语句覆盖率不低于80% }

然后运行测试时,c8 会检查最终覆盖率是否达到阈值。如果未达到,c8 进程会以非零状态码退出,导致 CI 流水线失败。

步骤 3:在 CI 中归档报告在 CI 脚本中,除了运行测试,记得将coverage目录作为产物(artifact)保存下来,方便后续查看。

一个简单的 GitHub Actions 配置示例:

# .github/workflows/test.yml name: Test and Coverage on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' - run: npm ci - run: npx playwright install --with-deps - run: npm run test:coverage:ci - name: Upload coverage report uses: actions/upload-artifact@v3 with: name: coverage-report path: coverage/ # 可选:上传到 Codecov 或 Coveralls - name: Upload to Codecov uses: codecov/codecov-action@v3 with: files: ./coverage/lcov.info

6. 常见问题排查与性能考量

在实际使用中,你可能会遇到一些棘手的情况。下面是我踩过的一些坑和对应的解决方案。

6.1 覆盖率数据为空或不全

  • 现象:生成的报告里没有你的源码文件,或者覆盖率极低。
  • 排查思路
    1. 检查 URL 过滤:首先在saveCoverage函数里把收集到的所有entry.url打印出来。确认你的前端应用源码的 URL 是否被正确加载和捕获。可能是你的过滤条件太严格,把目标文件排除了。
    2. 检查 Source Map:如果报告显示的是打包后的文件(如main.chunk.js)而不是源文件,说明 Source Map 没有正确映射。确保测试环境运行的是开发构建(development build)而非生产构建(production build),因为生产构建通常会优化或分离 Source Map。
    3. 确认收集时机:确保startJSCoverage在页面加载任何脚本之前被调用。最好的实践是在page.goto()page.setContent()之前就启动收集。对于 SPA,如果在页面加载后才启动收集,那么初始加载的代码将无法被统计。
    4. 页面导航:如果测试涉及页面跳转(非 SPA 路由),并且设置了resetOnNavigation: false,那么跳转后新页面的脚本覆盖率也会被持续记录。但如果设置了resetOnNavigation: true(默认),每次导航覆盖率都会重置,你需要根据测试场景决定使用哪种模式。

6.2 性能影响与优化

开启覆盖率收集会对测试执行速度有影响,因为浏览器需要额外的工作来插桩和记录。影响程度取决于代码量。

  • 优化建议
    • 按需收集:不要在所有测试中全局开启覆盖率。只为那些重要的端到端(E2E)测试或集成测试开启。对于单元测试或组件测试,使用传统的 Jest + Istanbul 组合可能更高效。
    • 使用独立的配置:可以创建一个单独的 Playwright 配置项(如playwright.coverage.config.js),专门用于运行需要收集覆盖率的测试套件。在这个配置里,可以通过globalSetupproject配置来统一管理覆盖率的启停。
    • 避免重复收集:如果多个测试访问同一个页面且测试场景独立,可以考虑在beforeAll钩子中启动收集,在afterAll钩子中停止并保存,而不是每个测试都做一遍。但要注意,这会使测试之间产生依赖,不利于并行化。

6.3 处理动态加载的代码块(Code Splitting)

现代前端应用普遍采用代码分割。Playwright Coverage 在这方面表现良好,因为它是在运行时插桩。只要动态import()的代码块在测试过程中被加载和执行,它就会被覆盖率收集器捕获。

注意事项:你需要确保测试用例的交互路径能触发这些动态代码块的加载。例如,测试一个懒加载的路由,你必须用 Playwright 去点击触发该路由的导航元素,并等待新内容加载完成。

test('应覆盖懒加载的模块', async ({ page }) => { await page.coverage.startJSCoverage(); await page.goto('/'); // 点击一个按钮,该按钮会动态加载 `About` 组件 await page.click('text=About Us'); // 等待新内容或网络请求完成 await page.waitForLoadState('networkidle'); const coverage = await page.coverage.stopJSCoverage(); // 检查 coverage 数据中是否包含 about.chunk.js 之类的条目 const aboutChunk = coverage.find(entry => entry.url.includes('about')); expect(aboutChunk).toBeDefined(); });

6.4 CSS 覆盖率的使用场景

page.coverage.startCSSCoverage()收集的是 CSS 规则的使用情况。这对于清理无用 CSS 样式、优化 CSS 体积非常有帮助。生成的报告会显示哪些 CSS 选择器在页面中被实际匹配过。

解读报告:CSS 覆盖率报告中的“未使用”规则,并不一定意味着可以安全删除。有些样式可能是为特定状态(如:hover,:focus)或特定媒体查询准备的,在静态页面快照中不会触发。因此,CSS 覆盖率报告更适合作为辅助参考,删除样式前仍需谨慎手动确认。

7. 与其它测试框架和工具的对比与选型

Playwright Coverage 并非唯一的选择。了解它在生态中的位置,能帮助你做出更合适的技术决策。

工具/场景优势劣势适用场景
Playwright Coverage1.浏览器环境真实,能捕获用户交互触发的代码。
2.支持 CSS 覆盖率
3. 与 Playwright E2E 测试无缝集成
4. 能处理动态加载的代码。
1.配置稍复杂,需额外工具生成报告。
2.性能开销大于单元测试覆盖率。
3. 主要针对E2E/集成测试,对纯逻辑覆盖效率低。
前端 E2E 测试覆盖率集成测试覆盖率检测未使用的 CSS
Jest + Istanbul1.配置简单,开箱即用。
2.运行速度快,适合单元测试。
3. 社区生态丰富,插件多。
1. 运行在Node 环境,无法真实反映浏览器执行路径。
2. 对需要 DOM 或浏览器 API 的组件测试,需配合 jsdom(模拟环境,可能与真实浏览器有差异)。
React/Vue 组件单元测试工具函数单元测试Node.js API 单元测试
Cypress1.自带覆盖率插件(@cypress/code-coverage),集成相对简单。
2. 同样在真实浏览器中运行。
1. 浏览器支持相对 Playwright 较少。
2. 测试运行模型(所有测试在一个浏览器实例中顺序运行)与 Playwright 不同,可能影响隔离性和并行化。
已在使用Cypress作为 E2E 测试框架的项目。
Puppeteer Coverage原理与 Playwright Coverage几乎完全相同(都基于 CDP)。Puppeteer 本身只是一个浏览器控制库,缺乏 Playwright 那种强大的测试运行器、断言库和多浏览器支持。轻量级脚本或已有 Puppeteer 基础的项目。

选型建议

  • 如果你的重点是前端应用的端到端测试质量评估,想知道用户操作流到底覆盖了多少业务代码,那么Playwright Coverage 是当前最强大、最准确的选择
  • 如果你的项目以单元测试和组件测试为主,追求极致的测试速度,那么Jest仍然是首选,它的覆盖率工具链已经非常成熟。
  • 一个成熟的现代前端项目,通常会采用混合策略:用 Jest 收集单元/组件测试的覆盖率(针对工具函数、组件逻辑),用 Playwright Coverage 收集关键用户流程的 E2E 测试覆盖率。两者可以互补,给出更全面的代码健康度视图。

我个人在大型项目中,会同时配置这两套。在 CI 中,先运行快速的单元测试并生成覆盖率报告,再运行关键的 E2E 测试并生成另一份覆盖率报告。有时甚至会尝试将两份报告合并,但这需要更复杂的工具链支持(如使用nyc merge命令)。

最后,记住覆盖率只是一个度量指标,而不是目标。追求高覆盖率本身没有错,但要警惕“为了覆盖率而测试”。100%的覆盖率不代表没有 Bug,它只意味着所有代码都被执行过。测试用例的质量——是否验证了正确的行为、是否覆盖了边界情况——远比一个单纯的百分比数字更重要。Playwright Coverage 的价值在于,它为我们提供了一个强大的透镜,让我们能看清测试的盲点,从而更有针对性地编写真正有价值的测试。