monocart-coverage-reports 3.0.0 → 3.2.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.
@@ -0,0 +1,115 @@
1
+ import path from 'node:path';
2
+ import { Util } from '../utils/util.js';
3
+ import { createTestResultsCollector, getRunnerOptions } from './test-results.js';
4
+
5
+ // Also recognize `mcr -c config -- test targets -- runner-options` without
6
+ // falling through to the shell's unrelated `test` command. Only MCR options
7
+ // before the first separator are parsed in that form.
8
+ const getTestCommand = (args, subArgs) => {
9
+ if (args[0] === 'test') {
10
+ return {
11
+ targets: args.slice(1), runnerArgs: subArgs.slice(1)
12
+ };
13
+ }
14
+ if (args.length || subArgs[1] !== 'test') {
15
+ return;
16
+ }
17
+ const remaining = subArgs.slice(2);
18
+ const index = remaining.indexOf('--');
19
+ if (index === -1) {
20
+ return {
21
+ targets: remaining, runnerArgs: []
22
+ };
23
+ }
24
+ return {
25
+ targets: remaining.slice(0, index), runnerArgs: remaining.slice(index + 1)
26
+ };
27
+ };
28
+
29
+ // Node owns target parsing, discovery and validation. Preserve each argument.
30
+ const getTestTargets = (test) => {
31
+ if (!test || (Array.isArray(test) && !test.length)) {
32
+ Util.logInfo('No test targets configured; skipping tests.');
33
+ return;
34
+ }
35
+ return Array.isArray(test) ? test : [test];
36
+ };
37
+
38
+ const initTestResultsCollector = (coverageOptions, runnerArgs, childArgs) => {
39
+ // Read results before coverage generation, which may remove the cache.
40
+ const testResultsCollector = createTestResultsCollector(runnerArgs, coverageOptions.cacheDir);
41
+ // Internal options must stay before Node's end-of-options marker.
42
+ childArgs.splice(1 + getRunnerOptions(runnerArgs).length, 0, ... testResultsCollector.args);
43
+ return testResultsCollector;
44
+ };
45
+
46
+ const runTestEnd = async (testResultsCollector, onTestEnd) => {
47
+ if (!testResultsCollector) {
48
+ return 0;
49
+ }
50
+ try {
51
+ const metadata = testResultsCollector.readMetadata();
52
+ const failed = !metadata.complete;
53
+ if (failed) {
54
+ Util.logError('Node test report is incomplete');
55
+ }
56
+ let testResults;
57
+ if (typeof onTestEnd === 'function') {
58
+ testResults = testResultsCollector.read();
59
+ await onTestEnd(testResults);
60
+ }
61
+ // Publish only after a successful hook, so redaction reaches raw/debug
62
+ // artifacts. Never fall back to unredacted data if the hook throws.
63
+ testResultsCollector.publish(testResults);
64
+ return process.exitCode || (failed ? 1 : 0);
65
+ } catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ Util.logError(`Node test report collection or onTestEnd failed: ${message}`);
68
+ return 1;
69
+ } finally {
70
+ // Do not export unfinished reporter output through coverage raw reports.
71
+ testResultsCollector.dispose();
72
+ }
73
+ };
74
+
75
+ const createTestRunner = (coverageOptions, runnerArgs = []) => {
76
+ const targets = getTestTargets(coverageOptions.test);
77
+ if (!targets) {
78
+ return;
79
+ }
80
+ const childArgs = ['--test', ... runnerArgs, ... targets];
81
+ let testResultsCollector;
82
+ let onTestEnd;
83
+ return {
84
+ initCoverageDir: (nodeV8CoverageDir) => {
85
+ // Tests may spawn children with a different working directory.
86
+ process.env.NODE_V8_COVERAGE = path.resolve(nodeV8CoverageDir);
87
+ },
88
+ onClose: async (generateCoverage) => {
89
+ const testExitCode = await runTestEnd(testResultsCollector, onTestEnd);
90
+ try {
91
+ const report = await generateCoverage();
92
+ if (!report) {
93
+ throw new Error('No coverage report was generated');
94
+ }
95
+ } finally {
96
+ // A later coverage hook must not mask a test hook/collection failure.
97
+ if (testExitCode) {
98
+ process.exitCode = process.exitCode || testExitCode;
99
+ }
100
+ }
101
+ },
102
+ execute: async (executeSubprocess, onClose) => {
103
+ // Always collect results; onStart may still configure the optional hook.
104
+ onTestEnd = coverageOptions.onTestEnd;
105
+ testResultsCollector = initTestResultsCollector(coverageOptions, runnerArgs, childArgs);
106
+ try {
107
+ return await executeSubprocess(process.execPath, childArgs, onClose);
108
+ } finally {
109
+ testResultsCollector?.dispose();
110
+ }
111
+ }
112
+ };
113
+ };
114
+
115
+ export { getTestCommand, createTestRunner };
@@ -31,8 +31,13 @@ const resolveAnonymousUrl = (url, index, type) => {
31
31
 
32
32
  const normalizePathDir = (sourcePath) => {
33
33
 
34
- // decodeURI("%20") to space
35
- let str = decodeURI(`${sourcePath}`);
34
+ let str = `${sourcePath}`;
35
+ try {
36
+ // decodeURI("%20") to space
37
+ str = decodeURI(str);
38
+ } catch {
39
+ // Preserve unresolvable URI text instead of aborting the report.
40
+ }
36
41
  str = str.replace(/\\/g, '/');
37
42
 
38
43
  // remove / of start, end or double, ./ ../
@@ -55,15 +60,21 @@ const betterFileURLToPath = (url) => {
55
60
  try {
56
61
  url = fileURLToPath(url);
57
62
  } catch (e) {
58
- url = decodeURIComponent(url);
59
- url = fileURLToPath(url);
63
+ try {
64
+ url = decodeURIComponent(url);
65
+ url = fileURLToPath(url);
66
+ } catch {
67
+ // in case that it cannot be safely resolved, we pass it verbatim.
68
+ return url;
69
+ }
60
70
  }
61
71
  return url;
62
72
  };
63
73
 
64
74
  const normalizeSourcePath = (sourceUrl, baseDir) => {
65
75
 
66
- // includes anonymous and everything
76
+ // Best-effort report paths, not filesystem lookup paths. Leave special cases
77
+ // to sourcePath, which can use the original URL instead of guessing here.
67
78
 
68
79
  // file url
69
80
  if (sourceUrl.startsWith('file:')) {
@@ -120,11 +131,14 @@ const normalizeSourcePath = (sourceUrl, baseDir) => {
120
131
  return normalizePathDir(relPath);
121
132
  };
122
133
 
134
+ const cssTypes = new Set(['css', 'less', 'sass', 'scss', 'styl', 'stylus', 'postcss', 'pcss', 'sss']);
135
+
123
136
  const getSourceType = (sourcePath) => {
124
137
  let ext = path.extname(sourcePath);
125
138
  let type = 'js';
126
139
  if (ext) {
127
- ext = ext.slice(1);
140
+ // extensions are case-insensitive, e.g. `.CSS` or `.SCSS`
141
+ ext = ext.slice(1).toLowerCase();
128
142
  const reg = /^[a-z0-9]+$/;
129
143
  if (reg.test(ext)) {
130
144
  type = ext;
@@ -245,6 +259,7 @@ export {
245
259
  resolveAnonymousUrl,
246
260
  normalizeSourcePath,
247
261
  getSourceType,
262
+ cssTypes,
248
263
  getSourcePathReplacer,
249
264
  initIstanbulSourcePath,
250
265
  initSourceMapSourcesPath
package/lib/utils/util.js CHANGED
@@ -226,7 +226,12 @@ const Util = {
226
226
  }
227
227
 
228
228
  // after decode url
229
- str = decodeURIComponent(str);
229
+ try {
230
+ str = decodeURIComponent(str);
231
+ } catch {
232
+ // The raw path did not match and cannot be decoded. Let later rules run.
233
+ return false;
234
+ }
230
235
 
231
236
  if (str.includes(pattern)) {
232
237
  return true;
package/migrate-to-3.md CHANGED
@@ -158,6 +158,10 @@ Custom reporter interfaces are unchanged: Istanbul reporters implement `execute(
158
158
  - `mcr test` is a new **optional** command; you do not have to replace your existing test runner.
159
159
  - `outputFilter` is a new **optional** filter, disabled by default. You do not have to replace `entryFilter` / `sourceFilter` with it.
160
160
 
161
+ ### Review test-result artifacts when upgrading to 3.2.0+
162
+
163
+ Starting in 3.2.0, `mcr test` always collects structured test results, even without `onTestEnd`. Raw exports (including ZIP) and debug caches can now include `test-results-<id>.json`, containing test names, paths and error strings that may be sensitive. Review existing automatic artifact uploads and retention policies. Use [`onTestEnd`](README.md#ontestend--mcr-test-only) to redact persisted results before publication; terminal output and temporary files already written are not redacted. Raw imports still merge coverage only, not test results. Other commands and API calls do not collect these test results.
164
+
161
165
  ## Migration checklist
162
166
 
163
167
  - [ ] Upgrade Node.js in local, CI, container and relevant child-process environments.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monocart-coverage-reports",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "A code coverage tool to generate native V8 reports or Istanbul reports.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -40,6 +40,7 @@
40
40
  "test-v8": "node ./test/test-v8.js",
41
41
  "test-puppeteer": "node ./test/test-puppeteer.js",
42
42
  "test-css": "node ./test/test-css.js",
43
+ "test-css-min": "node ./test/test-css-min.js",
43
44
  "test-minify": "node ./test/test-minify.js",
44
45
  "test-esbuild": "node ./test/test-esbuild.js",
45
46
  "test-rollup": "node ./test/test-rollup.js",
@@ -47,17 +48,17 @@
47
48
  "test-swc": "node ./test/test-swc.js",
48
49
  "test-v8-and-istanbul": "node ./test/test-v8-and-istanbul.js",
49
50
  "test-browser": "node ./test/test.js --browser",
50
- "test-cli": "npx mcr node ./test/specs/node.test.js -c test/mcr.config.cli.js",
51
+ "test-cli": "node ./lib/cli.js node ./test/specs/node.test.js -c test/mcr.config.cli.js",
51
52
  "test-demo": "node ./test/test-demo.js",
52
53
  "test-merge": "node ./test/test-merge.js",
53
54
  "test-merge-istanbul": "node ./test/test-merge-istanbul.js",
54
55
  "test-merge-v8": "node ./test/test-merge-v8.js",
55
- "test-merge-cli": "npx mcr merge -r console-details,v8 --inputDir .temp/code-coverage/raw --outputDir .temp/merge-cli",
56
+ "test-merge-cli": "node ./lib/cli.js merge -r console-details,v8 --inputDir .temp/code-coverage/raw --outputDir .temp/merge-cli",
56
57
  "test-client": "node ./test/test-client.js",
57
- "test-cp": "npx mcr node test/test-cp.mjs --outputDir .temp/test-cp",
58
+ "test-cp": "node ./lib/cli.js node test/test-cp.mjs --outputDir .temp/test-cp",
58
59
  "test-all": "node ./test/test.js",
59
60
  "test-pr": "node --inspect=9230 ./test/test-pr.js",
60
- "test": "npm run test-unit && npm run test-types && npx mcr npm run test-all -c test/mcr.config.mcr.js && node ./scripts/docs.js",
61
+ "test": "npm run test-unit && npm run test-types && node ./lib/cli.js npm run test-all -c test/mcr.config.mcr.js && node ./scripts/docs.js",
61
62
  "test:snap": "cross-env TEST_SNAPSHOT=true npm run test",
62
63
  "open": "node ./scripts/open.js",
63
64
  "eol": "git rm -rf --cached . && git reset --hard HEAD",
@@ -86,7 +87,7 @@
86
87
  "console-grid": "^2.2.4",
87
88
  "convert-source-map": "^2.0.0",
88
89
  "eight-colors": "^1.3.3",
89
- "execa": "^10.2.0",
90
+ "execa": "^10.2.1",
90
91
  "istanbul-lib-coverage": "^3.2.2",
91
92
  "istanbul-lib-report": "^3.0.1",
92
93
  "istanbul-reports": "^3.2.0",
@@ -101,12 +102,12 @@
101
102
  "@actions/github": "^9.1.1",
102
103
  "@jsdevtools/coverage-istanbul-loader": "^3.0.5",
103
104
  "@rollup/plugin-commonjs": "^29.0.3",
104
- "@rollup/plugin-typescript": "^12.3.0",
105
- "@swc/core": "^1.16.18",
105
+ "@rollup/plugin-typescript": "^12.3.1",
106
+ "@swc/core": "^1.16.20",
106
107
  "@types/node": "^26.6.5",
107
108
  "@vitejs/plugin-vue": "^6.0.10",
108
109
  "async-tick": "^1.0.2",
109
- "axios": "^1.20.0",
110
+ "axios": "^1.21.0",
110
111
  "babel-loader": "^10.1.1",
111
112
  "babel-plugin-istanbul": "^8.0.0",
112
113
  "cross-env": "^10.1.0",
@@ -116,20 +117,20 @@
116
117
  "eslint": "^10.13.0",
117
118
  "eslint-config-plus": "^2.1.0",
118
119
  "eslint-plugin-html": "^8.2.1",
119
- "eslint-plugin-vue": "^10.11.1",
120
+ "eslint-plugin-vue": "^10.12.0",
120
121
  "find-up": "^8.0.0",
121
122
  "koa": "^3.2.1",
122
- "koa-static-resolver": "^1.0.7",
123
- "minimatch": "^10.2.6",
123
+ "koa-static-resolver": "^1.1.0",
124
+ "minimatch": "^11.0.2",
124
125
  "monocart-code-viewer": "^1.2.0",
125
126
  "monocart-formatter": "^3.0.1",
126
127
  "open": "^11.0.4",
127
128
  "playwright": "^1.64.1",
128
- "postcss": "^8.5.30",
129
- "puppeteer": "^25.14.0",
130
- "rollup": "^4.64.9",
129
+ "postcss": "^8.5.32",
130
+ "puppeteer": "^25.16.0",
131
+ "rollup": "^4.65.1",
131
132
  "rollup-plugin-visualizer": "^7.1.1",
132
- "sass": "^1.105.2",
133
+ "sass": "^1.106.1",
133
134
  "stylelint": "^17.16.0",
134
135
  "stylelint-config-plus": "^1.2.0",
135
136
  "supports-color": "^11.0.0",
@@ -140,14 +141,21 @@
140
141
  "turbogrid": "^3.4.2",
141
142
  "typescript": "^6.0.3",
142
143
  "vine-ui": "^4.2.0",
143
- "vite": "^8.3.4",
144
- "vite-plugin-css-injected-by-js": "^5.0.2",
145
- "webpack": "^5.112.0"
144
+ "vite": "^8.3.5",
145
+ "vite-plugin-css-injected-by-js": "^5.0.3",
146
+ "webpack": "^5.112.2"
146
147
  },
147
148
  "engines": {
148
149
  "node": ">=22.15.0"
149
150
  },
150
- "allowScripts": {
151
- "puppeteer@25.8.0": true
151
+ "puppeteer": {
152
+ "skipDownload": true
153
+ },
154
+ "starfall": {
155
+ "outdate": {
156
+ "exclude": [
157
+ "typescript"
158
+ ]
159
+ }
152
160
  }
153
161
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: monocart-coverage-reports
3
- description: Collect, generate, merge, and debug JavaScript, TypeScript, and CSS code coverage with monocart-coverage-reports (MCR) 3.x. Use for Node.js tests and mcr test, native V8 or Istanbul coverage, Playwright/Puppeteer Chromium coverage, CLI/configuration, sourcemaps, filters, untested files, CI reports and thresholds, and shard merging. Also applies to Chinese requests about Monocart、代码覆盖率、覆盖率报告、覆盖率合并、覆盖率过滤和源码映射.
3
+ description: Collect, generate, merge, and debug JavaScript, TypeScript, and CSS code coverage with monocart-coverage-reports (MCR) 3.x. Use for Node.js tests and mcr test, native V8 or Istanbul coverage, Playwright/Puppeteer Chromium coverage, CLI/configuration, sourcemaps, filters, untested files, CI reports and thresholds, shard merging, and testing best practices. Also applies to Chinese requests about Monocart、MCR 最佳实践、代码覆盖率、覆盖率报告、覆盖率合并、覆盖率过滤和源码映射.
4
4
  ---
5
5
 
6
6
  # Coverage with Monocart Coverage Reports 3.x
@@ -23,7 +23,7 @@ Read the manifest for version, engines, exports, and types. Resolve bundled link
23
23
  ## Workflow and reference routing
24
24
 
25
25
  1. Inspect the host's scripts, runtime, MCR version, module format, and CI. Identify the desired source scope, reports, and parallel/sharded runs.
26
- 2. Choose **one collection path**. Reuse the existing runner (`mcr -c mcr.config.mjs -- npm run test:unit`); use the quick start below only when a small Node task lacks a setup. Read [collection.md](references/collection.md) for browser/CDP, manual Node capture, or Istanbul data.
26
+ 2. Choose **one collection path**. For a new Node.js test setup, prefer **`node:test` + `mcr test`**; for browser tests, prefer **Playwright Test + MCR**, or **Playwright Test + monocart-reporter** (already integrates MCR). Read [MCR best practices](references/best-practices.md) when choosing a setup or implementing browser test fixtures. Reuse an existing runner (`mcr -c mcr.config.mjs -- npm run test:unit`) rather than forcing migration. Read [collection.md](references/collection.md) for browser/CDP, manual Node capture, or Istanbul data.
27
27
  3. Confirm sources/maps match the **exact tested build**. Choose input filtering (`entryFilter` + `sourceFilter`, or `filter`) for early exclusion/performance, or `outputFilter` for simpler final-path selection at the cost of processing unwanted inputs. Do not stack strategies automatically; output filtering cannot restore discarded inputs. Read [filtering-and-sourcemaps.md](references/filtering-and-sourcemaps.md) for examples, untested files, ignores, and debugging.
28
28
  4. Read [cli.md](references/cli.md) for config, argument boundaries, Node runner options, and hooks.
29
29
  5. Read [reports-and-ci.md](references/reports-and-ci.md) for thresholds, stale-result prevention, raw exports, validated shard merging, and snapshots.
@@ -0,0 +1,218 @@
1
+ # MCR best practices: test code and get useful coverage
2
+
3
+ Use this module to choose a test setup and implement coverage collection. These recipes target **MCR 3.x and Node.js >= 22.15.0**. Reuse the host package manager, module format, test server, and installed tools. Do not upgrade dependencies or migrate an existing runner without approval.
4
+
5
+ ## 1. Choose the simplest collection path
6
+
7
+ | Target | Default recommendation | Why |
8
+ | --- | --- | --- |
9
+ | New Node.js tests | **Node.js native test runner + `mcr test`** | Built-in assertions/tests, automatic native V8 JS coverage, no instrumentation or custom collector. |
10
+ | Browser tests | **Playwright Test + MCR** | Runner-managed lifecycle and assertions; direct control over JS/CSS coverage reports. |
11
+ | Browser tests with integrated reporting | **Playwright Test + monocart-reporter** | Already integrates MCR; combines test results and coverage reporting. |
12
+ | Existing Jest/Vitest/Mocha/etc. | Keep the existing runner and its compatible collection integration | Adding coverage should not require rewriting working tests. |
13
+
14
+ MCR collects/processes coverage and generates reports; the test runner executes assertions. Prefer native V8 collection where supported. Choose **one owner** for collection and final generation, rather than collecting the same execution through multiple integrations.
15
+
16
+ ## 2. Node.js: prefer `mcr test <targets>`
17
+
18
+ Write tests with `node:test` and `node:assert/strict`. Cover meaningful outputs, errors, boundaries, and asynchronous behavior; use the real module rather than duplicating its implementation in a test. See the [complete module and test example](../SKILL.md#quick-start-test-your-code-and-get-coverage-3x).
19
+
20
+ Install only if needed, using the host package manager:
21
+
22
+ ```sh
23
+ npm install --save-dev monocart-coverage-reports@^3
24
+ npx mcr test "./test/**/*.test.mjs" -r v8,console-summary
25
+ ```
26
+
27
+ Replace the glob with actual test targets. For a repeatable project command, put `mcr test -c mcr.config.mjs` in an npm script such as `test:coverage`, with this config:
28
+
29
+ ```js
30
+ // mcr.config.mjs
31
+ export default {
32
+ test: ['./test/**/*.test.mjs'],
33
+ outputDir: './coverage-reports/node',
34
+ reports: ['v8', 'console-summary'],
35
+ outputFilter: {
36
+ '**/node_modules/**': false,
37
+ '**/src/**': true
38
+ }
39
+ };
40
+ ```
41
+
42
+ This example selects final `src/` paths. Adapt it to the host's real source layout and verify the resulting file list; do not copy a filter that excludes the code under test. See [filtering and sourcemaps](filtering-and-sourcemaps.md) for early input filtering, transpiled sources, and `all` for files never loaded by tests.
43
+
44
+ - `mcr test` invokes Node's native `--test`, automatically collects V8 JS coverage, and generates the selected reports. No manual `NODE_V8_COVERAGE`, coverage instrumentation, or API collector is needed.
45
+ - Quote target globs. Forward Node runner flags after `--`, for example `npx mcr test "./test/**/*.test.mjs" -- --test-reporter=spec`.
46
+ - Supply actual targets or config `test`; an empty target list skips testing and reporting.
47
+ - Open `coverage-reports/index.html` for the first command, or `coverage-reports/node/index.html` for the config above. Verify expected sources and use uncovered branches to choose the next tests.
48
+ - Failed tests still attempt report generation and remain failures. Do not suppress the exit status because an HTML file exists.
49
+ - Existing runners may use `mcr -c mcr.config.mjs -- npm run test:unit` for Node V8 collection where compatible; avoid duplicate coverage providers. TypeScript needs the host's supported loader/build and matching sourcemaps, not an assumption that every TS syntax runs directly.
50
+
51
+ See [CLI details](cli.md) for target overrides, configuration discovery, and argument boundaries.
52
+
53
+ ## 3. Browser: Playwright Test + MCR
54
+
55
+ Run tests with **`playwright test`**, not `mcr test`. Wrapping Playwright's Node runner with the MCR CLI does **not** collect JavaScript executing inside browser pages.
56
+
57
+ Use an automatic test-scoped fixture: start coverage before navigation, run real assertions, then stop and persist coverage before Playwright closes the page. This includes normal assertion failures. Generate once in the coordinator after every worker has finished writing.
58
+
59
+ ### Complete project example
60
+
61
+ [playwright-coverage](https://github.com/cenfun/playwright-coverage) is a minimal **Playwright Test + MCR 3.x** example using plain JavaScript ESM. It tests browser-side application code, not the Playwright test scripts or a Node.js backend. Its [quick start](https://github.com/cenfun/playwright-coverage#quick-start-for-your-own-project) explains how to adapt these five files to your application:
62
+
63
+ | File | Purpose |
64
+ | --- | --- |
65
+ | [fixtures.js](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.js) | Automatically start/stop page JS/CSS coverage and persist it with `add()`, including ordinary assertion failures. |
66
+ | [mcr.config.js](https://github.com/cenfun/playwright-coverage/blob/main/mcr.config.js) | Share output/report options and keep `cleanCache: false` for workers. |
67
+ | [global.setup.js](https://github.com/cenfun/playwright-coverage/blob/main/global.setup.js) | Clear the dedicated output directory before workers start. |
68
+ | [global-teardown.js](https://github.com/cenfun/playwright-coverage/blob/main/global-teardown.js) | Generate once after all workers finish; reject missing/empty results. |
69
+ | [playwright.config.js](https://github.com/cenfun/playwright-coverage/blob/main/playwright.config.js) | Select Chromium, register hooks, and configure the test runner. |
70
+
71
+ For an existing application, merge these settings, configure its actual `baseURL` and server lifecycle, and import the extended `test` from the fixture. Do not change the application's module system just for coverage; use `.mjs` as below when appropriate. Browser dependencies and binaries must already be installed or their installation approved.
72
+
73
+ The example's [Todo tests](https://github.com/cenfun/playwright-coverage/blob/main/tests/demo-todo-app.spec.js) serve real local HTML/JS/CSS through Playwright routes so the tests need no server or external website. They assert item creation, input clearing, whitespace handling, and text rendering. The routes and Todo assets are only an offline demo, not extra infrastructure to copy into your application.
74
+
75
+ Run `npm test` in the example project. Inspect `coverage-reports/index.html` for code coverage, `coverage-reports/lcov.info` for CI, and `playwright-report/index.html` for test results. These are different artifacts: a passing test report alone does not validate coverage. For JS-only coverage, omit CSS collection; retain sourcemaps and select your real application source scope for bundled code.
76
+
77
+ ### Minimal parallel-safe integration
78
+
79
+ This example assumes `@playwright/test` and MCR 3.x are installed, an approved Chromium binary is available, and the application server is already running. Reuse the host's server lifecycle/readiness checks (such as Playwright `webServer`); do not silently download browsers. Save these files at the host project root except the spec under `e2e/`. Merge the settings into an existing Playwright config rather than replacing its projects or hooks.
80
+
81
+ ```js
82
+ // coverage-options.mjs
83
+ export default {
84
+ outputDir: './coverage-reports/browser',
85
+ reports: ['v8', 'console-summary'],
86
+ // Workers share this cache. Only the coordinator may clean it.
87
+ cleanCache: false
88
+ };
89
+ ```
90
+
91
+ ```js
92
+ // coverage-setup.mjs
93
+ import fs from 'node:fs';
94
+ import options from './coverage-options.mjs';
95
+
96
+ export default function globalSetup() {
97
+ // Dedicated to this run: never share with another active Playwright invocation.
98
+ // Remove old reports as well as cache, so missing collection cannot leave stale artifacts.
99
+ fs.rmSync(options.outputDir, { recursive: true, force: true });
100
+ }
101
+ ```
102
+
103
+ ```js
104
+ // coverage-fixtures.mjs
105
+ import { test as base, expect } from '@playwright/test';
106
+ import { MCR } from 'monocart-coverage-reports';
107
+ import options from './coverage-options.mjs';
108
+
109
+ export const test = base.extend({
110
+ coverage: [async ({ page, browserName }, use) => {
111
+ if (browserName !== 'chromium') {
112
+ // Other browser projects still run tests, but do not provide native coverage.
113
+ await use();
114
+ return;
115
+ }
116
+ await Promise.all([
117
+ page.coverage.startJSCoverage({ resetOnNavigation: false }),
118
+ page.coverage.startCSSCoverage({ resetOnNavigation: false })
119
+ ]);
120
+ try {
121
+ await use();
122
+ } finally {
123
+ const [js, css] = await Promise.all([
124
+ page.coverage.stopJSCoverage(),
125
+ page.coverage.stopCSSCoverage()
126
+ ]);
127
+ await MCR(options).add([...js, ...css]);
128
+ }
129
+ }, { auto: true }]
130
+ });
131
+ export { expect };
132
+ ```
133
+
134
+ ```js
135
+ // coverage-teardown.mjs
136
+ import { MCR } from 'monocart-coverage-reports';
137
+ import options from './coverage-options.mjs';
138
+
139
+ export default async function globalTeardown() {
140
+ const results = await MCR(options).generate();
141
+ if (!results || !results.files.length) {
142
+ throw new Error('No browser coverage generated: check fixtures and source scope');
143
+ }
144
+ }
145
+ ```
146
+
147
+ ```js
148
+ // playwright.config.mjs
149
+ import { defineConfig } from '@playwright/test';
150
+
151
+ export default defineConfig({
152
+ testDir: './e2e',
153
+ globalSetup: './coverage-setup.mjs',
154
+ globalTeardown: './coverage-teardown.mjs',
155
+ use: {
156
+ browserName: 'chromium',
157
+ baseURL: process.env.APP_URL || 'http://localhost:3000'
158
+ }
159
+ });
160
+ ```
161
+
162
+ ```js
163
+ // e2e/counter.spec.mjs
164
+ import { test, expect } from '../coverage-fixtures.mjs';
165
+
166
+ // Assumes a counter starting at 0; replace with your application's actual behavior.
167
+ test('increments the counter when clicked', async ({ page }) => {
168
+ await page.goto('/');
169
+ const counter = page.getByTestId('counter');
170
+ await expect(counter).toHaveText('0');
171
+ await page.getByRole('button', { name: 'Increment', exact: true }).click();
172
+ await expect(counter).toHaveText('1');
173
+ });
174
+ ```
175
+
176
+ ```sh
177
+ npx playwright test
178
+ ```
179
+
180
+ All covered specs must import the extended `test` from the fixture, not directly from `@playwright/test`. Inspect `coverage-reports/browser/index.html`. The example deliberately starts without a source filter: inspect actual bundle URLs and mapped paths, then select application sources using [filtering and sourcemaps](filtering-and-sourcemaps.md). Leaving vendor/runtime code in the report is not an accurate application coverage scope.
181
+
182
+ ### Lifecycle and scope limitations
183
+
184
+ - Native `page.coverage` JS/CSS collection is **Chromium-only**. Firefox/WebKit require a separate instrumentation-based approach, such as collecting an Istanbul map; changing the reporter does not remove this limitation.
185
+ - `resetOnNavigation: false` preserves coverage across navigations in the same page. The fixture covers only its `page`; extra pages/popups, workers, and the Node.js backend require explicit collection strategies. Tests must not close the covered page before fixture teardown.
186
+ - Assertion failures normally run fixture teardown; crashes, forced termination, or timeouts may prevent collection. Retain the original failure and report collection failures rather than claiming coverage is complete.
187
+ - Only the coordinator removes the dedicated output directory before workers start. Clearing only `.cache` would leave old HTML reports available for upload if collection fails. Never point `outputDir` at source code, merge inputs, or another run's output.
188
+ - Do not call `generate()` or `cleanCache()` in each test/worker. `add()` persists data; the coordinator merges it during final generation. `cleanCache: true` in a shared worker would erase other tests' inputs.
189
+ - Give independent CI runs/shards separate output directories. Export `raw` per shard and merge only after validating every expected shard; do not let independent global teardowns race on shared cache. See [reports and CI](reports-and-ci.md).
190
+
191
+ ## 4. Browser alternative: Playwright Test + monocart-reporter
192
+
193
+ Choose this when you also want Monocart's Playwright test report, rather than maintaining a separate MCR reporting lifecycle. **monocart-reporter already integrates MCR**; do not add another MCR generator for the same data.
194
+
195
+ 1. Check the installed reporter's version, runtime requirements, bundled MCR version, and [official code coverage integration](https://github.com/cenfun/monocart-reporter#code-coverage-report). Do not assume its bundled MCR major matches a separate top-level installation or pass unsupported MCR 3.x options.
196
+ 2. Add `monocart-reporter` to the host's Playwright `reporter` configuration and configure its `coverage` options according to that release's documentation.
197
+ 3. Use an automatic Playwright fixture to start/stop page coverage around each test, as above, but submit the data through the reporter's documented coverage API with the current `testInfo`, instead of `MCR(options).add()`.
198
+ 4. Let the reporter own aggregation, cleanup, and final report generation. Do **not** also install the direct-MCR global setup/teardown above. Follow the reporter's own merge workflow for parallel jobs/shards.
199
+ 5. Run `npx playwright test` and verify both the test report and its coverage report contain current, intended data. Enabling a reporter alone does not start browser coverage collection.
200
+
201
+ Use the version-matched official fixture rather than guessing reporter API signatures. Direct MCR is preferable when you need independent coverage lifecycle/options; the reporter is preferable when you want unified test and coverage reporting. Both use the same browser coverage limitations.
202
+
203
+ ## 5. Make coverage trustworthy
204
+
205
+ - **Test behavior, not a percentage.** Add assertions for normal, boundary, error, and async paths. Use uncovered branches to find missing cases; do not weaken assertions or add unjustified ignores to satisfy a threshold.
206
+ - **Match the exact build.** Keep tested source, bundle, and sourcemaps together. For TS/bundlers, verify mapped source and line positions; do not rebuild different artifacts before generating/merging coverage.
207
+ - **Define the denominator.** Exclude only justified out-of-scope dependencies/generated assets, and use `all` when untouched application files must count as uncovered. A high percentage of loaded files alone does not prove the whole application is tested.
208
+ - **Choose useful artifacts.** Start with `v8` and `console-summary`; add `lcovonly` for compatible CI services, `v8-json` for machine validation, or `raw` for later merging. Do not merge HTML, summaries, or percentages.
209
+ - **Fail honestly.** Test, collection, report, and threshold failures must remain nonzero. `watermarks` only colors results; enforce thresholds explicitly. API callers must reject missing/empty results; CI must reject stale artifacts, not just check whether an old HTML file exists. See [strict CI and threshold recipes](reports-and-ci.md).
210
+ - **Keep artifacts safe.** Use dedicated per-run output directories and retain useful reports on failures without masking status. Reports and raw/debug data may contain source code, private paths, and URLs; publish only with approval.
211
+
212
+ ### Delivery checklist
213
+
214
+ - [ ] The chosen runner executes real assertions; collection starts before application execution/navigation.
215
+ - [ ] The report contains the intended current sources, accurate mappings, and any required untested files.
216
+ - [ ] Parallel tests do not clear one another's data; every required shard contributes to the merge.
217
+ - [ ] Reports are fresh and nonempty, and test/report/threshold failure paths fail CI.
218
+ - [ ] The user receives the run command, report location, source scope, and any collection limitations.
@@ -76,17 +76,22 @@ export default {
76
76
  - `test` is CLI-only and ignored by ordinary commands/API. Existing runners remain supported through `mcr npm run ...`.
77
77
  - Test failure still attempts report generation and retains a nonzero exit status. A report-generation failure must fail CI too. Validate both paths.
78
78
 
79
+ Starting in **3.2.0**, `mcr test` always collects structured test results, even without `onTestEnd`. Results follow the coverage cache lifecycle: normally cleaned after generation, retained in debug mode, or exported by `raw` (including ZIP). Raw imports merge coverage only, not test results. Test names, paths and error strings may contain secrets; review artifacts before uploading them. Use `onTestEnd` to redact persisted results before publication; this does not redact terminal output or temporary files already written.
80
+
79
81
  ## Hooks and shutdown
80
82
 
81
83
  | Hook | Scope / timing |
82
84
  | --- | --- |
83
85
  | `onStart(report)` | CLI child execution, before spawn |
86
+ | `onTestEnd(testResults)` | 3.2.0+, `mcr test` only: after result collection, before publication, `onReady` and coverage generation; awaited even on assertion failure |
84
87
  | `onReady(report, nodeV8CoverageDir, subprocess)` | CLI, after child exits, before reading inputs |
85
88
  | `onEntry(entry)` | V8 input processing; can mutate entry |
86
89
  | `onEnd(results)` | After report generation; suitable for thresholds |
87
90
 
88
91
  Hooks may be async. Use `onReady` for a concrete flush/readiness condition when necessary, not an arbitrary permanent delay. API integrations manage their own process lifecycle; they do not receive CLI-only hooks automatically. Preserve test/child failures even if cleanup or reporting also fails.
89
92
 
93
+ `onTestEnd` is optional and is not called for skipped runs, other commands or API calls. Mutate the result object to sanitize persisted raw/debug data. If it throws, test results are not published, the command fails, and coverage generation is still attempted. Missing/invalid reports fail without invoking the hook; incomplete reports are delivered with `complete: false` and also fail the command. See the [README hook contract](../../../README.md#ontestend--mcr-test-only) for the result schema and storage details. Save separate results outside `outputDir` if subsequent coverage cleanup could remove them.
94
+
90
95
  ## API use in CommonJS hosts
91
96
 
92
97
  MCR 3.x uses ESM, but a CommonJS host can use dynamic import without converting its entire application:
@@ -15,6 +15,8 @@ Examples use ESM (`.mjs`, or `.js` under `"type": "module"`) and the MCR 3.x run
15
15
 
16
16
  Do not assume Chromium V8 coverage works in Firefox/WebKit or that it automatically covers every browser context, page, worker, or remote backend. Explicitly collect the runtimes/targets the task requires.
17
17
 
18
+ For test suites, prefer the [Playwright Test fixture and lifecycle in MCR best practices](best-practices.md#3-browser-playwright-test--mcr), or its `monocart-reporter` alternative. The standalone script below is for custom collection outside a runner.
19
+
18
20
  ## Playwright Chromium: complete standalone script
19
21
 
20
22
  Assumes the application's test server is already running at `APP_URL` (or the local default). Prefer the host's existing server lifecycle and readiness checks.
@@ -128,6 +128,8 @@ const options = {
128
128
  };
129
129
  ```
130
130
 
131
+ **Test-result privacy (3.2.0+):** `mcr test` adds `test-results-<id>.json` to raw exports (including ZIP) and retains it in debug caches, even without `onTestEnd`. Test names, paths and error strings can contain credentials or personal data. Review artifacts before uploading, and use [`onTestEnd`](cli.md#hooks-and-shutdown) to redact persisted results before publication. This does not redact terminal output or temporary files already written. Raw imports merge coverage only; test results are not merged.
132
+
131
133
  The relative raw directory becomes `coverage-reports/shard-1/raw`. Upload/download the **entire** raw directory, including source files. After every required job finishes, merge in a separate output directory:
132
134
 
133
135
  ```js