monocart-coverage-reports
v3.1.0
Published
A code coverage tool to generate native V8 reports or Istanbul reports.
Readme
Monocart Coverage Reports
🌐 English | 简体中文
- 用法
- 最佳实践
- 选项配置
- 所有支持的报告类型
- 比较两种报告
- 如何收集Istanbul覆盖率数据
- 如何收集V8覆盖率数据
- 过滤V8覆盖率数据
- 使用
sourcePath修改源文件路径 - 为未测试的文件添加空的覆盖率报告
- 回调钩子 Hooks
- 如何忽略未覆盖的代码
- 多进程支持
- 如何使用CLI命令行
- 如何加载配置文件
- 使用
mcr test运行测试 - 如何合并覆盖率报告
- 常见问题
- 如何调试覆盖率数据和查看sourcemap
- 如何跟其他框架集成
- 集成的例子
- Contributing
- 更新日志
- 感谢
Usage
MCR 3.x 的所有 API 和 CLI 命令均要求 Node.js >= 22.15.0,包括普通命令、
merge和test。
从 2.x 升级: 请参阅 3.x 迁移指南。
- 安装
npm install monocart-coverage-reports- API
import { MCR } from 'monocart-coverage-reports';
const mcr = MCR({
name: 'My Coverage Report - 2024-02-28',
outputDir: './coverage-reports',
reports: ['v8', 'console-details'],
cleanCache: true
});
await mcr.add(coverageData);
await mcr.generate();也可以使用ESM的 import 然后加载配置文件
import { CoverageReport } from 'monocart-coverage-reports';
const mcr = new CoverageReport();
await mcr.loadConfig();
MCR({...})和new CoverageReport()创建的是同一个实例。MCR()是简写工厂函数;new CoverageReport()更适合需要单独调用loadConfig()的场景。
参见 多进程支持
- CLI
mcr node my-app.js -r v8,console-details参见 命令行
Best Practices
为新项目建立测试方案时,优先使用无需插桩的原生覆盖率采集:
| 测试目标 | 推荐方案 | 职责划分 |
| --- | --- | --- |
| Node.js 端 | Node.js 原生 test runner + mcr test | node:test 执行测试断言;MCR 自动采集 JavaScript 原生 V8 覆盖率并生成报告。 |
| 浏览器端 | Playwright Test + MCR | Playwright 执行测试断言;自动 fixture 采集 Chromium JS/CSS 覆盖率;MCR 合并并生成报告。 |
| 浏览器端,需要统一测试报告 | Playwright Test + monocart-reporter | reporter 已集成 MCR;配置其 coverage 选项,并由 fixture 提交覆盖率数据,统一管理测试报告和覆盖率报告。 |
详细配置步骤和示例见最佳实践指南。
AI Agent Skill
使用 AI 辅助开发本仓库源码时,请先阅读 AGENTS.md,了解源码结构、开发命令、验证流程和安全边界。.github/copilot-instructions.md 为 GitHub Copilot 提供可独立使用的核心规则摘要。这些指南仅用于源码仓库,不随 npm 包发布,与下面可复用的集成技能相互独立。
项目提供可复用的 Monocart Coverage Reports 技能,帮助 AI coding agent 采集覆盖率、配置 CLI、排查 sourcemap 和过滤问题、合并 raw 报告及设置 CI 阈值。结构为主 SKILL.md 加按需加载的专题参考文档,仅面向 MCR 3.x,并检查其运行环境要求。
npm 包包含 skills/monocart-coverage-reports/。请将整个目录(包括 references/)复制到 agent 支持的 skills 目录,或直接让 agent 读取 SKILL.md。安装 MCR 不会自动注册技能或修改 agent 配置。技能说明使用英文,但提问和回复可以使用中文或其他语言。
在使用 MCR 的项目根目录中定位已安装的技能和包配置(旧版本可能尚未包含技能):
node -p "require.resolve('monocart-coverage-reports/skills/monocart-coverage-reports/SKILL.md')"
node -p "require.resolve('monocart-coverage-reports/package.json')"读取定位到的技能文件,再按相对链接读取专题文档。请以已安装包的运行时、API 和版本要求为准,不要假设所有示例都适用于旧版本。以上命令用于定位文件,不是将 Markdown 作为 JavaScript 导入。
Options
- 默认选项: lib/default/options.js
- 选项的类型描述,见
CoverageReportOptionslib/index.d.ts - 配置文件
| 选项 | 类型 | 默认值 | 说明 |
| :-- | :-- | :-- | :-- |
| logging | "off" \| "error" \| "info" \| "debug" | "info" | 日志级别。设为 "debug" 会保留原始缓存并输出 sourcemap,便于排查问题。参见 调试覆盖率和sourcemap。 |
| name | string | "Coverage Report" | 报告标题,显示在 UI 以及 summary 报告中。 |
| reports | string \| (string \| ReportDescription)[] | "" (自动) | 生成哪些报告。V8 数据默认 "v8",Istanbul 数据默认 "html"。参见 所有支持的报告类型。 |
| outputDir | string | "./coverage-reports" | 所有报告以及 .cache 的输出目录。 |
| inputDir | string \| string[] | null | 导入的原始覆盖率数据目录(用于 合并)。 |
| baseDir | string | process.cwd() | 规范化相对源路径的基准目录。当生成报告的工作目录和源码目录不一致时需要设置。 |
| dataDir | string | null | generate() 阶段自动加载的覆盖率数据目录,作为 addFromDir() 的替代。 |
| entryFilter | string \| object \| function | null | (仅 V8)按 URL 过滤入口文件,参见 过滤V8覆盖率数据。 |
| sourceFilter | string \| object \| function | null | (仅 V8)过滤从 sourcemap 解包出的源文件。 |
| outputFilter | string \| object \| function | null | (仅 V8)在 sourcemap 转换后,按最终 sourcePath 过滤文件。参见 过滤V8覆盖率数据。 |
| filter | string \| object \| function | null | 合并 entryFilter 和 sourceFilter 的统一过滤器。 |
| sourcePath | object \| function | null | 修改源文件路径,参见 使用 sourcePath。 |
| all | string \| string[] \| object | null | 为未测试文件添加空覆盖率,参见 为未测试的文件添加空的覆盖率报告。 |
| outputFile | string | "index.html" | (仅 V8)V8 报告的 [子目录/]文件名。 |
| inline | boolean | false | (仅 V8)将所有资源内联到单一 HTML 文件。 |
| assetsPath | string | "./assets" | (仅 V8)非内联模式下的资源目录。 |
| lcov | boolean | false | 额外生成 lcov.info,等同于添加 lcovonly 报告。 |
| v8Ignore | boolean | true | (仅 V8)启用/禁用基于注释的忽略(v8 ignore …),参见 忽略未覆盖代码。 |
| reportPath | string \| () => string | null | 覆盖默认的报告入口路径(默认 outputDir/index.html)。多个报告共用同一 outputDir 时很有用。 |
| watermarks | [number, number] \| object | [50, 80] | 覆盖率高/低阈值百分比。也支持按指标配置:{ bytes:[50,80], lines:[50,80] }。 |
| clean | boolean | true | 生成报告前清理 outputDir 中的旧报告(.cache 目录在 cleanCache 也为 true 时才会被清理)。 |
| cleanCache | boolean | false | 启动时清理缓存目录。当 clean 和 cleanCache 都为 false 时,之前运行的缓存数据会保留,可在多进程间复用。 |
| gc | number | null | 内存阈值(MB),在关键阶段若 JavaScript 堆内存使用量(heapUsed)超过阈值则强制触发 GC,适用于超大数据集,参见 JavaScript heap out of memory。 |
| sourceMap | boolean | false | 将源文件和 sourcemap 也保存到 cache 便于调试(需要 logging: "debug")。 |
| sourceMapResolver | (url, defaultResolver) => Promise<string \| object> | null | 自定义 sourcemap 加载逻辑(例如从构建缓存中读取),可调用 defaultResolver(url) 回退到默认解析。 |
| onEntry | (entry) => void \| Promise<void> | null | (仅 V8)每个入口文件处理前触发,参见 Hooks。 |
| onEnd | (coverageResults) => void \| Promise<void> | null | 报告生成完成后触发,参见 Hooks。 |
Available Reports
内置V8报告(仅V8格式数据支持):
v8

v8-json- 保存
CoverageResults到一个json文件 (默认是coverage-report.json) - 用于VSCode扩展来显示原生V8代码覆盖率: Monocart Coverage for VSCode
- 保存

内置Istanbul报告 (V8和Istanbul格式数据都支持):
clovercoberturahtmlhtml-spajsonjson-summarylcovlcovonlynoneteamcitytexttext-lcovtext-summary
其他内置报告 (V8和Istanbul格式数据都支持):
codacy保存覆盖率数据到 Codacy 专属的json文件 (默认是codacy.json)console-summary在控制台显示覆盖率概要

console-details在控制台显示每个文件的覆盖率概要。如果是Github actions,可以使用环境变量FORCE_COLOR: true来强制开启颜色支持

markdown-summary保存概要信息到markdown文件 (默认是coverage-summary.md)。 如果是Github actions, 可以把markdown的内容添加到a job summary
cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
markdown-details保存覆盖率详情到markdown文件 (默认是coverage-details.md)- 预览运行结果 runs
raw只是保存原始覆盖率数据, 用于使用inputDir参数来导入多个原始数据进行合并报告。参见 合并覆盖率报告自定义报告
const coverageOptions = { reports: [ [path.resolve('./test/custom-istanbul-reporter.js'), { type: 'istanbul', file: 'custom-istanbul-coverage.text' }], [path.resolve('./test/custom-v8-reporter.js'), { type: 'v8', outputFile: 'custom-v8-coverage.json' }], [path.resolve('./test/custom-v8-reporter.mjs'), { type: 'both' }] ] };- Istanbul自定义报告
例子: ./test/custom-istanbul-reporter.js, see istanbul built-in reporters' implementation for reference.
- V8自定义报告
type选项决定报告接口及执行时机:'istanbul'— 使用 Istanbul 报告接口:先调用new Reporter(reportOptions),再调用execute(context)。context 提供 Istanbul 报告树和源码查找能力。V8 输入会先转换为 Istanbul 覆盖率数据。'v8'(默认值)— 对 V8 输入,先调用new Reporter(reportOptions, globalOptions),再调用await generate(reportData)。reportData是 V8 覆盖率结果,包含type、summary和files;每个文件包含 V8 覆盖范围和汇总。'both'— 对 V8 或 Istanbul 输入均使用上述generate(reportData)接口。请检查reportData.type,按对应的结果结构处理。它不会同时提供两份原始格式的数据,结果中不存在v8Data或istanbulData属性。
报告统一使用原生 ESM
import()加载。ESM 报告使用export default Reporter;通过 Node 内置互操作,也可加载module.exports = Reporter的 CommonJS 报告(在 ESM 项目中使用.cjs)。本地路径必须明确指定文件扩展名,不补全扩展名、不解析目录入口,不提供独立的 CommonJS 加载器。报告加载或初始化失败会使报告生成失败,不会静默跳过。
Multiple Reports:
如何配置多个报告
import { MCR } from 'monocart-coverage-reports';
const coverageOptions = {
outputDir: './coverage-reports',
reports: [
// built-in reports
['console-summary'],
['v8'],
['html', {
subdir: 'istanbul'
}],
['json', {
file: 'my-json-file.json'
}],
'lcovonly',
// custom reports
// Specify reporter name with the NPM package
['custom-reporter-1'],
['custom-reporter-2', {
type: 'istanbul',
key: 'value'
}],
// Specify reporter name with local path
['/absolute/path/to/custom-reporter.js']
]
};
const mcr = MCR(coverageOptions);Compare Reports
如果是V8数据格式使用Istanbul的报告,将自动从V8转换到Istanbul
| | Istanbul | V8 | V8 to Istanbul | | :--------------| :------ | :------ | :---------------------- | | 数据格式 | Istanbul (Object) | V8 (Array) | V8 (Array) | | 输出报告 | Istanbul reports | V8 reports | Istanbul reports | | - Bytes 字节覆盖率 | ❌ | ✅ | ❌ | | - Statements 语句覆盖率 | ✅ | ✅ | ✅ | | - Branches 分支覆盖率 | ✅ | ✅ | ✅ | | - Functions 函数覆盖率 | ✅ | ✅ | ✅ | | - Lines 行覆盖率 | ✅ | ✅ | ✅ | | - Execution counts 函数执行数 | ✅ | ✅ | ✅ | | CSS 覆盖率 | ❌ | ✅ | ✅ | | 压缩过的代码 | ❌ | ✅ | ❌ |
Collecting Istanbul Coverage Data
在收集Istanbul覆盖率数据之前,需要编译源代码来安装Istanbul计数器
- webpack babel-loader: babel-plugin-istanbul, 参见例子: webpack.config-istanbul.js
- 官方CLI: nyc instrument 或API: istanbul-lib-instrument
- vite: vite-plugin-istanbul
- rollup: rollup-plugin-istanbul
- swc: swc-plugin-coverage-instrument
从浏览器
- Istanbul的覆盖率数据会保存到全局的
window.__coverage__,直接读取即可, 参见例子: test-istanbul.js
- Istanbul的覆盖率数据会保存到全局的
从Node.js
- 同理对于Node.js会保存到全局的
global.__coverage__
- 同理对于Node.js会保存到全局的
使用CDP
getIstanbulCoverage()参见CDPClientAPI
Collecting V8 Coverage Data
在收集V8覆盖率数据之前,需要开启构建工具的
sourcemap支持,并且不要压缩代码- webpack:
devtool: source-mapandmode: development, example webpack.config-v8.js - rollup:
sourcemap: trueandtreeshake: false - esbuild:
sourcemap: true,treeShaking: falseandminify: false - vite:
sourcemap: trueandminify: false
- webpack:
浏览器 (仅支持基于Chromium的浏览器)
从Node.js
使用CDP
Collecting V8 Coverage Data with Playwright
使用Playwright的覆盖接口收集覆盖率数据
await Promise.all([
page.coverage.startJSCoverage({
// reportAnonymousScripts: true,
resetOnNavigation: false
}),
page.coverage.startCSSCoverage({
// Note, anonymous styles (without sourceURLs) are not supported, alternatively, you can use CDPClient
resetOnNavigation: false
})
]);
await page.goto('your page url');
const [jsCoverage, cssCoverage] = await Promise.all([
page.coverage.stopJSCoverage(),
page.coverage.stopCSSCoverage()
]);
const coverageData = [... jsCoverage, ... cssCoverage];
Playwright Test 自动 fixture 参见完整 playwright-coverage 示例中的 fixtures.js,接入方式见最佳实践指南。 参见例子 ./test/test-v8.js, css
Collecting Raw V8 Coverage Data with Puppeteer
取决于包管理器配置,Puppeteer 可能不会在安装依赖时下载浏览器。如果没有可用浏览器,请手动安装:
npx puppeteer browsers install chrome
使用Puppeteer的覆盖接口收集覆盖率数据,注意Puppeteer默认不会提供原生V8的覆盖率数据,需要设置includeRawScriptCoverage
await Promise.all([
page.coverage.startJSCoverage({
// reportAnonymousScripts: true,
resetOnNavigation: false,
// provide raw v8 coverage data
includeRawScriptCoverage: true
}),
page.coverage.startCSSCoverage({
resetOnNavigation: false
})
]);
await page.goto('your page url');
const [jsCoverage, cssCoverage] = await Promise.all([
page.coverage.stopJSCoverage(),
page.coverage.stopCSSCoverage()
]);
// to raw V8 script coverage
const coverageData = [... jsCoverage.map((it) => {
return {
source: it.text,
... it.rawScriptCoverage
};
}), ... cssCoverage];Collecting V8 Coverage Data from Node.js
有多种方法可以从Node.js收集V8覆盖率数据:
NODE_V8_COVERAGE=
dir- 使用Node.js环境变量
NODE_V8_COVERAGE=dir来启动程序, 然后在进程正常结束之后,覆盖率数据将自动保存到指定的dir目录. - 从
dir目录读取所有的JSON文件,来生成覆盖率报告 - 参见例子:
cross-env NODE_V8_COVERAGE=
.temp/v8-coverage-envnode ./test/test-node-env.js && node ./test/generate-report.js- 使用Node.js环境变量
V8 API + NODE_V8_COVERAGE
- 如果进程不能正常结束,比如被强制关闭,或者压根就不结束,比如启动了一个服务类的,那么需要手动写入覆盖率数据,这里需要调用接口
v8.takeCoverage() - 参见例子:
cross-env NODE_V8_COVERAGE=
.temp/v8-coverage-apinode ./test/test-node-api.js- 如果进程不能正常结束,比如被强制关闭,或者压根就不结束,比如启动了一个服务类的,那么需要手动写入覆盖率数据,这里需要调用接口
Inspector API
- 首先连接到Node.js的V8 inspector
- 然后使用inspector的覆盖相关API来开启和收集覆盖率数据
- 参见例子:
- vm的例子 (注意这里需要使用
scriptOffset,因为vm里一般都会加一层包裹代码,需要这个偏移位置来修正覆盖率数据块的位置):
CDP API
- 开启Node调试
- 使用CDP的覆盖率接口开启和收集覆盖率数据
- 参见例子:
node --inspect=9229 ./test/test-node-cdp.js
Node Debugging + CDP + NODE_V8_COVERAGE + V8 API
- 如果启动了一个Node服务,可以手动调用
v8.takeCoverage()接口来保存覆盖率数据,开启Node调试就可以远程通过CDP连接的Runtime.evaluate,来调用这个接口. - 参见koa的例子:
- 如果启动了一个Node服务,可以手动调用
Child Process + NODE_V8_COVERAGE
- 如果是子进程,可参见 命令行
Collecting V8 Coverage Data with CDPClient API
timeout 控制 HTTP 调试目标发现和 WebSocket 连接的超时(默认 10000ms);requestTimeout 控制内置 WebSocket 会话中每条 CDP 命令的超时(默认 30000ms)。设为 0 可禁用相应超时。外部 Playwright/Puppeteer 会话自行管理命令超时。自定义 socket 可使用 new MCR.WSSession(ws, { timeout: 30000 })。
CDPClient为MCR提供的内置接口类,用来更便捷的处理覆盖率相关数据,所有的API如下
interface CoverageClient {
// 开始和停止并收集JS的覆盖率数据
startJSCoverage: () => Promise<void>;
stopJSCoverage: () => Promise<V8CoverageEntry[]>;
// 开始和停止并收集CSS的覆盖率数据,支持匿名文件(比如style里的css)
startCSSCoverage: () => Promise<void>;
stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
// 开始和停止并收集JS和CSS的覆盖率数据
startCoverage: () => Promise<void>;
stopCoverage: () => Promise<V8CoverageEntry[]>;
/** 如果开启了NODE_V8_COVERAGE,这个接口用来手动保存当前覆盖率数据 */
writeCoverage: () => Promise<string>;
/** 收集istanbul覆盖率数据 */
getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
}- 结合使用Node调试端口
--inspect=9229或者浏览器调试端口--remote-debugging-port=9229
import { MCR } from 'monocart-coverage-reports';
const client = await MCR.CDPClient({
port: 9229
});
await client.startJSCoverage();
// run your test here
const coverageData = await client.stopJSCoverage();import { chromium } from 'playwright';
import { MCR } from 'monocart-coverage-reports';
const browser = await chromium.launch();
const page = await browser.newPage();
const session = await page.context().newCDPSession(page);
const client = await MCR.CDPClient({
session
});
// both js and css coverage
await client.startCoverage();
// run your test page here
await page.goto('your page url');
const coverageData = await client.stopCoverage();- 结合使用 Puppeteer CDPSession
import puppeteer from 'puppeteer';
import { MCR } from 'monocart-coverage-reports';
const browser = await puppeteer.launch({});
const page = await browser.newPage();
const session = await page.target().createCDPSession();
const client = await MCR.CDPClient({
session
});
// both js and css coverage
await client.startCoverage();
// run your test page here
await page.goto('your page url');
const coverageData = await client.stopCoverage();- 结合使用 Selenium Webdriver WebSocket (仅支持Chrome/Edge浏览器)
import { Builder, Browser } from 'selenium-webdriver';
import { MCR } from 'monocart-coverage-reports';
const driver = await new Builder().forBrowser(Browser.CHROME).build();
const pageCdpConnection = await driver.createCDPConnection('page');
const session = new MCR.WSSession(pageCdpConnection._wsConnection);
const client = await MCR.CDPClient({
session
});V8 Coverage Data API
- JavaScript V8代码覆盖官方说明
- Playwright的覆盖率接口
- Puppeteer的覆盖率接口
- DevTools Protocol的覆盖率接口 参见 ScriptCoverage 和 v8-coverage
// Coverage data for a source range.
export interface CoverageRange {
// JavaScript script source offset for the range start.
startOffset: integer;
// JavaScript script source offset for the range end.
endOffset: integer;
// Collected execution count of the source range.
count: integer;
}
// Coverage data for a JavaScript function.
/**
* @functionName can be an empty string.
* @ranges is always non-empty. The first range is called the "root range".
* @isBlockCoverage indicates if the function has block coverage information.
If this is false, it usually means that the function was never called.
It seems to be equivalent to ranges.length === 1 && ranges[0].count === 0.
*/
export interface FunctionCoverage {
// JavaScript function name.
functionName: string;
// Source ranges inside the function with coverage data.
ranges: CoverageRange[];
// Whether coverage data for this function has block granularity.
isBlockCoverage: boolean;
}
// Coverage data for a JavaScript script.
export interface ScriptCoverage {
// JavaScript script id.
scriptId: Runtime.ScriptId;
// JavaScript script name or url.
url: string;
// Functions contained in the script that has coverage data.
functions: FunctionCoverage[];
}
export type V8CoverageData = ScriptCoverage[];| JavaScript Runtime | V8 Coverage | | | :--------------| :----: | :---------------------- | | Chrome (65%) | ✅ | Chromium-based | | Safari (18%) | ❌ | | | Edge (5%) | ✅ | Chromium-based | | Firefox (2%) | ❌ | | | Node.js | ✅ | | | Deno | ❌ | issue | | Bun | ❌ | issue |
MCR 为每个 V8 覆盖率条目扩展了以下字段:
| 字段 | 类型 | 说明 |
| :-- | :-- | :-- |
| url | string | 脚本 URL 或文件路径 |
| type | "js" \| "css" | 条目类型 |
| source | string | (JS)源代码文本 |
| sourceMap | object | (JS)已解析的 sourcemap 对象 |
| scriptOffset | number | (JS)脚本包裹层的字节偏移(例如 vm 模块中)。此偏移会自动加到所有 range 的 start/end 值上 |
| distFile | string | (JS)该 source 解包自的 dist 打包文件 |
| empty | boolean | 当条目通过 all 选项添加(无真实覆盖率数据)时标记为 true,显示为 0% 覆盖 |
| fake | boolean | 当源代码非原始代码(例如从 .ts/.jsx 编译而来)时标记为 true。AST 解析器会跳过 fake 源的覆盖率提取 |
这些字段可以在 onEntry 钩子中读取或修改。
Filtering Results
Using entryFilter and sourceFilter to filter the results for V8 report
当收集到V8的覆盖数据时,它实际上包含了所有的入口文件的覆盖率数据, 比如有以下3个文件:
- dist/main.js
- dist/vendor.js
- dist/something-else.js
这个时候可以使用entryFilter来过滤这些入口文件. 比如我们不需要看到vendor.js和something-else.js的覆盖率,就可以过滤掉,只剩下1个文件
- dist/main.js
如果一个入口文件存在行内或者链接的sourcemap文件,那么我们会尝试读取并解析sourcemap,以获取入口文件包含的所有源文件,并添加到列表。此时如果logging没有设置成debug,那么这个入口文件在成功解出源文件后会被移除
- src/index.js
- src/components/app.js
- node_modules/dependency/dist/dependency.js
这个时候可以使用sourceFilter来过滤这些源文件。比如我们不需要看到源文件dependency.js的覆盖率,就可以过滤掉,最后只剩下如下文件
- src/index.js
- src/components/app.js
过滤可以使用函数:
const coverageOptions = {
entryFilter: (entry) => entry.url.indexOf('main.js') !== -1,
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
};也可以使用便捷的minimatch来匹配(推荐):
const coverageOptions = {
entryFilter: '**/main.js',
sourceFilter: '**/src/**'
};支持多个匹配:
const coverageOptions = {
entryFilter: {
'**/node_modules/**': false,
'**/vendor.js': false,
'**/src/**': true
},
sourceFilter: {
'**/node_modules/**': false,
'**/**': true
}
};作为CLI参数(JSON字符串,Added in: v2.8):
mcr --sourceFilter "{'**/node_modules/**':false,'**/**':true}"注意,这些匹配实际上会转换成一个过滤函数(如下),所以如果一个匹配成功则会直接返回,后面的将不再继续匹配。请注意先后顺序,如果存在包含关系的,可以调整上下顺序,最后如果都未匹配,则默认返回false
const coverageOptions = {
entryFilter: (entry) => {
if (minimatch(entry.url, '**/node_modules/**')) {
return false;
}
if (minimatch(entry.url, '**/vendor.js')) {
return false;
}
if (minimatch(entry.url, '**/src/**')) {
return true;
}
// else unmatched
return false;
}
};Using filter instead of entryFilter and sourceFilter
如果你不想定义两个过滤器,可以使用 filter 选项代替,可以将多个匹配合并在一起. (Added in: v2.8)
const coverageOptions = {
// combined patterns
filter: {
'**/node_modules/**': false,
'**/vendor.js': false,
'**/src/**': true,
'**/**': true
}
};使用 outputFilter 过滤最终文件
版本要求:
outputFilter是 v3.0.0 新增的选项,v2.13.x 及更早版本不支持。
两套过滤方案选一套即可,不要混淆。 outputFilter 与已有的 entryFilter + sourceFilter 都用于决定报告中保留哪些文件,功能重叠,是两种替代方案,而不是必须叠加配置的过滤器。合并选项 filter 只是 entryFilter + sourceFilter 的简写,不会配置 outputFilter。
| 方案 | 过滤时机和对象 | 优点 | 代价与注意事项 |
| :-- | :-- | :-- | :-- |
| entryFilter + sourceFilter(或 filter) | 从输入端过滤:在发现入口文件时,entryFilter 按 URL 过滤;在 sourcemap 解包源文件时,sourceFilter 按源路径过滤。 | 尽早排除不需要的文件,减少后续无效处理,性能更高。大型项目或 bundle 较多时,优先推荐此方案。 | 需要区分入口文件和 sourcemap 源文件,并配置两个阶段的规则。如果通过 entryFilter 排除整个 bundle,其中需要保留的源文件也会一起丢失。 |
| outputFilter | 从输出结果过滤:在 sourcemap 转换、合并和添加未测试文件(all)完成后、生成报告前,按最终 sourcePath 过滤。 | 配置更简单,一套规则直接面向最终文件列表,无需区分入口文件和解包后的源文件。 | 不需要的文件在被丢弃前仍会经历前期发现和处理,无法节省这些开销。当最终排除的文件较多时,可能带来明显的生成耗时和内存开销,存在性能问题。 |
选择建议: 优先考虑性能时,使用输入过滤方案(entryFilter + sourceFilter,或 filter);更看重配置简单、希望直接过滤最终结果,且可以接受额外处理成本时,选择 outputFilter。切换到 outputFilter 时,应移除同一范围的旧输入过滤配置:输入阶段已经排除的文件,无法通过输出过滤恢复。
对于 V8 输入,outputFilter 同时作用于 V8 报告和转换后的 Istanbul 报告。默认值为 null,不启用过滤。
它与 entryFilter 和 sourceFilter 使用相同的字符串和有序对象匹配规则:按顺序返回第一个匹配项的值,未匹配的文件不保留。匹配目标是经过 sourcePath 改写后的最终路径。函数形式接收最终文件对象,而不是路径字符串。
const coverageOptions = {
outputFilter: {
'**/node_modules/**': false,
'**/src/**': true
}
};
// 也可以使用:
// outputFilter: '**/src/**'
// outputFilter: (entry) => entry.sourcePath.startsWith('src/')命令行示例(使用引号避免 shell 展开匹配规则):
mcr merge --inputDir ./raw --outputFilter "**/src/**"
mcr merge --inputDir ./raw --outputFilter "{'**/node_modules/**':false,'**/src/**':true}"outputFilter 不改变 raw 原始覆盖率导出,也不适用于 Istanbul 格式的输入数据。
Resolve sourcePath for the Source Files
当一个文件从sourcemap解包,它的路径可能是个虚拟路径, 此时可以使用sourcePath选项来修改文件路径。比如,我们测试了多个dist包的入口文件,它们的源文件可能包含了一些共同的文件,但路径可能不同,如果我们需要相同的文件覆盖率数据可以自动合并,那么需要使用sourcePath来统一这些相同文件的路径
const coverageOptions = {
sourcePath: (filePath) => {
// Remove the virtual prefix
const list = ['my-dist-file1/', 'my-dist-file2/'];
for (const str of list) {
if (filePath.startsWith(str)) {
return filePath.slice(str.length);
}
}
return filePath;
}
};内置报告路径规范化采用尽力而为的原则,无法完全兼容所有非标准、虚拟或来自其他操作系统的路径。此类特殊情况应通过 sourcePath 自行修正,而不是依赖自动推断。回调接收规范化后的 filePath;对于 V8 入口和 sourcemap 源文件,info.url 提供报告路径规范化前的原始 URL(sourcemap 存在 sourceRoot 时包含该前缀)。它修改的是报告路径,不是读取缺失源码时使用的文件系统路径。缺少 sourcesContent 时,本地源码相对于外部 map 的 URL 加载(内联 map 则相对于生成文件),并将 sourceRoot 作为目录应用。
它也支持简单key/value的替换:
const coverageOptions = {
sourcePath: {
'my-dist-file1/': '',
'my-dist-file2/': ''
}
};解决文件路径不完整的问题:
import path from 'node:path';
// MCR coverage options
const coverageOptions = {
sourcePath: (filePath, info) => {
if (!filePath.includes('/') && info.distFile) {
return `${path.dirname(info.distFile)}/${filePath}`;
}
return filePath;
}
};Adding Empty Coverage for Untested Files
默认不包含未测试文件。使用 all 可将它们以 0% 覆盖率加入报告,即使没有采集到任何覆盖率数据也可生成报告。同一原始源码的真实 V8 覆盖率优先于空覆盖率条目。
const coverageOptions = {
all: './src'
// 支持多个目录
// all: ['./src', './lib']
};未测试的文件也适用于sourceFilter过滤器. 而且也可以指定自己的filter过滤器 (可以返回文件类型来支持js或css的覆盖率格式):
const coverageOptions = {
all: {
dir: ['./src'],
filter: {
// exclude files
'**/ignored-*.js': false,
'**/*.html': false,
// empty css coverage
'**/*.scss': 'css',
'**/*': true
}
}
};可以将未测试的文件转换成 JavaScript,以便进行 AST 分析。以下示例仅处理不含 JSX 的 TypeScript,并假定已安装 @swc/core。JSX/TSX 或 Vue 应使用匹配的编译器和解析器,这不是通用转换器。保留完整文件路径,避免不同目录下的同名文件冲突;SWC 返回的 sourcemap 是 JSON 字符串,赋值前需要解析。
import { fileURLToPath } from 'node:url';
import { transform } from '@swc/core';
const coverageOptions = {
all: {
dir: ['./src'],
filter: '**/*.ts',
transformer: async (entry) => {
const { code, map } = await transform(entry.source, {
filename: fileURLToPath(entry.url),
sourceMaps: true,
isModule: true,
jsc: {
parser: {
syntax: 'typescript'
}
}
});
entry.source = code;
entry.sourceMap = JSON.parse(map);
}
}
};Hooks
onEnd — 报告生成后触发
典型用法:检测覆盖率是否达标,失败时抛错,让 API 调用方处理异常、CLI 返回非零状态;不要使用 process.exit() 提前中断清理。
const getCoverageError = (metric, summary, minimum) => {
if (!summary || !(summary.total > 0)) {
return `${metric}: missing or empty coverage`;
}
const { pct } = summary;
if (typeof pct !== 'number' || !Number.isFinite(pct) || pct < minimum) {
return `${metric}: ${pct}%, required ${minimum}%`;
}
return '';
};
const coverageOptions = {
name: 'My Coverage Report',
outputDir: './coverage-reports',
onEnd: (coverageResults) => {
if (!coverageResults) {
throw new Error('No coverage results');
}
const thresholds = {
bytes: 80,
lines: 60
};
const errors = [];
for (const [metric, minimum] of Object.entries(thresholds)) {
const error = getCoverageError(metric, coverageResults.summary?.[metric], minimum);
if (error) {
errors.push(error);
}
}
if (errors.length) {
throw new Error(errors.join('\n'));
}
}
};本示例拒绝缺失或总数为零的指标;如果某项指标允许为空,应明确调整策略。没有可用输入时,generate() 可能直接返回 undefined 而不调用 onEnd。API 调用方必须检查返回值;普通 CLI 流程应使用干净的输出目录,并验证本次生成的非空报告。参见 严格 CI 示例。
onEntry — 每个V8入口在处理前触发(仅V8)
可以在进入AST解析前改写 entry(例如对非标准语法的源码做预编译),参见 Unparsable source
const coverageOptions = {
onEntry: async (entry) => {
// entry.source, entry.sourceMap, entry.fake, ...
}
};onStart / onReady — 仅CLI可用
只在使用 mcr <command> 时有效:
onStart(coverageReport)— 子进程启动前触发onReady(coverageReport, nodeV8CoverageDir, subprocess)— 子进程退出后、MCR 读取nodeV8CoverageDir之前触发。用于子进程尚未写完覆盖数据需要等待的场景
// mcr.config.js
export default {
onReady: async (coverageReport, nodeV8CoverageDir, subprocess) => {
// 等待某个信号,或先对 raw 文件做一些预处理
}
};Ignoring Uncovered Codes
使用特定的注释,以v8 ignore 开头可以忽略未覆盖的代码:
- 忽略开始到结束
/* v8 ignore start */
function uncovered() {
}
/* v8 ignore stop */- 忽略接下来一行或者多行
/* v8 ignore next */
const os = platform === 'win32' ? 'Windows' : 'Other';
const os = platform === 'win32' ? 'Windows' /* v8 ignore next */ : 'Other';
// v8 ignore next 3
if (platform === 'linux') {
console.log('hello linux');
}- 兼容支持 c8 coverage 或 nodejs coverage 的语法格式
/* c8 ignore start */
function uncovered() {
}
/* c8 ignore stop */
/* node:coverage disable */
function uncovered() {
}
/* node:coverage enable */- 可以设置
v8Ignore: false完全禁用基于注释的忽略机制。
Multiprocessing Support
多进程支持可以很好的解决异步并行的情况。所有的覆盖率数据会保存到
[outputDir]/.cache,在报告生成之后,这些缓存数据会被清除。除非开启了调试模式,或者使用了raw报告
- 主进程,初始化,清理之前的缓存
import { MCR } from 'monocart-coverage-reports';
import coverageOptions from 'path-to/same-options.js';
const mcr = MCR(coverageOptions);
// clean previous cache before the start of testing
// unless the running environment is new and no cache
mcr.cleanCache();- 子进程1, 测试业务1
import { MCR } from 'monocart-coverage-reports';
import coverageOptions from 'path-to/same-options.js';
const mcr = MCR(coverageOptions);
await mcr.add(coverageData1);- 子进程2, 测试业务2
import { MCR } from 'monocart-coverage-reports';
import coverageOptions from 'path-to/same-options.js';
const mcr = MCR(coverageOptions);
await mcr.add(coverageData2);- 主进程,所有测试完成之后
// generate coverage reports after the completion of testing
import { MCR } from 'monocart-coverage-reports';
import coverageOptions from 'path-to/same-options.js';
const mcr = MCR(coverageOptions);
await mcr.generate();Command Line
使用
mcr命令行将使用NODE_V8_COVERAGE=dir来启动一个子进程运行程序,直到正常退出,然后自动从dir目录来读取覆盖率数据,并生成覆盖率报告
- 全局安装
npm i monocart-coverage-reports -g
mcr node ./test/specs/node.test.js -r v8,console-details --lcov- 本地项目安装
npm i monocart-coverage-reports
npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov- CLI 选项
命令使用 Execa 的默认选项执行,不主动开启 shell 模式。请分别传入可执行文件及参数,例如 mcr node "./my app.js",不要把整条命令作为一个带引号的参数传入。MCR 保留参数边界,不展开环境变量或 glob,也不解析管道、重定向、&& 等 shell 语法;组合命令请放入脚本。使用 mcr [options] -- executable ...args 可以避免子命令参数被当作 MCR 参数解析。平台相关的命令执行由 Execa 处理,包括在 Windows 下按需通过 cmd.exe 调用批处理文件并转义参数。
以下选项仅在 CLI 模式(mcr <command>)下可用:
| 选项 | 类型 | 默认值 | 说明 |
| :-- | :-- | :-- | :-- |
| -c, --config | string | — | 自定义配置文件路径 |
| --import | string | null | 通过 NODE_OPTIONS 预加载 ESM 模块,如 tsx。|
| --require | string | null | --import 的已弃用别名;使用 ESM 加载,不使用 CommonJS 解析 |
| --env | string | null | 从 dotenv 文件加载环境变量(默认 .env)。|
| test(仅配置文件) | string \| string[] \| null | null | 未指定命令行目标时,原样传给 node --test 的默认测试文件路径或 glob,仅 mcr test 使用;其他命令和 API 忽略。需要 Node.js >= 22.15.0 |
| onStart | (coverageReport) => Promise<void> | null | 子进程启动前触发;参见 Hooks |
| onReady | (coverageReport, nodeV8CoverageDir, subprocess) => Promise<void> | null | 子进程退出后、MCR 读取覆盖率前触发;参见 Hooks |
其他选项(如 --name、--reports、--outputDir、--lcov 等)参见 选项配置 表格,它们在 CLI 中的用法相同。
- 使用
--可以隔离子程序参数,以免两种参数混淆
mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js--import <module>/--require <module>: 通过NODE_OPTIONS把一个预加载模块传给子进程。常用于 TypeScript/JSX 运行时(tsx、ts-node等),也可用于加载mcr.config.ts。在所有受支持的 Node.js 版本中均可用。本地路径以当前工作目录为基准,必须明确指定文件扩展名,支持包含空格的路径。包名由原生 ESM 按 MCR 模块位置解析。两个选项都使用import(),并向 Node 传递--import;--require仅为已弃用别名,不提供 CommonJS 加载逻辑。CJS 模块仅通过 Node 内置的 import 互操作支持。
mcr --import tsx node ./test.ts--env [path]: 子进程启动前从 dotenv 文件加载环境变量(默认.env)。使用process.loadEnvFile,在所有受支持的 Node.js 版本中均可用
mcr --env .env.test node ./test.jstest:运行 Node.js 测试并生成覆盖率报告,参见使用mcr test运行测试。merge: 直接合并inputDir中的原始覆盖率数据并生成合并报告,无需启动子进程。
mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged参见 合并覆盖率报告。
Config File
MCR 3.x 中,显式指定的配置不存在,或找到的配置加载失败(包括 JSON 解析、模块语法及初始化错误)时,loadConfig() 会直接抛出异常,所有 CLI 命令也会非零退出,不再需要 strict 参数。未指定自定义配置且找不到默认配置时,仍使用已有选项继续执行。
根据以下优先级加载配置文件
- 自定义配置文件(如果没有指定则加载后面的默认配置文件):
- CLI:
mcr --config <my-config-file-path> - API:
await mcr.loadConfig("my-config-file-path")
- CLI:
mcr.config.jsmcr.config.cjsmcr.config.mjsmcr.config.json- json formatmcr.config.ts(需要预加载 TypeScript 执行模块,比如tsx):
mcr --import tsx -c mcr.config.ts node ./test.js未指定自定义路径时,MCR 会从当前工作目录向上查找默认配置文件(通过
find-up)。不在搜索路径内的文件会被忽略
Run Tests with mcr test
MCR 3.x 重要更新: 使用内置的 Node.js 测试 runner,一条命令即可运行测试并自动生成覆盖率报告。需要 Node.js >= 22.15.0。
安装 MCR 后,直接指定测试文件即可运行,无需配置文件:
npm install --save-dev monocart-coverage-reports
npx mcr test "./test/**/*.test.js" -r v8,console-summary -o ./coverage-reports此命令会运行匹配的测试,在终端打印覆盖率摘要,并将 V8 HTML 报告写入 ./coverage-reports/index.html。请为 glob 加引号,让 Node.js 而不是 shell 处理匹配。
对于需要反复执行的测试,可将测试目标和覆盖率选项保存到 mcr.config.js(ESM;需在 package.json 设置 "type": "module",或将配置命名为 mcr.config.mjs):
export default {
test: ['./test/unit/**/*.test.js', './test/integration/**/*.test.js'],
reports: ['v8', 'console-summary'],
outputDir: './coverage-reports'
};# 运行配置中的测试目标
npx mcr test
# 覆盖配置中的测试目标,并在 -- 后透传 Node.js 测试 runner 参数
npx mcr test "./test/unit/**/*.test.js" -- --test-reporter=spec
# 显式指定配置文件
npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js
# 在命令行选择报告
npx mcr test "../tests/**/*.test.js" -r v8,console-summary
# 运行多个测试文件
npx mcr test ./test/first.test.js ./test/second.test.js
# 按测试名称筛选,并指定 reporter
npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js -- --test-name-pattern="my test" --test-reporter=spec
# 将 test 命令放在初始分隔符后
npx mcr -c ./mcr.config.js -- test "../tests/**/*.test.js" -- --test-reporter=spec命令行指定的测试目标会覆盖配置中的 test,而不是追加;未指定命令行目标时才使用配置。命令行指定目标时可以不使用配置文件。原有 MCR 参数(如 -c、-r、-o、--lcov)可以放在目标之前或之后。Node runner 参数请放在 -- 后,原样透传,不作为 MCR 参数或目标覆盖项解析。建议为 glob 加引号,避免 shell 提前展开。使用另一种 mcr [options] -- test ... 形式时,所有 MCR 参数必须放在第一个 -- 前,Node runner 参数则放在第二个 -- 后。
测试目标原样传给 node --test。MCR 不检查目标类型或文件是否存在,不展开目录/glob,不解析或标准化路径,不转义特殊字符,也不去重;node --test 原生接收测试文件路径或 glob 形式的位置参数,而不是 JavaScript 配置对象。MCR 配置中的字符串表示一个这样的参数(不会按空格拆分),字符串数组表示多个按原顺序传递的参数;null 仅是 MCR 跳过测试的默认值,不会传给 Node。测试发现、匹配、参数解释及错误行为均交给 Node。相对路径以当前工作目录为基准,而不是配置文件目录。不承诺直接指定目录会递归发现测试;递归匹配请使用 "../tests/**/*.test.js" 等 glob,行为以当前 Node 版本的原生支持为准。TypeScript 执行能力取决于 Node 版本或 --import tsx 等加载器。
test 默认为 null;未指定命令行目标,且 test 未配置或为 null、空字符串、[] 时跳过测试,也不生成报告。MCR 仅判断是否提供了目标,无效或未匹配目标由 Node 自行处理。如果没有产生覆盖率数据且无法生成报告,命令仍会在报告生成阶段失败。显式指定的配置文件不存在或配置加载失败时,也会失败退出,不会静默跳过测试。测试失败仍生成覆盖率报告,并保留非零退出状态;报告生成失败同样非零退出。支持现有覆盖率配置和 CLI hooks,可按需通过过滤器排除测试文件。test 配置在其他 CLI 命令和 API 调用中均不生效,包括 merge 和 generate()。
其他 CLI 选项参见命令行。
Merge Coverage Reports
以下这些使用场景可能需要使用合并覆盖率报告:
- 多个执行环境,比如Node.js服务端,以及浏览器客户端,比如
Next.js - 多种测试类型,比如
Jest单元测试,以及Playwright的端到端自动化测试 - 分布式测试,测试结果保存到了多台机器或不同的容器中
Automatic Merging
- 默认
MCR在执行generate()时会自动合并覆盖率数据。所以可以在多进程支持下,多次添加覆盖率数据,最后将自动合并 - 比如
Next.js就可以同时添加前后端覆盖率数据,最后再执行generate()生成覆盖率报告,见例子nextjs-with-playwright - 使用
Codecov在线覆盖率报告服务,请设置输出codecov报告, 它会生成专属的codecov.json,如果有多个codecov.json文件上传,它们会自动合并数据,参见Codecov 和 合并报告说明
Manual Merging
手动合并覆盖率报告需要使用raw报告来导出原始的覆盖率数据到指定的目录
- 比如,单元测试保存到
./coverage-reports/unit/raw,见例子Jest+ jest-monocart-coverageVitest+ vitest-monocart-coverage
const coverageOptions = {
name: 'My Unit Test Coverage Report',
outputDir: './coverage-reports/unit',
reports: [
['raw', {
// relative path will be "./coverage-reports/unit/raw"
// defaults to raw
outputDir: 'raw'
}],
['v8'],
['console-details']
]
};同样的,E2E测试保存到
./coverage-reports/e2e/raw. 见例子:Playwright+ monocart-reporter with coverage APIPlaywright+MCR, see playwright-coverage- see more Integration Examples
然后创建一个
merge-coverage.js文件,使用inputDir参数导入raw数据,来生成合并的覆盖率报告.
// merge-coverage.js
import fs from 'fs';
import { CoverageReport } from 'monocart-coverage-reports';
const inputDir = [
'./coverage-reports/unit/raw',
'./coverage-reports/e2e/raw'
];
const coverageOptions = {
name: 'My Merged Coverage Report',
inputDir,
outputDir: './coverage-reports/merged',
// filter for both unit and e2e
entryFilter: {
'**/node_modules/**': false,
'**/*': true
},
sourceFilter: {
'**/node_modules/**': false,
'**/src/**': true
},
sourcePath: (filePath, info) => {
// Unify the file path for the same files
// For example, the file index.js has different paths:
// unit: unit-dist/src/index.js
// e2e: e2e-dist/src/index.js
// return filePath.replace("unit-dist/", "").replace("e2e-dist/", "")
return filePath;
},
reports: [
['v8'],
['console-details']
],
onEnd: () => {
// remove the raw files if no longer needed
// inputDir.forEach((p) => {
// fs.rmSync(p, {
// recursive: true,
// force: true
// });
// });
}
};
await new CoverageReport(coverageOptions).generate();- 最后在所有测试完成后运行
node path/to/merge-coverage.js. 所有的执行脚本大概如下:
{
"scripts": {
"test:unit": "jest",
"test:e2e": "playwright test",
"merge-coverage": "node path/to/merge-coverage.js",
"test": "npm run test:unit && npm run test:e2e && npm run merge-coverage"
}
}参见例子: merge-code-coverage
Common issues
常见问题
Unexpected coverage
覆盖率看起来不正确,多数情况是因为sourcemap转换的问题导致的. 可以先尝试设置构建工具的 minify=false 也就是不要压缩代码来解决。下面来看看sourcemap存在问题的具体原因:
const a = tf ? 'true' : 'false';
^ ^ ^
m1 p m2上面是经过构建工具编译过的代码,通过AST分析,位置p对应的原始位置是我们要找的,而从sourcemap里仅能找到离p最近的位置映射m1和m2,也就是位置p并没有精确的映射保存到sourcemap里,从而无法直接获取精确的原始位置,但我们能知道p的原始位置应该在m1和m2之间。
MCR如何解决这个问题:
- 1, 首先会尝试使用
diff-sequences工具来比较m1和m2之间的生成代码和原始代码,找到p对应的字符位置,可以解决绝大多数问题。但是如果代码是非JS格式的,比如Vue模板是HTML,或JSX这些,不管怎么比较也是很难精确找到对应位置的,甚至此时的sourcemap本身都比较乱。 - 2, 然后就是通过分析AST,找到所有的functions, statements 和 branches,因为V8覆盖率本身不提供这些指标的覆盖率. (对于分支覆盖暂不支持
AssignmentPattern类型,因为即使分析AST也无法从V8覆盖率找到它的数据)。
Unparsable source
源码无法解析问题。由上面我们知道MCR通过分析源码的AST获取更多指标的覆盖率信息,但源码如果不是标准的 ECMAScript,比如ts, jsx这些,那么分析的时候就会报错,此时我们可以手动来编译这些文件(可行但不推荐).
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import * as TsNode from 'ts-node';
const coverageOptions = {
onEntry: (entry) => {
const filePath = fileURLToPath(entry.url);
const originalSource = fs.readFileSync(filePath).toString('utf-8');
const fileName = path.basename(filePath);
const tn = TsNode.create({});
const source = tn.compile(originalSource, fileName);
entry.fake = false;
entry.source = source;
}
};JavaScript heap out of memory
内存溢出问题可能出现在有太多的原生V8覆盖率文件要处理. 我们可以使用Node.js的一个选项来增加内存使用:
- run: npm run test:coverage
env:
NODE_OPTIONS: --max-old-space-size=8192Debug for Coverage and Sourcemap
当你觉得覆盖率存在问题的时候,
MCR支持自行调试来核验覆盖率的准确性
- 首先打开调试设置
logging: 'debug'
const coverageOptions = {
logging: 'debug',
reports: [
['v8'],
['console-details']
]
};调试模式下,也就是logging为debug的时候, 原始的覆盖率数据将保留在[outputDir]/.cache缓存目录下,不会删除,如果使用了raw报告,那么位置变为[outputDir]/raw下,这样我们可以打开v8报告的html文件,通过下面新增的一些调试帮助信息来核对覆盖率

- 调试sourcemap可以直接使用Source Map Visualization (esbuild作者提供的sourcemap在线查看器)

- 生成额外的source和sourcemap文件到cache或raw文件夹
const coverageOptions = {
logging: 'debug',
sourceMap: true
};- 使用环境变量
MCR_LOG_TIME显示时间日志
process.env.MCR_LOG_TIME = true;CSS 覆盖率与 sourcemap
多个 bundle 映射到同一原始文件时,CSS 覆盖率取并集。
对于 all 无法直接解析的预处理器语法,请使用 all.transformer 编译为 CSS,设置 entry.type = 'css',并保留 sourcemap。
Sass mixin 与 @include
Sass @include 可能将一条生成规则映射到不连续的源码位置。规则无法可靠映射时,MCR 会将整个 bundle 按生成的 CSS 展示,而不是预处理器源文件;该 bundle 的覆盖率也不会合并到原始源码的覆盖率中。
未测试预处理源文件的行覆盖率
通过 all 加入的未测试文件,其字节覆盖率为 0%,包括 sourcemap 还原的原始文件。解析或映射规则之外的行(如变量、未使用的 mixin)可能显示为已覆盖;请以 bytes 覆盖率为准。
压缩后 sourcemap 可能不准确
CSS 压缩可能产生不准确的 sourcemap,导致报告回退到生成的 CSS。遇到映射不准确时,可尝试禁用 CSS 压缩。
V8 会将整个 block 视为已覆盖
Chromium 可能将整个 media query 或嵌套 CSS block 标记为已覆盖。可考虑使用 postcss-atomic-selectors 作为临时解决方案。
Integration with Any Testing Framework
通用集成方案
- 通过API接口在程序集成
- 首先,要自行收集覆盖率数据,然后,添加到报告实例
await mcr.add(coverageData) - 最后,生成覆盖率报告
await mcr.generate() - 参见 多进程支持
- 首先,要自行收集覆盖率数据,然后,添加到报告实例
- 通过CLI命令行与其他命令行集成
- 直接在其他命令行前面添加mcr的命令行即可
mcr your-cli --your-arguments - 参见 命令行
- 直接在其他命令行前面添加mcr的命令行即可
Integration Examples
Playwright
- playwright-coverage - Example for Playwright coverage reports
- playwright-bdd-coverage - Example for Playwright BDD coverage reports
- monocart-reporter - Playwright custom reporter, supports generating Code coverage report
- Coverage for component testing with
monocart-reporter: - Coverage for Next.js, both server side and client side:
- Coverage for Remix:
- see Collecting V8 Coverage Data with Playwright
c8
- c8 has integrated
MCRas an experimental feature since v10.1.0
c8 --experimental-monocart --reporter=v8 --reporter=console-details node foo.jsCodeceptJS
- CodeceptJS is a BDD + AI testing framework for e2e testing, it has integrated
MCRsince v3.5.15, see plugins/coverage
VSCode
- Monocart Coverage for VSCode - Shows native V8 code coverage in VSCode
Jest
- jest-monocart-coverage - Jest custom reporter for coverage reports
- merge-code-coverage - Example for merging code coverage (Jest unit + Playwright e2e sharding)
Vitest
- vitest-monocart-coverage - Vitest custom provider module for coverage reports
- merge-code-coverage-vitest - Example for merging code coverage (Vitest unit + Playwright e2e sharding)
Node Test Runner
- node-monocart-coverage - Custom reporter for Node test runner for coverage
Puppeteer
- jest-puppeteer-coverage - Example for Jest puppeteer coverage
- maplibre-gl-js - Example for Jest (unit) + Puppeteer (e2e) + Codecov
- see Collecting Raw V8 Coverage Data with Puppeteer
Cypress
- cypress-monocart-coverage - Cypress plugin for coverage reports
WebdriverIO
- wdio-monocart-service - WebdriverIO service for coverage reports
Storybook Test Runner
- storybook-monocart-coverage - Example for Storybook V8 coverage reports
TestCafe
- testcafe-reporter-coverage - TestCafe custom reporter for coverage reports
Selenium Webdriver
- selenium-webdriver-coverage - Example for Selenium Webdriver V8 coverage reports
Mocha
mcr mocha ./test/**/*.jsTypeScript
cross-env NODE_OPTIONS="--import tsx" npx mcr tsx ./src/example.ts
cross-env NODE_OPTIONS="--import tsx" npx mcr mocha ./test/**/*.ts
# MCR 3.x: Node.js >= 22.15.0
mcr --import tsx tsx ./src/example.ts
mcr --import tsx mocha ./test/**/*.tscross-env NODE_OPTIONS="--loader ts-node/esm --no-warnings" npx mcr ts-node ./src/example.ts
cross-env NODE_OPTIONS="--loader ts-node/esm --no-warnings" npx mcr mocha ./test/**/*.tsAVA
mcr avaCodecov
- Supports native
codecovbuilt-in report (specification)
const coverageOptions = {
outputDir: './coverage-reports',
reports: [
['codecov']
]
};- Github actions:
- name: Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage-reports/codecov.jsonCodacy
- Using
lcovreport:
const coverageOptions = {
outputDir: './coverage-reports',
lcov: true
};- Github actions:
- name: Codacy Coverage Reporter
uses: codacy/codacy-coverage-reporter-action@v1
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
coverage-reports: ./docs/mcr/lcov.infoCoveralls
- Using
lcovreport:
const coverageOptions = {
outputDir: './coverage-reports',
lcov: true
};- Github actions:
- name: Coveralls
uses: coverallsapp/github-action@v2
with:
files: ./coverage-reports/lcov.infoSonar Cloud
- Using
lcovreport. Github actions example:
- name: Analyze with SonarCloud
uses: sonarsource/sonarcloud-github-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
projectBaseDir: ./
args: >
-Dsonar.organization=cenfun
-Dsonar.projectKey=monocart-coverage-reports
-Dsonar.projectName=monocart-coverage-reports
-Dsonar.javascript.lcov.reportPaths=docs/mcr/lcov.info
-Dsonar.sources=lib
-Dsonar.tests=test
-Dsonar.exclusions=dist/*,packages/*Contributing
- 推荐使用最新 Node.js 24 LTS 开发;包的最低运行版本为 22.15.0。开发工具可能要求高于最低运行版本的补丁版本。
- VSCode (extensions: eslint/stylelint/vue)
npm install
npx playwright install --with-deps
npm run build
npm run lint
npm run test
npm run devREADME 中的 JavaScript 示例通过 npm run lint:readme 和 npm run lint 使用项目的 ESLint 配置检查。仅针对省略上下文的片段关闭 no-undef 和 no-unused-vars,语法、代码风格及其他正确性规则保持启用。TypeScript 声明使用 ts 代码块,示意图使用 text。刻意保留非标准格式的演示可继续使用 js 高亮,并在区域前后分别添加独占一行的 <!-- readme-lint-disable --> 和 <!-- readme-lint-enable --> 隐藏注释;该区域不参与 lint 和自动修复。标记必须配对且不能嵌套,代码块内部出现的标记仅视为演示文本。
npm run lint:readme
npm run lint:readme -- --fix- Refreshing
eol=lffor snapshot of test (Windows)
git add . -u
git commit -m "Saving files before refreshing line endings"
npm run eolThanks

