monocart-coverage-reports 2.13.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +314 -116
- package/README.zh-Hans.md +315 -117
- package/lib/assets.js +6 -6
- package/lib/cli.js +191 -95
- package/lib/client/cdp-client.js +74 -43
- package/lib/client/coverage-client.js +24 -8
- package/lib/client/ws-session.js +115 -37
- package/lib/converter/ast-visitor.js +5 -7
- package/lib/converter/ast.js +29 -8
- package/lib/converter/collect-source-maps.js +42 -21
- package/lib/converter/converter.js +124 -129
- package/lib/converter/css-coverage.js +76 -0
- package/lib/converter/fake-source-coverage.js +64 -0
- package/lib/converter/find-original-range.js +214 -6
- package/lib/converter/flatten-source-maps.js +2 -4
- package/lib/converter/ignore.js +2 -4
- package/lib/converter/info-branch.js +5 -3
- package/lib/converter/info-function.js +5 -3
- package/lib/converter/info-statement.js +5 -3
- package/lib/converter/range-index.js +78 -0
- package/lib/converter/source-range-merge.js +103 -0
- package/lib/converter/untested.js +30 -60
- package/lib/default/options.js +13 -1
- package/lib/generate.js +44 -26
- package/lib/index.d.ts +52 -4
- package/lib/index.js +35 -41
- package/lib/istanbul/istanbul-summary.js +2 -2
- package/lib/istanbul/istanbul.js +35 -13
- package/lib/packages/monocart-coverage-assets.js +4 -3
- package/lib/packages/monocart-coverage-converter.js +27 -0
- package/lib/packages/monocart-coverage-vendor.js +2 -41
- package/lib/packages/monocart-coverage-vendor.js.LICENSE +3 -221
- package/lib/platform/concurrency.js +47 -43
- package/lib/platform/share.js +8 -1
- package/lib/register/hooks.js +19 -13
- package/lib/register/register.js +5 -3
- package/lib/reports/codacy.js +3 -5
- package/lib/reports/codecov.js +3 -5
- package/lib/reports/console-details.js +5 -5
- package/lib/reports/console-summary.js +4 -4
- package/lib/reports/custom.js +8 -24
- package/lib/reports/markdown-details.js +4 -6
- package/lib/reports/markdown-summary.js +4 -6
- package/lib/reports/raw.js +10 -12
- package/lib/reports/v8-json.js +3 -5
- package/lib/reports/v8.js +3 -5
- package/lib/utils/dedupe.js +1 -1
- package/lib/utils/gc.js +30 -14
- package/lib/utils/markdown.js +2 -2
- package/lib/utils/merge/merge.js +2 -4
- package/lib/utils/merge/range-tree.js +1 -1
- package/lib/utils/request.js +85 -73
- package/lib/utils/snapshot.js +4 -4
- package/lib/utils/source-path.js +26 -11
- package/lib/utils/util.js +67 -42
- package/lib/v8/v8-summary.js +2 -4
- package/lib/v8/v8.js +11 -11
- package/migrate-to-3.md +175 -0
- package/package.json +54 -42
- package/skills/monocart-coverage-reports/SKILL.md +106 -0
- package/skills/monocart-coverage-reports/references/best-practices.md +218 -0
- package/skills/monocart-coverage-reports/references/cli.md +108 -0
- package/skills/monocart-coverage-reports/references/collection.md +154 -0
- package/skills/monocart-coverage-reports/references/filtering-and-sourcemaps.md +170 -0
- package/skills/monocart-coverage-reports/references/reports-and-ci.md +219 -0
- package/skills/monocart-coverage-reports/references/repository-map.md +59 -0
- package/lib/index.mjs +0 -9
- package/lib/register/hooks.mjs +0 -3
- package/lib/register/register.mjs +0 -3
package/README.zh-Hans.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
> JS代码覆盖率工具,用来生成原生的[V8](https://v8.dev/blog/javascript-code-coverage)或者[Istanbul](https://istanbul.js.org/)代码覆盖率报告
|
|
14
14
|
|
|
15
15
|
* [用法](#usage)
|
|
16
|
+
* [最佳实践](#best-practices)
|
|
16
17
|
* [选项配置](#options)
|
|
17
18
|
* [所有支持的报告类型](#available-reports)
|
|
18
19
|
* [比较两种报告](#compare-reports)
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
* [多进程支持](#multiprocessing-support)
|
|
32
33
|
* [如何使用CLI命令行](#command-line)
|
|
33
34
|
* [如何加载配置文件](#config-file)
|
|
35
|
+
* [使用 `mcr test` 运行测试](#run-tests-with-mcr-test)
|
|
34
36
|
* [如何合并覆盖率报告](#merge-coverage-reports)
|
|
35
37
|
- [自动合并](#automatic-merging)
|
|
36
38
|
- [手动合并](#manual-merging)
|
|
@@ -66,18 +68,21 @@
|
|
|
66
68
|
* [感谢](#thanks)
|
|
67
69
|
|
|
68
70
|
## Usage
|
|
69
|
-
>
|
|
71
|
+
> **MCR 3.x 的所有 API 和 CLI 命令均要求 [Node.js >= 22.15.0](https://nodejs.org/)**,包括普通命令、`merge` 和 `test`。
|
|
72
|
+
|
|
73
|
+
**从 2.x 升级:** 请参阅 [3.x 迁移指南](migrate-to-3.md)。
|
|
74
|
+
|
|
70
75
|
- 安装
|
|
71
76
|
```sh
|
|
72
77
|
npm install monocart-coverage-reports
|
|
73
78
|
```
|
|
74
79
|
- API
|
|
75
80
|
```js
|
|
76
|
-
|
|
81
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
77
82
|
const mcr = MCR({
|
|
78
83
|
name: 'My Coverage Report - 2024-02-28',
|
|
79
84
|
outputDir: './coverage-reports',
|
|
80
|
-
reports: [
|
|
85
|
+
reports: ['v8', 'console-details'],
|
|
81
86
|
cleanCache: true
|
|
82
87
|
});
|
|
83
88
|
await mcr.add(coverageData);
|
|
@@ -99,6 +104,35 @@ mcr node my-app.js -r v8,console-details
|
|
|
99
104
|
```
|
|
100
105
|
参见 [命令行](#command-line)
|
|
101
106
|
|
|
107
|
+
## Best Practices
|
|
108
|
+
|
|
109
|
+
为新项目建立测试方案时,优先使用无需插桩的原生覆盖率采集:
|
|
110
|
+
|
|
111
|
+
| 测试目标 | 推荐方案 | 职责划分 |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| Node.js 端 | **Node.js 原生 test runner + `mcr test`** | `node:test` 执行测试断言;MCR 自动采集 JavaScript 原生 V8 覆盖率并生成报告。 |
|
|
114
|
+
| 浏览器端 | **Playwright Test + MCR** | Playwright 执行测试断言;自动 fixture 采集 Chromium JS/CSS 覆盖率;MCR 合并并生成报告。 |
|
|
115
|
+
| 浏览器端,需要统一测试报告 | **Playwright Test + [monocart-reporter](https://github.com/cenfun/monocart-reporter#code-coverage-report)** | reporter 已集成 MCR;配置其 coverage 选项,并由 fixture 提交覆盖率数据,统一管理测试报告和覆盖率报告。 |
|
|
116
|
+
|
|
117
|
+
详细配置步骤和示例见[最佳实践指南](skills/monocart-coverage-reports/references/best-practices.md)。
|
|
118
|
+
|
|
119
|
+
## AI Agent Skill
|
|
120
|
+
|
|
121
|
+
使用 AI 辅助开发本仓库源码时,请先阅读 [AGENTS.md](https://github.com/cenfun/monocart-coverage-reports/blob/main/AGENTS.md),了解源码结构、开发命令、验证流程和安全边界。`.github/copilot-instructions.md` 为 GitHub Copilot 提供可独立使用的核心规则摘要。这些指南仅用于源码仓库,不随 npm 包发布,与下面可复用的集成技能相互独立。
|
|
122
|
+
|
|
123
|
+
项目提供可复用的 [Monocart Coverage Reports 技能](skills/monocart-coverage-reports/SKILL.md),帮助 AI coding agent 采集覆盖率、配置 CLI、排查 sourcemap 和过滤问题、合并 raw 报告及设置 CI 阈值。结构为主 `SKILL.md` 加按需加载的专题参考文档,仅面向 MCR 3.x,并检查其运行环境要求。
|
|
124
|
+
|
|
125
|
+
npm 包包含 `skills/monocart-coverage-reports/`。请将**整个目录**(包括 `references/`)复制到 agent 支持的 skills 目录,或直接让 agent 读取 `SKILL.md`。安装 MCR 不会自动注册技能或修改 agent 配置。技能说明使用英文,但提问和回复可以使用中文或其他语言。
|
|
126
|
+
|
|
127
|
+
在使用 MCR 的项目根目录中定位已安装的技能和包配置(旧版本可能尚未包含技能):
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
node -p "require.resolve('monocart-coverage-reports/skills/monocart-coverage-reports/SKILL.md')"
|
|
131
|
+
node -p "require.resolve('monocart-coverage-reports/package.json')"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
读取定位到的技能文件,再按相对链接读取专题文档。请以已安装包的运行时、API 和版本要求为准,不要假设所有示例都适用于旧版本。以上命令用于定位文件,不是将 Markdown 作为 JavaScript 导入。
|
|
135
|
+
|
|
102
136
|
## Options
|
|
103
137
|
- 默认选项: [lib/default/options.js](./lib/default/options.js)
|
|
104
138
|
- 选项的类型描述,见 `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
|
|
@@ -115,6 +149,7 @@ mcr node my-app.js -r v8,console-details
|
|
|
115
149
|
| `dataDir` | `string` | `null` | `generate()` 阶段自动加载的覆盖率数据目录,作为 `addFromDir()` 的替代。 |
|
|
116
150
|
| `entryFilter` | `string \| object \| function` | `null` | (仅 V8)按 URL 过滤入口文件,参见 [过滤V8覆盖率数据](#filtering-results)。 |
|
|
117
151
|
| `sourceFilter` | `string \| object \| function` | `null` | (仅 V8)过滤从 sourcemap 解包出的源文件。 |
|
|
152
|
+
| `outputFilter` | `string \| object \| function` | `null` | (仅 V8)在 sourcemap 转换后,按最终 `sourcePath` 过滤文件。参见 [过滤V8覆盖率数据](#filtering-results)。 |
|
|
118
153
|
| `filter` | `string \| object \| function` | `null` | 合并 `entryFilter` 和 `sourceFilter` 的统一过滤器。 |
|
|
119
154
|
| `sourcePath` | `object \| function` | `null` | 修改源文件路径,参见 [使用 `sourcePath`](#resolve-sourcepath-for-the-source-files)。 |
|
|
120
155
|
| `all` | `string \| string[] \| object` | `null` | 为未测试文件添加空覆盖率,参见 [为未测试的文件添加空的覆盖率报告](#adding-empty-coverage-for-untested-files)。 |
|
|
@@ -127,7 +162,7 @@ mcr node my-app.js -r v8,console-details
|
|
|
127
162
|
| `watermarks` | `[number, number] \| object` | `[50, 80]` | 覆盖率高/低阈值百分比。也支持按指标配置:`{ bytes:[50,80], lines:[50,80] }`。 |
|
|
128
163
|
| `clean` | `boolean` | `true` | 生成报告前清理 `outputDir` 中的旧报告(`.cache` 目录在 `cleanCache` 也为 `true` 时才会被清理)。 |
|
|
129
164
|
| `cleanCache` | `boolean` | `false` | 启动时清理缓存目录。当 `clean` 和 `cleanCache` 都为 `false` 时,之前运行的缓存数据会保留,可在多进程间复用。 |
|
|
130
|
-
| `gc` | `number` | `null` | 内存阈值(MB),在关键阶段若
|
|
165
|
+
| `gc` | `number` | `null` | 内存阈值(MB),在关键阶段若 JavaScript 堆内存使用量(`heapUsed`)超过阈值则强制触发 GC,适用于超大数据集,参见 [JavaScript heap out of memory](#javascript-heap-out-of-memory)。 |
|
|
131
166
|
| `sourceMap` | `boolean` | `false` | 将源文件和 sourcemap 也保存到 cache 便于调试(需要 `logging: "debug"`)。 |
|
|
132
167
|
| `sourceMapResolver` | `(url, defaultResolver) => Promise<string \| object>` | `null` | 自定义 sourcemap 加载逻辑(例如从构建缓存中读取),可调用 `defaultResolver(url)` 回退到默认解析。 |
|
|
133
168
|
| `onEntry` | `(entry) => void \| Promise<void>` | `null` | (仅 V8)每个入口文件处理前触发,参见 [Hooks](#hooks)。 |
|
|
@@ -202,7 +237,7 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
|
|
|
202
237
|
|
|
203
238
|
- 自定义报告
|
|
204
239
|
```js
|
|
205
|
-
{
|
|
240
|
+
const coverageOptions = {
|
|
206
241
|
reports: [
|
|
207
242
|
[path.resolve('./test/custom-istanbul-reporter.js'), {
|
|
208
243
|
type: 'istanbul',
|
|
@@ -216,22 +251,24 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
|
|
|
216
251
|
type: 'both'
|
|
217
252
|
}]
|
|
218
253
|
]
|
|
219
|
-
}
|
|
254
|
+
};
|
|
220
255
|
```
|
|
221
256
|
- Istanbul自定义报告
|
|
222
257
|
> 例子: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
|
|
223
258
|
- V8自定义报告
|
|
224
259
|
> 例子: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
|
|
225
260
|
|
|
226
|
-
`type`
|
|
227
|
-
- `'istanbul'` —
|
|
228
|
-
- `'v8'`
|
|
229
|
-
- `'both'` —
|
|
261
|
+
`type` 选项决定报告接口及执行时机:
|
|
262
|
+
- `'istanbul'` — 使用 Istanbul 报告接口:先调用 `new Reporter(reportOptions)`,再调用 `execute(context)`。context 提供 Istanbul 报告树和源码查找能力。V8 输入会先转换为 Istanbul 覆盖率数据。
|
|
263
|
+
- `'v8'`(默认值)— 对 V8 输入,先调用 `new Reporter(reportOptions, globalOptions)`,再调用 `await generate(reportData)`。`reportData` 是 V8 覆盖率结果,包含 `type`、`summary` 和 `files`;每个文件包含 V8 覆盖范围和汇总。
|
|
264
|
+
- `'both'` — 对 V8 或 Istanbul 输入均使用上述 `generate(reportData)` 接口。请检查 `reportData.type`,按对应的结果结构处理。它**不会**同时提供两份原始格式的数据,结果中不存在 `v8Data` 或 `istanbulData` 属性。
|
|
265
|
+
|
|
266
|
+
报告统一使用原生 ESM `import()` 加载。ESM 报告使用 `export default Reporter`;通过 Node 内置互操作,也可加载 `module.exports = Reporter` 的 CommonJS 报告(在 ESM 项目中使用 `.cjs`)。本地路径必须明确指定文件扩展名,不补全扩展名、不解析目录入口,不提供独立的 CommonJS 加载器。报告加载或初始化失败会使报告生成失败,不会静默跳过。
|
|
230
267
|
|
|
231
268
|
### Multiple Reports:
|
|
232
269
|
如何配置多个报告
|
|
233
270
|
```js
|
|
234
|
-
|
|
271
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
235
272
|
const coverageOptions = {
|
|
236
273
|
outputDir: './coverage-reports',
|
|
237
274
|
reports: [
|
|
@@ -248,15 +285,15 @@ const coverageOptions = {
|
|
|
248
285
|
|
|
249
286
|
// custom reports
|
|
250
287
|
// Specify reporter name with the NPM package
|
|
251
|
-
[
|
|
252
|
-
[
|
|
253
|
-
type:
|
|
254
|
-
key:
|
|
288
|
+
['custom-reporter-1'],
|
|
289
|
+
['custom-reporter-2', {
|
|
290
|
+
type: 'istanbul',
|
|
291
|
+
key: 'value'
|
|
255
292
|
}],
|
|
256
293
|
// Specify reporter name with local path
|
|
257
294
|
['/absolute/path/to/custom-reporter.js']
|
|
258
295
|
]
|
|
259
|
-
}
|
|
296
|
+
};
|
|
260
297
|
const mcr = MCR(coverageOptions);
|
|
261
298
|
```
|
|
262
299
|
|
|
@@ -324,7 +361,7 @@ await Promise.all([
|
|
|
324
361
|
})
|
|
325
362
|
]);
|
|
326
363
|
|
|
327
|
-
await page.goto(
|
|
364
|
+
await page.goto('your page url');
|
|
328
365
|
|
|
329
366
|
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
330
367
|
page.coverage.stopJSCoverage(),
|
|
@@ -334,11 +371,18 @@ const [jsCoverage, cssCoverage] = await Promise.all([
|
|
|
334
371
|
const coverageData = [... jsCoverage, ... cssCoverage];
|
|
335
372
|
|
|
336
373
|
```
|
|
337
|
-
|
|
374
|
+
Playwright Test 自动 fixture 参见完整 [playwright-coverage 示例](https://github.com/cenfun/playwright-coverage)中的 [fixtures.js](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.js),接入方式见[最佳实践指南](skills/monocart-coverage-reports/references/best-practices.md#complete-project-example)。
|
|
338
375
|
参见例子 [./test/test-v8.js](./test/test-v8.js), [css](./test/test-css.js)
|
|
339
376
|
|
|
340
377
|
|
|
341
378
|
### Collecting Raw V8 Coverage Data with Puppeteer
|
|
379
|
+
|
|
380
|
+
> 取决于包管理器配置,Puppeteer 可能不会在安装依赖时下载浏览器。如果没有可用浏览器,请手动安装:
|
|
381
|
+
>
|
|
382
|
+
> ```sh
|
|
383
|
+
> npx puppeteer browsers install chrome
|
|
384
|
+
> ```
|
|
385
|
+
|
|
342
386
|
使用Puppeteer的覆盖接口收集覆盖率数据,注意Puppeteer默认不会提供原生V8的覆盖率数据,需要设置`includeRawScriptCoverage`
|
|
343
387
|
```js
|
|
344
388
|
await Promise.all([
|
|
@@ -353,7 +397,7 @@ await Promise.all([
|
|
|
353
397
|
})
|
|
354
398
|
]);
|
|
355
399
|
|
|
356
|
-
await page.goto(
|
|
400
|
+
await page.goto('your page url');
|
|
357
401
|
|
|
358
402
|
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
359
403
|
page.coverage.stopJSCoverage(),
|
|
@@ -406,30 +450,35 @@ const coverageData = [... jsCoverage.map((it) => {
|
|
|
406
450
|
- 如果是子进程,可参见 [命令行](#command-line)
|
|
407
451
|
|
|
408
452
|
### Collecting V8 Coverage Data with `CDPClient` API
|
|
453
|
+
|
|
454
|
+
`timeout` 控制 HTTP 调试目标发现和 WebSocket 连接的超时(默认 10000ms);`requestTimeout` 控制内置 WebSocket 会话中每条 CDP 命令的超时(默认 30000ms)。设为 `0` 可禁用相应超时。外部 Playwright/Puppeteer 会话自行管理命令超时。自定义 socket 可使用 `new MCR.WSSession(ws, { timeout: 30000 })`。
|
|
455
|
+
|
|
409
456
|
- `CDPClient`为`MCR`提供的内置接口类,用来更便捷的处理覆盖率相关数据,所有的API如下
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
457
|
+
```ts
|
|
458
|
+
interface CoverageClient {
|
|
459
|
+
// 开始和停止并收集JS的覆盖率数据
|
|
460
|
+
startJSCoverage: () => Promise<void>;
|
|
461
|
+
stopJSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
414
462
|
|
|
415
|
-
// 开始和停止并收集CSS的覆盖率数据,支持匿名文件(比如style里的css)
|
|
416
|
-
startCSSCoverage: () => Promise<void>;
|
|
417
|
-
stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
463
|
+
// 开始和停止并收集CSS的覆盖率数据,支持匿名文件(比如style里的css)
|
|
464
|
+
startCSSCoverage: () => Promise<void>;
|
|
465
|
+
stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
418
466
|
|
|
419
|
-
// 开始和停止并收集JS和CSS的覆盖率数据
|
|
420
|
-
startCoverage: () => Promise<void>;
|
|
421
|
-
stopCoverage: () => Promise<V8CoverageEntry[]>;
|
|
467
|
+
// 开始和停止并收集JS和CSS的覆盖率数据
|
|
468
|
+
startCoverage: () => Promise<void>;
|
|
469
|
+
stopCoverage: () => Promise<V8CoverageEntry[]>;
|
|
422
470
|
|
|
423
|
-
/** 如果开启了NODE_V8_COVERAGE,这个接口用来手动保存当前覆盖率数据 */
|
|
424
|
-
writeCoverage: () => Promise<string>;
|
|
471
|
+
/** 如果开启了NODE_V8_COVERAGE,这个接口用来手动保存当前覆盖率数据 */
|
|
472
|
+
writeCoverage: () => Promise<string>;
|
|
425
473
|
|
|
426
|
-
/** 收集istanbul覆盖率数据 */
|
|
427
|
-
getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
|
|
474
|
+
/** 收集istanbul覆盖率数据 */
|
|
475
|
+
getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
|
|
476
|
+
}
|
|
428
477
|
```
|
|
429
478
|
|
|
430
479
|
- 结合使用Node调试端口`--inspect=9229` 或者浏览器调试端口 `--remote-debugging-port=9229`
|
|
431
480
|
```js
|
|
432
|
-
|
|
481
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
433
482
|
const client = await MCR.CDPClient({
|
|
434
483
|
port: 9229
|
|
435
484
|
});
|
|
@@ -440,8 +489,8 @@ const coverageData = await client.stopJSCoverage();
|
|
|
440
489
|
|
|
441
490
|
- 结合使用 [Playwright CDPSession](https://playwright.dev/docs/api/class-cdpsession)
|
|
442
491
|
```js
|
|
443
|
-
|
|
444
|
-
|
|
492
|
+
import { chromium } from 'playwright';
|
|
493
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
445
494
|
const browser = await chromium.launch();
|
|
446
495
|
const page = await browser.newPage();
|
|
447
496
|
const session = await page.context().newCDPSession(page);
|
|
@@ -451,14 +500,14 @@ const client = await MCR.CDPClient({
|
|
|
451
500
|
// both js and css coverage
|
|
452
501
|
await client.startCoverage();
|
|
453
502
|
// run your test page here
|
|
454
|
-
await page.goto(
|
|
503
|
+
await page.goto('your page url');
|
|
455
504
|
const coverageData = await client.stopCoverage();
|
|
456
505
|
```
|
|
457
506
|
|
|
458
507
|
- 结合使用 [Puppeteer CDPSession](https://pptr.dev/api/puppeteer.cdpsession)
|
|
459
508
|
```js
|
|
460
|
-
|
|
461
|
-
|
|
509
|
+
import puppeteer from 'puppeteer';
|
|
510
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
462
511
|
const browser = await puppeteer.launch({});
|
|
463
512
|
const page = await browser.newPage();
|
|
464
513
|
const session = await page.target().createCDPSession();
|
|
@@ -468,20 +517,20 @@ const client = await MCR.CDPClient({
|
|
|
468
517
|
// both js and css coverage
|
|
469
518
|
await client.startCoverage();
|
|
470
519
|
// run your test page here
|
|
471
|
-
await page.goto(
|
|
520
|
+
await page.goto('your page url');
|
|
472
521
|
const coverageData = await client.stopCoverage();
|
|
473
522
|
```
|
|
474
523
|
|
|
475
524
|
- 结合使用 [Selenium Webdriver](https://www.selenium.dev/documentation/webdriver/) WebSocket (仅支持Chrome/Edge浏览器)
|
|
476
525
|
```js
|
|
477
|
-
|
|
478
|
-
|
|
526
|
+
import { Builder, Browser } from 'selenium-webdriver';
|
|
527
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
479
528
|
const driver = await new Builder().forBrowser(Browser.CHROME).build();
|
|
480
529
|
const pageCdpConnection = await driver.createCDPConnection('page');
|
|
481
530
|
const session = new MCR.WSSession(pageCdpConnection._wsConnection);
|
|
482
531
|
const client = await MCR.CDPClient({
|
|
483
532
|
session
|
|
484
|
-
})
|
|
533
|
+
});
|
|
485
534
|
```
|
|
486
535
|
|
|
487
536
|
### V8 Coverage Data API
|
|
@@ -489,7 +538,7 @@ const client = await MCR.CDPClient({
|
|
|
489
538
|
- [Playwright的覆盖率接口](https://playwright.dev/docs/api/class-coverage)
|
|
490
539
|
- [Puppeteer的覆盖率接口](https://pptr.dev/api/puppeteer.coverage)
|
|
491
540
|
- [DevTools Protocol的覆盖率接口](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) 参见 [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) 和 [v8-coverage](https://github.com/bcoe/v8-coverage)
|
|
492
|
-
```
|
|
541
|
+
```ts
|
|
493
542
|
// Coverage data for a source range.
|
|
494
543
|
export interface CoverageRange {
|
|
495
544
|
// JavaScript script source offset for the range start.
|
|
@@ -578,15 +627,15 @@ export type V8CoverageData = ScriptCoverage[];
|
|
|
578
627
|
过滤可以使用函数:
|
|
579
628
|
```js
|
|
580
629
|
const coverageOptions = {
|
|
581
|
-
entryFilter: (entry) => entry.url.indexOf(
|
|
630
|
+
entryFilter: (entry) => entry.url.indexOf('main.js') !== -1,
|
|
582
631
|
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
|
|
583
632
|
};
|
|
584
633
|
```
|
|
585
634
|
也可以使用便捷的[`minimatch`](https://github.com/isaacs/minimatch)来匹配(推荐):
|
|
586
635
|
```js
|
|
587
636
|
const coverageOptions = {
|
|
588
|
-
entryFilter:
|
|
589
|
-
sourceFilter:
|
|
637
|
+
entryFilter: '**/main.js',
|
|
638
|
+
sourceFilter: '**/src/**'
|
|
590
639
|
};
|
|
591
640
|
```
|
|
592
641
|
支持多个匹配:
|
|
@@ -611,10 +660,17 @@ mcr --sourceFilter "{'**/node_modules/**':false,'**/**':true}"
|
|
|
611
660
|
```js
|
|
612
661
|
const coverageOptions = {
|
|
613
662
|
entryFilter: (entry) => {
|
|
614
|
-
if (minimatch(entry.url, '**/node_modules/**')) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
663
|
+
if (minimatch(entry.url, '**/node_modules/**')) {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
if (minimatch(entry.url, '**/vendor.js')) {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
if (minimatch(entry.url, '**/src/**')) {
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
// else unmatched
|
|
673
|
+
return false;
|
|
618
674
|
}
|
|
619
675
|
};
|
|
620
676
|
```
|
|
@@ -633,6 +689,42 @@ const coverageOptions = {
|
|
|
633
689
|
};
|
|
634
690
|
```
|
|
635
691
|
|
|
692
|
+
### 使用 `outputFilter` 过滤最终文件
|
|
693
|
+
> **版本要求:** `outputFilter` 是 **v3.0.0** 新增的选项,v2.13.x 及更早版本不支持。
|
|
694
|
+
|
|
695
|
+
**两套过滤方案选一套即可,不要混淆。** `outputFilter` 与已有的 `entryFilter` + `sourceFilter` 都用于决定报告中保留哪些文件,功能重叠,是两种替代方案,而不是必须叠加配置的过滤器。合并选项 `filter` 只是 `entryFilter` + `sourceFilter` 的简写,不会配置 `outputFilter`。
|
|
696
|
+
|
|
697
|
+
| 方案 | 过滤时机和对象 | 优点 | 代价与注意事项 |
|
|
698
|
+
| :-- | :-- | :-- | :-- |
|
|
699
|
+
| `entryFilter` + `sourceFilter`(或 `filter`) | 从输入端过滤:在发现入口文件时,`entryFilter` 按 URL 过滤;在 sourcemap 解包源文件时,`sourceFilter` 按源路径过滤。 | 尽早排除不需要的文件,减少后续无效处理,性能更高。大型项目或 bundle 较多时,优先推荐此方案。 | 需要区分入口文件和 sourcemap 源文件,并配置两个阶段的规则。如果通过 `entryFilter` 排除整个 bundle,其中需要保留的源文件也会一起丢失。 |
|
|
700
|
+
| `outputFilter` | 从输出结果过滤:在 sourcemap 转换、合并和添加未测试文件(`all`)完成后、生成报告前,按最终 `sourcePath` 过滤。 | 配置更简单,一套规则直接面向最终文件列表,无需区分入口文件和解包后的源文件。 | 不需要的文件在被丢弃前仍会经历前期发现和处理,无法节省这些开销。当最终排除的文件较多时,可能带来明显的生成耗时和内存开销,存在性能问题。 |
|
|
701
|
+
|
|
702
|
+
**选择建议:** 优先考虑性能时,使用输入过滤方案(`entryFilter` + `sourceFilter`,或 `filter`);更看重配置简单、希望直接过滤最终结果,且可以接受额外处理成本时,选择 `outputFilter`。切换到 `outputFilter` 时,应移除同一范围的旧输入过滤配置:输入阶段已经排除的文件,无法通过输出过滤恢复。
|
|
703
|
+
|
|
704
|
+
对于 V8 输入,`outputFilter` 同时作用于 V8 报告和转换后的 Istanbul 报告。默认值为 `null`,不启用过滤。
|
|
705
|
+
|
|
706
|
+
它与 `entryFilter` 和 `sourceFilter` 使用相同的字符串和有序对象匹配规则:按顺序返回第一个匹配项的值,未匹配的文件不保留。匹配目标是经过 `sourcePath` 改写后的最终路径。函数形式接收最终文件对象,而不是路径字符串。
|
|
707
|
+
|
|
708
|
+
```js
|
|
709
|
+
const coverageOptions = {
|
|
710
|
+
outputFilter: {
|
|
711
|
+
'**/node_modules/**': false,
|
|
712
|
+
'**/src/**': true
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
// 也可以使用:
|
|
716
|
+
// outputFilter: '**/src/**'
|
|
717
|
+
// outputFilter: (entry) => entry.sourcePath.startsWith('src/')
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
命令行示例(使用引号避免 shell 展开匹配规则):
|
|
721
|
+
```sh
|
|
722
|
+
mcr merge --inputDir ./raw --outputFilter "**/src/**"
|
|
723
|
+
mcr merge --inputDir ./raw --outputFilter "{'**/node_modules/**':false,'**/src/**':true}"
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
`outputFilter` 不改变 raw 原始覆盖率导出,也不适用于 Istanbul 格式的输入数据。
|
|
727
|
+
|
|
636
728
|
## Resolve `sourcePath` for the Source Files
|
|
637
729
|
当一个文件从sourcemap解包,它的路径可能是个虚拟路径, 此时可以使用`sourcePath`选项来修改文件路径。比如,我们测试了多个dist包的入口文件,它们的源文件可能包含了一些共同的文件,但路径可能不同,如果我们需要相同的文件覆盖率数据可以自动合并,那么需要使用`sourcePath`来统一这些相同文件的路径
|
|
638
730
|
```js
|
|
@@ -649,38 +741,40 @@ const coverageOptions = {
|
|
|
649
741
|
}
|
|
650
742
|
};
|
|
651
743
|
```
|
|
744
|
+
内置报告路径规范化采用尽力而为的原则,无法完全兼容所有非标准、虚拟或来自其他操作系统的路径。此类特殊情况应通过 `sourcePath` 自行修正,而不是依赖自动推断。回调接收规范化后的 `filePath`;对于 V8 入口和 sourcemap 源文件,`info.url` 提供报告路径规范化前的原始 URL(sourcemap 存在 `sourceRoot` 时包含该前缀)。它修改的是报告路径,不是读取缺失源码时使用的文件系统路径。缺少 `sourcesContent` 时,本地源码相对于外部 map 的 URL 加载(内联 map 则相对于生成文件),并将 `sourceRoot` 作为目录应用。
|
|
745
|
+
|
|
652
746
|
它也支持简单key/value的替换:
|
|
653
747
|
```js
|
|
654
748
|
const coverageOptions = {
|
|
655
749
|
sourcePath: {
|
|
656
|
-
'my-dist-file1/': '',
|
|
750
|
+
'my-dist-file1/': '',
|
|
657
751
|
'my-dist-file2/': ''
|
|
658
752
|
}
|
|
659
753
|
};
|
|
660
754
|
```
|
|
661
755
|
解决文件路径不完整的问题:
|
|
662
756
|
```js
|
|
663
|
-
|
|
757
|
+
import path from 'node:path';
|
|
664
758
|
|
|
665
759
|
// MCR coverage options
|
|
666
760
|
const coverageOptions = {
|
|
667
|
-
sourcePath: (filePath, info)=> {
|
|
761
|
+
sourcePath: (filePath, info) => {
|
|
668
762
|
if (!filePath.includes('/') && info.distFile) {
|
|
669
763
|
return `${path.dirname(info.distFile)}/${filePath}`;
|
|
670
764
|
}
|
|
671
765
|
return filePath;
|
|
672
766
|
}
|
|
673
|
-
}
|
|
767
|
+
};
|
|
674
768
|
```
|
|
675
769
|
|
|
676
770
|
## Adding Empty Coverage for Untested Files
|
|
677
|
-
|
|
771
|
+
默认不包含未测试文件。使用 `all` 可将它们以 0% 覆盖率加入报告,即使没有采集到任何覆盖率数据也可生成报告。同一原始源码的真实 V8 覆盖率优先于空覆盖率条目。
|
|
678
772
|
```js
|
|
679
773
|
const coverageOptions = {
|
|
680
|
-
all: './src'
|
|
774
|
+
all: './src'
|
|
681
775
|
|
|
682
776
|
// 支持多个目录
|
|
683
|
-
all: ['./src', './lib']
|
|
777
|
+
// all: ['./src', './lib']
|
|
684
778
|
};
|
|
685
779
|
```
|
|
686
780
|
未测试的文件也适用于`sourceFilter`过滤器. 而且也可以指定自己的`filter`过滤器 (可以返回文件类型来支持js或css的覆盖率格式):
|
|
@@ -693,30 +787,29 @@ const coverageOptions = {
|
|
|
693
787
|
'**/ignored-*.js': false,
|
|
694
788
|
'**/*.html': false,
|
|
695
789
|
// empty css coverage
|
|
696
|
-
'**/*.scss':
|
|
790
|
+
'**/*.scss': 'css',
|
|
697
791
|
'**/*': true
|
|
698
792
|
}
|
|
699
793
|
}
|
|
700
794
|
};
|
|
701
795
|
```
|
|
702
|
-
|
|
796
|
+
可以将未测试的文件转换成 JavaScript,以便进行 AST 分析。以下示例仅处理**不含 JSX 的 TypeScript**,并假定已安装 `@swc/core`。JSX/TSX 或 Vue 应使用匹配的编译器和解析器,这不是通用转换器。保留完整文件路径,避免不同目录下的同名文件冲突;SWC 返回的 sourcemap 是 JSON 字符串,赋值前需要解析。
|
|
703
797
|
```js
|
|
704
|
-
|
|
705
|
-
|
|
798
|
+
import { fileURLToPath } from 'node:url';
|
|
799
|
+
import { transform } from '@swc/core';
|
|
706
800
|
const coverageOptions = {
|
|
707
801
|
all: {
|
|
708
802
|
dir: ['./src'],
|
|
803
|
+
filter: '**/*.ts',
|
|
709
804
|
transformer: async (entry) => {
|
|
710
|
-
const { code, map } = await
|
|
711
|
-
filename:
|
|
805
|
+
const { code, map } = await transform(entry.source, {
|
|
806
|
+
filename: fileURLToPath(entry.url),
|
|
712
807
|
sourceMaps: true,
|
|
713
808
|
isModule: true,
|
|
714
809
|
jsc: {
|
|
715
810
|
parser: {
|
|
716
|
-
syntax:
|
|
717
|
-
|
|
718
|
-
},
|
|
719
|
-
transform: {}
|
|
811
|
+
syntax: 'typescript'
|
|
812
|
+
}
|
|
720
813
|
}
|
|
721
814
|
});
|
|
722
815
|
entry.source = code;
|
|
@@ -729,36 +822,45 @@ const coverageOptions = {
|
|
|
729
822
|
## Hooks
|
|
730
823
|
|
|
731
824
|
### `onEnd` — 报告生成后触发
|
|
732
|
-
|
|
825
|
+
典型用法:检测覆盖率是否达标,失败时抛错,让 API 调用方处理异常、CLI 返回非零状态;不要使用 `process.exit()` 提前中断清理。
|
|
733
826
|
```js
|
|
734
|
-
const
|
|
827
|
+
const getCoverageError = (metric, summary, minimum) => {
|
|
828
|
+
if (!summary || !(summary.total > 0)) {
|
|
829
|
+
return `${metric}: missing or empty coverage`;
|
|
830
|
+
}
|
|
831
|
+
const { pct } = summary;
|
|
832
|
+
if (typeof pct !== 'number' || !Number.isFinite(pct) || pct < minimum) {
|
|
833
|
+
return `${metric}: ${pct}%, required ${minimum}%`;
|
|
834
|
+
}
|
|
835
|
+
return '';
|
|
836
|
+
};
|
|
735
837
|
const coverageOptions = {
|
|
736
838
|
name: 'My Coverage Report',
|
|
737
839
|
outputDir: './coverage-reports',
|
|
738
840
|
onEnd: (coverageResults) => {
|
|
841
|
+
if (!coverageResults) {
|
|
842
|
+
throw new Error('No coverage results');
|
|
843
|
+
}
|
|
739
844
|
const thresholds = {
|
|
740
845
|
bytes: 80,
|
|
741
846
|
lines: 60
|
|
742
847
|
};
|
|
743
|
-
console.log('check thresholds ...', thresholds);
|
|
744
848
|
const errors = [];
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
|
|
849
|
+
for (const [metric, minimum] of Object.entries(thresholds)) {
|
|
850
|
+
const error = getCoverageError(metric, coverageResults.summary?.[metric], minimum);
|
|
851
|
+
if (error) {
|
|
852
|
+
errors.push(error);
|
|
750
853
|
}
|
|
751
|
-
}
|
|
854
|
+
}
|
|
752
855
|
if (errors.length) {
|
|
753
|
-
|
|
754
|
-
console.log(EC.red(errMsg));
|
|
755
|
-
// throw new Error(errMsg);
|
|
756
|
-
// process.exit(1);
|
|
856
|
+
throw new Error(errors.join('\n'));
|
|
757
857
|
}
|
|
758
858
|
}
|
|
759
|
-
}
|
|
859
|
+
};
|
|
760
860
|
```
|
|
761
861
|
|
|
862
|
+
本示例拒绝缺失或总数为零的指标;如果某项指标允许为空,应明确调整策略。没有可用输入时,`generate()` 可能直接返回 `undefined` 而不调用 `onEnd`。API 调用方必须检查返回值;普通 CLI 流程应使用干净的输出目录,并验证本次生成的非空报告。参见 [严格 CI 示例](skills/monocart-coverage-reports/references/reports-and-ci.md#reject-stale-reports-and-missing-collection-in-ci)。
|
|
863
|
+
|
|
762
864
|
### `onEntry` — 每个V8入口在处理前触发(仅V8)
|
|
763
865
|
可以在进入AST解析前改写 entry(例如对非标准语法的源码做预编译),参见 [Unparsable source](#unparsable-source)
|
|
764
866
|
```js
|
|
@@ -775,7 +877,7 @@ const coverageOptions = {
|
|
|
775
877
|
- `onReady(coverageReport, nodeV8CoverageDir, subprocess)` — 子进程退出后、MCR 读取 `nodeV8CoverageDir` 之前触发。用于子进程尚未写完覆盖数据需要等待的场景
|
|
776
878
|
```js
|
|
777
879
|
// mcr.config.js
|
|
778
|
-
|
|
880
|
+
export default {
|
|
779
881
|
onReady: async (coverageReport, nodeV8CoverageDir, subprocess) => {
|
|
780
882
|
// 等待某个信号,或先对 raw 文件做一些预处理
|
|
781
883
|
}
|
|
@@ -783,6 +885,8 @@ module.exports = {
|
|
|
783
885
|
```
|
|
784
886
|
|
|
785
887
|
## Ignoring Uncovered Codes
|
|
888
|
+
<!-- readme-lint-disable -->
|
|
889
|
+
|
|
786
890
|
使用特定的注释,以`v8 ignore `开头可以忽略未覆盖的代码:
|
|
787
891
|
- 忽略开始到结束
|
|
788
892
|
```js
|
|
@@ -817,12 +921,14 @@ function uncovered() {
|
|
|
817
921
|
```
|
|
818
922
|
- 可以设置 `v8Ignore: false` 完全禁用基于注释的忽略机制。
|
|
819
923
|
|
|
924
|
+
<!-- readme-lint-enable -->
|
|
925
|
+
|
|
820
926
|
## Multiprocessing Support
|
|
821
927
|
> 多进程支持可以很好的解决异步并行的情况。所有的覆盖率数据会保存到`[outputDir]/.cache`,在报告生成之后,这些缓存数据会被清除。除非开启了[调试模式](#debug-for-coverage-and-sourcemap),或者使用了`raw`报告
|
|
822
928
|
- 主进程,初始化,清理之前的缓存
|
|
823
929
|
```js
|
|
824
|
-
|
|
825
|
-
|
|
930
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
931
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
826
932
|
const mcr = MCR(coverageOptions);
|
|
827
933
|
// clean previous cache before the start of testing
|
|
828
934
|
// unless the running environment is new and no cache
|
|
@@ -831,16 +937,16 @@ mcr.cleanCache();
|
|
|
831
937
|
|
|
832
938
|
- 子进程1, 测试业务1
|
|
833
939
|
```js
|
|
834
|
-
|
|
835
|
-
|
|
940
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
941
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
836
942
|
const mcr = MCR(coverageOptions);
|
|
837
943
|
await mcr.add(coverageData1);
|
|
838
944
|
```
|
|
839
945
|
|
|
840
946
|
- 子进程2, 测试业务2
|
|
841
947
|
```js
|
|
842
|
-
|
|
843
|
-
|
|
948
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
949
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
844
950
|
const mcr = MCR(coverageOptions);
|
|
845
951
|
await mcr.add(coverageData2);
|
|
846
952
|
```
|
|
@@ -848,8 +954,8 @@ await mcr.add(coverageData2);
|
|
|
848
954
|
- 主进程,所有测试完成之后
|
|
849
955
|
```js
|
|
850
956
|
// generate coverage reports after the completion of testing
|
|
851
|
-
|
|
852
|
-
|
|
957
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
958
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
853
959
|
const mcr = MCR(coverageOptions);
|
|
854
960
|
await mcr.generate();
|
|
855
961
|
```
|
|
@@ -871,14 +977,17 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
|
|
|
871
977
|
|
|
872
978
|
- CLI 选项
|
|
873
979
|
|
|
980
|
+
命令使用 Execa 的默认选项执行,不主动开启 shell 模式。请分别传入可执行文件及参数,例如 `mcr node "./my app.js"`,不要把整条命令作为一个带引号的参数传入。MCR 保留参数边界,不展开环境变量或 glob,也不解析管道、重定向、`&&` 等 shell 语法;组合命令请放入脚本。使用 `mcr [options] -- executable ...args` 可以避免子命令参数被当作 MCR 参数解析。平台相关的命令执行由 Execa 处理,包括在 Windows 下按需通过 `cmd.exe` 调用批处理文件并转义参数。
|
|
981
|
+
|
|
874
982
|
以下选项仅在 CLI 模式(`mcr <command>`)下可用:
|
|
875
983
|
|
|
876
984
|
| 选项 | 类型 | 默认值 | 说明 |
|
|
877
985
|
| :-- | :-- | :-- | :-- |
|
|
878
986
|
| `-c, --config` | `string` | — | 自定义配置文件路径 |
|
|
879
|
-
| `--import` | `string` | `null` | 通过 `NODE_OPTIONS` 预加载 ESM 模块,如 `tsx
|
|
880
|
-
| `--require` | `string` | `null` |
|
|
881
|
-
| `--env` | `string` | `null` | 从 dotenv 文件加载环境变量(默认 `.env
|
|
987
|
+
| `--import` | `string` | `null` | 通过 `NODE_OPTIONS` 预加载 ESM 模块,如 `tsx`。|
|
|
988
|
+
| `--require` | `string` | `null` | `--import` 的已弃用别名;使用 ESM 加载,不使用 CommonJS 解析 |
|
|
989
|
+
| `--env` | `string` | `null` | 从 dotenv 文件加载环境变量(默认 `.env`)。|
|
|
990
|
+
| `test`(仅配置文件) | `string \| string[] \| null` | `null` | 未指定命令行目标时,原样传给 `node --test` 的默认测试文件路径或 glob,**仅 `mcr test` 使用**;其他命令和 API 忽略。需要 Node.js `>= 22.15.0` |
|
|
882
991
|
| `onStart` | `(coverageReport) => Promise<void>` | `null` | 子进程启动前触发;参见 [Hooks](#hooks) |
|
|
883
992
|
| `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | 子进程退出后、MCR 读取覆盖率前触发;参见 [Hooks](#hooks) |
|
|
884
993
|
|
|
@@ -889,16 +998,18 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
|
|
|
889
998
|
mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
|
|
890
999
|
```
|
|
891
1000
|
|
|
892
|
-
- `--import <module>` / `--require <module>`: 通过 `NODE_OPTIONS` 把一个预加载模块传给子进程。常用于 TypeScript/JSX 运行时(`tsx`、`ts-node` 等),也可用于加载 `mcr.config.ts
|
|
1001
|
+
- `--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 互操作支持。
|
|
893
1002
|
```sh
|
|
894
1003
|
mcr --import tsx node ./test.ts
|
|
895
1004
|
```
|
|
896
1005
|
|
|
897
|
-
- `--env [path]`: 子进程启动前从 dotenv 文件加载环境变量(默认 `.env
|
|
1006
|
+
- `--env [path]`: 子进程启动前从 dotenv 文件加载环境变量(默认 `.env`)。使用 `process.loadEnvFile`,在所有受支持的 Node.js 版本中均可用
|
|
898
1007
|
```sh
|
|
899
1008
|
mcr --env .env.test node ./test.js
|
|
900
1009
|
```
|
|
901
1010
|
|
|
1011
|
+
- `test`:运行 Node.js 测试并生成覆盖率报告,参见[使用 `mcr test` 运行测试](#run-tests-with-mcr-test)。
|
|
1012
|
+
|
|
902
1013
|
- `merge`: 直接合并 `inputDir` 中的原始覆盖率数据并生成合并报告,无需启动子进程。
|
|
903
1014
|
```sh
|
|
904
1015
|
mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged
|
|
@@ -911,6 +1022,8 @@ mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverag
|
|
|
911
1022
|
- [AVA](#ava)
|
|
912
1023
|
|
|
913
1024
|
## Config File
|
|
1025
|
+
MCR 3.x 中,显式指定的配置不存在,或找到的配置加载失败(包括 JSON 解析、模块语法及初始化错误)时,`loadConfig()` 会直接抛出异常,所有 CLI 命令也会非零退出,不再需要 `strict` 参数。未指定自定义配置且找不到默认配置时,仍使用已有选项继续执行。
|
|
1026
|
+
|
|
914
1027
|
根据以下优先级加载配置文件
|
|
915
1028
|
- 自定义配置文件(如果没有指定则加载后面的默认配置文件):
|
|
916
1029
|
- CLI: `mcr --config <my-config-file-path>`
|
|
@@ -926,6 +1039,59 @@ mcr --import tsx -c mcr.config.ts node ./test.js
|
|
|
926
1039
|
|
|
927
1040
|
> 未指定自定义路径时,MCR 会从当前工作目录向上查找默认配置文件(通过 [`find-up`](https://github.com/sindresorhus/find-up))。不在搜索路径内的文件会被忽略
|
|
928
1041
|
|
|
1042
|
+
## Run Tests with `mcr test`
|
|
1043
|
+
|
|
1044
|
+
**MCR 3.x 重要更新:** 使用内置的 [Node.js 测试 runner](https://nodejs.org/docs/latest/api/test.html),一条命令即可运行测试并自动生成覆盖率报告。需要 **Node.js >= 22.15.0**。
|
|
1045
|
+
|
|
1046
|
+
安装 MCR 后,直接指定测试文件即可运行,无需配置文件:
|
|
1047
|
+
|
|
1048
|
+
```sh
|
|
1049
|
+
npm install --save-dev monocart-coverage-reports
|
|
1050
|
+
npx mcr test "./test/**/*.test.js" -r v8,console-summary -o ./coverage-reports
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
此命令会运行匹配的测试,在终端打印覆盖率摘要,并将 V8 HTML 报告写入 `./coverage-reports/index.html`。请为 glob 加引号,让 Node.js 而不是 shell 处理匹配。
|
|
1054
|
+
|
|
1055
|
+
对于需要反复执行的测试,可将测试目标和覆盖率选项保存到 `mcr.config.js`(ESM;需在 `package.json` 设置 `"type": "module"`,或将配置命名为 `mcr.config.mjs`):
|
|
1056
|
+
|
|
1057
|
+
```js
|
|
1058
|
+
export default {
|
|
1059
|
+
test: ['./test/unit/**/*.test.js', './test/integration/**/*.test.js'],
|
|
1060
|
+
reports: ['v8', 'console-summary'],
|
|
1061
|
+
outputDir: './coverage-reports'
|
|
1062
|
+
};
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
```sh
|
|
1066
|
+
# 运行配置中的测试目标
|
|
1067
|
+
npx mcr test
|
|
1068
|
+
# 覆盖配置中的测试目标,并在 -- 后透传 Node.js 测试 runner 参数
|
|
1069
|
+
npx mcr test "./test/unit/**/*.test.js" -- --test-reporter=spec
|
|
1070
|
+
|
|
1071
|
+
# 显式指定配置文件
|
|
1072
|
+
npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js
|
|
1073
|
+
|
|
1074
|
+
# 在命令行选择报告
|
|
1075
|
+
npx mcr test "../tests/**/*.test.js" -r v8,console-summary
|
|
1076
|
+
|
|
1077
|
+
# 运行多个测试文件
|
|
1078
|
+
npx mcr test ./test/first.test.js ./test/second.test.js
|
|
1079
|
+
|
|
1080
|
+
# 按测试名称筛选,并指定 reporter
|
|
1081
|
+
npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js -- --test-name-pattern="my test" --test-reporter=spec
|
|
1082
|
+
|
|
1083
|
+
# 将 test 命令放在初始分隔符后
|
|
1084
|
+
npx mcr -c ./mcr.config.js -- test "../tests/**/*.test.js" -- --test-reporter=spec
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
命令行指定的测试目标会**覆盖**配置中的 `test`,而不是追加;未指定命令行目标时才使用配置。命令行指定目标时可以不使用配置文件。原有 MCR 参数(如 `-c`、`-r`、`-o`、`--lcov`)可以放在目标之前或之后。Node runner 参数请放在 `--` 后,原样透传,不作为 MCR 参数或目标覆盖项解析。建议为 glob 加引号,避免 shell 提前展开。使用另一种 `mcr [options] -- test ...` 形式时,所有 MCR 参数必须放在第一个 `--` 前,Node runner 参数则放在第二个 `--` 后。
|
|
1088
|
+
|
|
1089
|
+
测试目标原样传给 `node --test`。MCR 不检查目标类型或文件是否存在,不展开目录/glob,不解析或标准化路径,不转义特殊字符,也不去重;`node --test` 原生接收测试文件路径或 glob 形式的位置参数,而不是 JavaScript 配置对象。MCR 配置中的字符串表示一个这样的参数(不会按空格拆分),字符串数组表示多个按原顺序传递的参数;`null` 仅是 MCR 跳过测试的默认值,不会传给 Node。测试发现、匹配、参数解释及错误行为均交给 Node。相对路径以当前工作目录为基准,而不是配置文件目录。不承诺直接指定目录会递归发现测试;递归匹配请使用 `"../tests/**/*.test.js"` 等 glob,行为以当前 Node 版本的原生支持为准。TypeScript 执行能力取决于 Node 版本或 `--import tsx` 等加载器。
|
|
1090
|
+
|
|
1091
|
+
`test` 默认为 `null`;未指定命令行目标,且 `test` 未配置或为 `null`、空字符串、`[]` 时跳过测试,也不生成报告。MCR 仅判断是否提供了目标,无效或未匹配目标由 Node 自行处理。如果没有产生覆盖率数据且无法生成报告,命令仍会在报告生成阶段失败。显式指定的配置文件不存在或配置加载失败时,也会失败退出,不会静默跳过测试。测试失败仍生成覆盖率报告,并保留非零退出状态;报告生成失败同样非零退出。支持现有覆盖率配置和 CLI hooks,可按需通过过滤器排除测试文件。**`test` 配置在其他 CLI 命令和 API 调用中均不生效**,包括 `merge` 和 `generate()`。
|
|
1092
|
+
|
|
1093
|
+
其他 CLI 选项参见[命令行](#command-line)。
|
|
1094
|
+
|
|
929
1095
|
## Merge Coverage Reports
|
|
930
1096
|
以下这些使用场景可能需要使用合并覆盖率报告:
|
|
931
1097
|
- 多个执行环境,比如Node.js服务端,以及浏览器客户端,比如`Next.js`
|
|
@@ -945,12 +1111,12 @@ mcr --import tsx -c mcr.config.ts node ./test.js
|
|
|
945
1111
|
```js
|
|
946
1112
|
const coverageOptions = {
|
|
947
1113
|
name: 'My Unit Test Coverage Report',
|
|
948
|
-
outputDir:
|
|
1114
|
+
outputDir: './coverage-reports/unit',
|
|
949
1115
|
reports: [
|
|
950
1116
|
['raw', {
|
|
951
1117
|
// relative path will be "./coverage-reports/unit/raw"
|
|
952
1118
|
// defaults to raw
|
|
953
|
-
outputDir:
|
|
1119
|
+
outputDir: 'raw'
|
|
954
1120
|
}],
|
|
955
1121
|
['v8'],
|
|
956
1122
|
['console-details']
|
|
@@ -966,8 +1132,8 @@ const coverageOptions = {
|
|
|
966
1132
|
- 然后创建一个`merge-coverage.js`文件,使用`inputDir`参数导入`raw`数据,来生成合并的覆盖率报告.
|
|
967
1133
|
```js
|
|
968
1134
|
// merge-coverage.js
|
|
969
|
-
|
|
970
|
-
|
|
1135
|
+
import fs from 'fs';
|
|
1136
|
+
import { CoverageReport } from 'monocart-coverage-reports';
|
|
971
1137
|
const inputDir = [
|
|
972
1138
|
'./coverage-reports/unit/raw',
|
|
973
1139
|
'./coverage-reports/e2e/raw'
|
|
@@ -986,7 +1152,7 @@ const coverageOptions = {
|
|
|
986
1152
|
'**/node_modules/**': false,
|
|
987
1153
|
'**/src/**': true
|
|
988
1154
|
},
|
|
989
|
-
|
|
1155
|
+
|
|
990
1156
|
sourcePath: (filePath, info) => {
|
|
991
1157
|
// Unify the file path for the same files
|
|
992
1158
|
// For example, the file index.js has different paths:
|
|
@@ -1000,7 +1166,7 @@ const coverageOptions = {
|
|
|
1000
1166
|
['v8'],
|
|
1001
1167
|
['console-details']
|
|
1002
1168
|
],
|
|
1003
|
-
|
|
1169
|
+
|
|
1004
1170
|
onEnd: () => {
|
|
1005
1171
|
// remove the raw files if no longer needed
|
|
1006
1172
|
// inputDir.forEach((p) => {
|
|
@@ -1030,7 +1196,7 @@ await new CoverageReport(coverageOptions).generate();
|
|
|
1030
1196
|
> 常见问题
|
|
1031
1197
|
### Unexpected coverage
|
|
1032
1198
|
覆盖率看起来不正确,多数情况是因为sourcemap转换的问题导致的. 可以先尝试设置构建工具的 `minify=false` 也就是不要压缩代码来解决。下面来看看sourcemap存在问题的具体原因:
|
|
1033
|
-
```
|
|
1199
|
+
```text
|
|
1034
1200
|
const a = tf ? 'true' : 'false';
|
|
1035
1201
|
^ ^ ^
|
|
1036
1202
|
m1 p m2
|
|
@@ -1046,21 +1212,21 @@ const a = tf ? 'true' : 'false';
|
|
|
1046
1212
|
### Unparsable source
|
|
1047
1213
|
源码无法解析问题。由上面我们知道`MCR`通过分析源码的AST获取更多指标的覆盖率信息,但源码如果不是标准的 ECMAScript,比如`ts`, `jsx`这些,那么分析的时候就会报错,此时我们可以手动来编译这些文件(可行但不推荐).
|
|
1048
1214
|
```js
|
|
1049
|
-
import * as fs from
|
|
1050
|
-
import * as path from
|
|
1051
|
-
import { fileURLToPath } from
|
|
1215
|
+
import * as fs from 'fs';
|
|
1216
|
+
import * as path from 'path';
|
|
1217
|
+
import { fileURLToPath } from 'url';
|
|
1052
1218
|
import * as TsNode from 'ts-node';
|
|
1053
1219
|
const coverageOptions = {
|
|
1054
|
-
onEntry:
|
|
1055
|
-
const filePath = fileURLToPath(entry.url)
|
|
1056
|
-
const originalSource = fs.readFileSync(filePath).toString(
|
|
1220
|
+
onEntry: (entry) => {
|
|
1221
|
+
const filePath = fileURLToPath(entry.url);
|
|
1222
|
+
const originalSource = fs.readFileSync(filePath).toString('utf-8');
|
|
1057
1223
|
const fileName = path.basename(filePath);
|
|
1058
1224
|
const tn = TsNode.create({});
|
|
1059
1225
|
const source = tn.compile(originalSource, fileName);
|
|
1060
1226
|
entry.fake = false;
|
|
1061
1227
|
entry.source = source;
|
|
1062
1228
|
}
|
|
1063
|
-
}
|
|
1229
|
+
};
|
|
1064
1230
|
```
|
|
1065
1231
|
|
|
1066
1232
|
### JavaScript heap out of memory
|
|
@@ -1101,9 +1267,30 @@ const coverageOptions = {
|
|
|
1101
1267
|
|
|
1102
1268
|
- 使用环境变量`MCR_LOG_TIME`显示时间日志
|
|
1103
1269
|
```js
|
|
1104
|
-
process.env.MCR_LOG_TIME = true
|
|
1270
|
+
process.env.MCR_LOG_TIME = true;
|
|
1105
1271
|
```
|
|
1106
1272
|
|
|
1273
|
+
### CSS 覆盖率与 sourcemap
|
|
1274
|
+
|
|
1275
|
+
多个 bundle 映射到同一原始文件时,CSS 覆盖率取并集。
|
|
1276
|
+
|
|
1277
|
+
对于 `all` 无法直接解析的预处理器语法,请使用 `all.transformer` 编译为 CSS,设置 `entry.type = 'css'`,并保留 sourcemap。
|
|
1278
|
+
|
|
1279
|
+
#### Sass mixin 与 `@include`
|
|
1280
|
+
|
|
1281
|
+
Sass `@include` 可能将一条生成规则映射到不连续的源码位置。规则无法可靠映射时,MCR 会将**整个 bundle 按生成的 CSS 展示**,而不是预处理器源文件;该 bundle 的覆盖率也不会合并到原始源码的覆盖率中。
|
|
1282
|
+
|
|
1283
|
+
#### 未测试预处理源文件的行覆盖率
|
|
1284
|
+
|
|
1285
|
+
通过 `all` 加入的未测试文件,其字节覆盖率为 `0%`,包括 sourcemap 还原的原始文件。解析或映射规则之外的行(如变量、未使用的 mixin)可能显示为已覆盖;请以 **bytes** 覆盖率为准。
|
|
1286
|
+
|
|
1287
|
+
#### 压缩后 sourcemap 可能不准确
|
|
1288
|
+
CSS 压缩可能产生不准确的 sourcemap,导致报告回退到生成的 CSS。遇到映射不准确时,可尝试**禁用 CSS 压缩**。
|
|
1289
|
+
|
|
1290
|
+
#### V8 会将整个 block 视为已覆盖
|
|
1291
|
+
|
|
1292
|
+
Chromium 可能将整个 media query 或嵌套 CSS block 标记为已覆盖。可考虑使用 [postcss-atomic-selectors](https://www.npmjs.com/package/postcss-atomic-selectors) 作为临时解决方案。
|
|
1293
|
+
|
|
1107
1294
|
## Integration with Any Testing Framework
|
|
1108
1295
|
通用集成方案
|
|
1109
1296
|
- 通过API接口在程序集成
|
|
@@ -1185,7 +1372,7 @@ mcr mocha ./test/**/*.js
|
|
|
1185
1372
|
```sh
|
|
1186
1373
|
cross-env NODE_OPTIONS="--import tsx" npx mcr tsx ./src/example.ts
|
|
1187
1374
|
cross-env NODE_OPTIONS="--import tsx" npx mcr mocha ./test/**/*.ts
|
|
1188
|
-
# Node.js
|
|
1375
|
+
# MCR 3.x: Node.js >= 22.15.0
|
|
1189
1376
|
mcr --import tsx tsx ./src/example.ts
|
|
1190
1377
|
mcr --import tsx mocha ./test/**/*.ts
|
|
1191
1378
|
```
|
|
@@ -1206,7 +1393,7 @@ mcr ava
|
|
|
1206
1393
|
- Supports native `codecov` built-in report ([specification](https://docs.codecov.com/docs/codecov-custom-coverage-format))
|
|
1207
1394
|
```js
|
|
1208
1395
|
const coverageOptions = {
|
|
1209
|
-
outputDir:
|
|
1396
|
+
outputDir: './coverage-reports',
|
|
1210
1397
|
reports: [
|
|
1211
1398
|
['codecov']
|
|
1212
1399
|
]
|
|
@@ -1226,7 +1413,7 @@ const coverageOptions = {
|
|
|
1226
1413
|
- Using `lcov` report:
|
|
1227
1414
|
```js
|
|
1228
1415
|
const coverageOptions = {
|
|
1229
|
-
outputDir:
|
|
1416
|
+
outputDir: './coverage-reports',
|
|
1230
1417
|
lcov: true
|
|
1231
1418
|
};
|
|
1232
1419
|
```
|
|
@@ -1244,7 +1431,7 @@ const coverageOptions = {
|
|
|
1244
1431
|
- Using `lcov` report:
|
|
1245
1432
|
```js
|
|
1246
1433
|
const coverageOptions = {
|
|
1247
|
-
outputDir:
|
|
1434
|
+
outputDir: './coverage-reports',
|
|
1248
1435
|
lcov: true
|
|
1249
1436
|
};
|
|
1250
1437
|
```
|
|
@@ -1277,17 +1464,25 @@ const coverageOptions = {
|
|
|
1277
1464
|
```
|
|
1278
1465
|
|
|
1279
1466
|
## Contributing
|
|
1280
|
-
- Node.js 22
|
|
1467
|
+
- 推荐使用最新 Node.js 24 LTS 开发;包的最低运行版本为 22.15.0。开发工具可能要求高于最低运行版本的补丁版本。
|
|
1281
1468
|
- VSCode (extensions: eslint/stylelint/vue)
|
|
1282
1469
|
```sh
|
|
1283
1470
|
npm install
|
|
1284
1471
|
npx playwright install --with-deps
|
|
1285
1472
|
|
|
1286
1473
|
npm run build
|
|
1474
|
+
npm run lint
|
|
1287
1475
|
npm run test
|
|
1288
1476
|
|
|
1289
1477
|
npm run dev
|
|
1290
1478
|
```
|
|
1479
|
+
README 中的 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 和自动修复。标记必须配对且不能嵌套,代码块内部出现的标记仅视为演示文本。
|
|
1480
|
+
|
|
1481
|
+
```sh
|
|
1482
|
+
npm run lint:readme
|
|
1483
|
+
npm run lint:readme -- --fix
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1291
1486
|
- Refreshing `eol=lf` for snapshot of test (Windows)
|
|
1292
1487
|
```sh
|
|
1293
1488
|
git add . -u
|
|
@@ -1298,4 +1493,7 @@ npm run eol
|
|
|
1298
1493
|
|
|
1299
1494
|
## Thanks
|
|
1300
1495
|
- [@bcoe](https://github.com/bcoe)
|
|
1301
|
-
- [@edumserrano](https://github.com/edumserrano)
|
|
1496
|
+
- [@edumserrano](https://github.com/edumserrano)
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+

|