Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

monocart-coverage-reports

Package Overview
Dependencies
Maintainers
1
Versions
89
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

monocart-coverage-reports - npm Package Compare versions

Comparing version 2.7.5 to 2.7.6

lib/client/ws-session.js

38

lib/cli.js
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');

@@ -136,7 +135,2 @@ const EC = require('eight-colors');

if (!fs.existsSync(nodeV8CoverageDir)) {
EC.logRed(`Invalid V8 coverage dir: ${Util.relativePath(nodeV8CoverageDir)}`);
return 1;
}
await coverageReport.addFromDir(nodeV8CoverageDir);

@@ -159,2 +153,17 @@ await coverageReport.generate();

// the -- separator
const argv = [];
const subArgv = [];
let separator = false;
process.argv.forEach((it) => {
if (!separator && it === '--') {
separator = true;
}
if (separator) {
subArgv.push(it);
} else {
argv.push(it);
}
});
program

@@ -164,6 +173,6 @@ .name('mcr')

.version(version)
.argument('<command>', 'command to execute')
.argument('[command]', 'command to execute')
.allowUnknownOption()
.option('-c, --config <path>', 'custom config file path')
.option('--logging <logging>', 'off, error, info, debug')
.option('-l, --logging <logging>', 'off, error, info, debug')

@@ -189,6 +198,15 @@ .option('-n, --name <name>', 'report name for title')

.action((_command, cliOptions) => {
const command = program.args.join(' ');
const args = [].concat(program.args).concat(subArgv);
if (args[0] === '--') {
args.shift();
}
const command = args.join(' ').trim();
if (!command) {
program.outputHelp();
return;
}
executeCommand(command, cliOptions);
});
program.parse();
program.parse(argv);

@@ -7,3 +7,3 @@

const CDPSession = require('./cdp-session.js');
const WSSession = require('./ws-session.js');
const CoverageClient = require('./coverage-client.js');

@@ -89,3 +89,3 @@

Util.logDebug(`${EC.green('Connected')} ${url}`);
const session = new CDPSession(ws);
const session = new WSSession(ws);
resolve([null, session]);

@@ -92,0 +92,0 @@ });

@@ -6,6 +6,8 @@ /**

*/
const EC = require('eight-colors');
const { Locator } = require('monocart-formatter');
const Util = require('../utils/util.js');
// position mapping for conversion between offset and line/column
const { Locator } = require('monocart-formatter');
const findOriginalRange = require('./find-original-range.js');

@@ -1463,3 +1465,3 @@ const { getJsAstInfo, getCssAstInfo } = require('./ast.js');

}
Util.logTime(`- parsed ast: ${sourcePath}`, time_start_ast);
Util.logTime(`- parsed ast: ${sourcePath} (${EC.cyan(Util.BSF(maxContentLength))})`, time_start_ast);

@@ -1481,2 +1483,3 @@ // console.log(sourcePath, astInfo.statements.length);

decodedMappings: [],
cacheMap: new Map(),
// coverage info

@@ -1497,3 +1500,4 @@ bytes: [],

unpackDistFile(item, state, options);
Util.logTime(`- unpacked dist: ${sourcePath}`, time_start_unpack);
const unpackedFiles = EC.cyan(`${state.originalList.length} files`);
Util.logTime(`- unpacked dist: ${sourcePath} (${unpackedFiles})`, time_start_unpack);

@@ -1506,3 +1510,4 @@ stateList.push(state);

const dataList = generateV8DataList(stateList, options);
Util.logTime('- generated v8 data list', time_start_convert);
const dataFiles = EC.cyan(`${dataList.v8DataList.length} files`);
Util.logTime(`- generated data list (${dataFiles})`, time_start_convert);

@@ -1509,0 +1514,0 @@ return dataList;

@@ -823,6 +823,11 @@ const { diff } = require('../packages/monocart-coverage-vendor');

const { sourcePath } = state;
const { sourcePath, cacheMap } = state;
const key = `${start}_${end}`;
if (cacheMap.has(key)) {
return cacheMap.get(key);
}
const createMappingError = (errors) => {
return {
const res = {
error: true,

@@ -834,2 +839,7 @@ start,

};
// cache error response
cacheMap.set(key, res);
return res;
};

@@ -857,3 +867,9 @@

return {
const inComment = originalState.locator.lineParser.commentParser.isComment(originalStart, originalEnd);
if (inComment) {
return createMappingError(['the range in a original comment']);
}
const res = {
start: originalStart,

@@ -864,4 +880,9 @@ end: originalEnd,

// cache response
cacheMap.set(key, res);
return res;
};
module.exports = findOriginalRange;

@@ -24,3 +24,3 @@ const fs = require('fs');

const generateV8ListReports = async (v8list, coverageData, fileSources, options) => {
const time_start = Date.now();
let istanbulReportPath;

@@ -36,3 +36,3 @@ // v8 to istanbul reports

const v8CoverageResults = await saveV8Report(v8list, options, istanbulReportPath);
Util.logTime('- saved coverage reports', time_start);
return v8CoverageResults;

@@ -441,5 +441,2 @@ };

// console.log('after merge', v8list.map((it) => it.url));
// only debug level
const time_start = Date.now();
const results = convertV8List(v8list, options);

@@ -449,4 +446,2 @@ const {

} = results;
Util.logTime(`converted v8 list: ${v8DataList.length}`, time_start);
return generateV8ListReports(v8DataList, coverageData, fileSources, options);

@@ -813,3 +808,3 @@ }

if (!dir || !fs.existsSync(dir)) {
Util.logError(`Invalid V8 coverage dir: ${dir}`);
Util.logInfo(`Not found V8 coverage dir: ${dir}`);
return;

@@ -816,0 +811,0 @@ }

@@ -455,2 +455,10 @@ declare namespace MCR {

/** custom websocket CDPSession */
export class WSSession implements CDPSession {
constructor(ws: any);
send: (method: string, params?: any) => Promise<any>;
on: (type: string, handler: (e: any) => void) => void;
detach: () => Promise<void>
}
export interface CDPOptions {

@@ -457,0 +465,0 @@ /** Adapt to the CDPSession of Playwright or Puppeteer */

@@ -14,2 +14,4 @@ const fs = require('fs');

const { getSnapshot, diffSnapshot } = require('./utils/snapshot.js');
const WSSession = require('./client/ws-session.js');
const CDPClient = require('./client/cdp-client.js');

@@ -185,3 +187,3 @@

Util.logTime(`generated coverage reports: ${coverageResults.reportPath}`, time_start);
Util.logTime(`generated coverage reports: ${EC.cyan(coverageResults.reportPath)}`, time_start);

@@ -291,4 +293,5 @@ const onEnd = this.options.onEnd;

MCR.diffSnapshot = diffSnapshot;
MCR.WSSession = WSSession;
MCR.CDPClient = CDPClient;
module.exports = MCR;
const http = require('http');
const https = require('https');
const dns = require('dns');
// fixed ECONNREFUSED on NodeJS 18
dns.setDefaultResultOrder('ipv4first');
// minimal http request for get sourcemap json

@@ -5,0 +9,0 @@ // https://nodejs.org/docs/latest/api/http.html#httprequesturl-options-callback

@@ -511,3 +511,3 @@ const fs = require('fs');

const duration = Date.now() - time_start;
const durationH = Util.TSF(duration);
const durationH = Util.TF(duration);
const ls = [`[MCR] ${message}`, ' ('];

@@ -514,0 +514,0 @@ if (duration <= 100) {

{
"name": "monocart-coverage-reports",
"version": "2.7.5",
"version": "2.7.6",
"description": "A code coverage tool to generate native V8 reports or Istanbul reports.",

@@ -27,3 +27,2 @@ "main": "./lib/index.js",

"scripts": {
"build-docs": "node ./scripts/build-docs.js",
"build-test": "node ./scripts/build-test.js",

@@ -55,3 +54,4 @@ "build": "sf lint && sf b -p && npm run build-test",

"test-client": "node ./test/test-client.js",
"test-all": "npm run test-node && npm run test-browser && npm run test-cli && npm run test-tsx && npm run test-merge && npm run build-docs",
"test-all": "npm run test-node && npm run test-browser && npm run test-cli && npm run test-tsx && npm run test-merge && node ./scripts/docs.js",
"test-node-14": "npm run test-node && npm run test-v8 && npm run test-anonymous && npm run test-css && npm run test-minify && npm run test-esbuild && npm run test-v8-and-istanbul && npm run test-cli",
"test": "npx mcr npm run test-all -c test/mcr.config.mcr.js",

@@ -58,0 +58,0 @@ "test:snap": "cross-env TEST_SNAPSHOT=true npm run test",

@@ -12,28 +12,27 @@ # Monocart Coverage Reports

* [Usage](#usage)
* [Default Options](#default-options)
* [Options](#options)
* [Available Reports](#available-reports)
* [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
* [onEnd Hook](#onend-hook)
* [Command Line](#command-line)
* [Compare Reports](#compare-reports)
* [Compare Workflows](#compare-workflows)
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
* [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
* [Manually Resolve the Sourcemap](#manually-resolve-the-sourcemap)
* [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
* [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
* [Collecting V8 Coverage Data with `CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
* [Multiprocessing Support](#multiprocessing-support)
* [Merge Coverage Reports](#merge-coverage-reports)
- [Collecting V8 Coverage Data with Playwright](#collecting-v8-coverage-data-with-playwright)
- [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
- [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
- [Collecting V8 Coverage Data with `CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
- [V8 Coverage Data API](#v8-coverage-data-api)
* [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
* [Resolve `sourcePath` for the Source Files](#resolve-sourcepath-for-the-source-files)
* [Adding Empty Coverage for Untested Files](#adding-empty-coverage-for-untested-files)
* [onEnd Hook](#onend-hook)
* [Ignoring Uncovered Codes](#ignoring-uncovered-codes)
* [Chromium Coverage API](#chromium-coverage-api)
* [V8 Coverage Data Format](#v8-coverage-data-format)
* [How to convert V8 to Istanbul](#how-to-convert-v8-to-istanbul)
- [Using `v8-to-istanbul`](#using-v8-to-istanbul)
- [How Monocart Works](#how-monocart-works)
* [Multiprocessing Support](#multiprocessing-support)
* [Command Line](#command-line)
* [Config File](#config-file)
* [Merge Coverage Reports](#merge-coverage-reports)
* [Common issues](#common-issues)
- [Unexpected coverage](#unexpected-coverage)
- [Unparsable source](#unparsable-source)
* [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
* [Common issues](#common-issues)
* [Integration](#integration)
* [Integration with Any Testing Framework](#integration-with-any-testing-framework)
* [Integration Examples](#integration-examples)
- [Playwright](#playwright)

@@ -47,9 +46,14 @@ - [Jest](#jest)

- [TestCafe](#testcafe)
- [Mocha](#mocha)
- [tsx](#tsx)
- [ts-node](#ts-node)
- [AVA](#ava)
- [Codecov](#codecov)
- [Coveralls](#coveralls)
- [Sonar Cloud](#sonar-cloud)
- [Integration with Any Testing Framework](#integration-with-any-testing-framework)
* [Thanks](#thanks)
## Usage
> It's recommended to use [Node.js 20+](https://nodejs.org/).
- [API](#multiprocessing-support)
```js

@@ -62,19 +66,21 @@ const MCR = require('monocart-coverage-reports');

}
const coverageReport = MCR(coverageOptions);
coverageReport.cleanCache();
const mcr = MCR(coverageOptions);
mcr.cleanCache();
await coverageReport.add(coverageData1);
await coverageReport.add(coverageData2);
await mcr.add(coverageData1);
await mcr.add(coverageData2);
await coverageReport.generate();
await mcr.generate();
// Or
// const { CoverageReport } = require('monocart-coverage-reports');
// const coverageReport = new CoverageReport(coverageOptions);
// const mcr = new CoverageReport(coverageOptions);
```
- [example v8](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js)
- [example istanbul](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-istanbul.js)
- [CLI](#command-line)
```sh
mcr node my-app.js -r v8,console-details
```
## Default Options
- [lib/default/options.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/lib/default/options.js)
## Options
- Default Options: [lib/default/options.js](./lib/default/options.js)
- `reports` [Available Reports](#available-reports)

@@ -88,3 +94,4 @@ - `entryFilter` and `sourceFilter` [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)

- Declaration [lib/index.d.ts](https://github.com/cenfun/monocart-coverage-reports/blob/main/lib/index.d.ts)
- Declaration: [lib/index.d.ts](./lib/index.d.ts)
- [Config file](#config-file)

@@ -102,5 +109,3 @@ ## Available Reports

- Better Support for Sourcemap Conversion
- Demos:
- Browser: Build with webpack [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [Minify](https://cenfun.github.io/monocart-coverage-reports/minify); Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/esbuild); Collect with [puppeteer](https://cenfun.github.io/monocart-coverage-reports/puppeteer/); [anonymous](https://cenfun.github.io/monocart-coverage-reports/anonymous/) and [css](https://cenfun.github.io/monocart-coverage-reports/css/)
- Node.js: Collect with [env](https://cenfun.github.io/monocart-coverage-reports/node-env), and also V8 [API](https://cenfun.github.io/monocart-coverage-reports/node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/node-cdp); Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/node-koa/)
- Demos: [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [more](https://cenfun.github.io/monocart-coverage-reports/)

@@ -120,3 +125,2 @@ ![](./assets/v8.gif)

- `html-spa`
- [Istanbul html-spa](https://cenfun.github.io/monocart-coverage-reports/istanbul/html-spa/)
- `json`

@@ -202,144 +206,6 @@ - `json-summary`

}
const coverageReport = MCR(coverageOptions);
coverageReport.cleanCache();
const mcr = MCR(coverageOptions);
mcr.cleanCache();
```
## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
When V8 coverage data collected, it actually contains the data of all entry files, for example:
```
dist/main.js
dist/vendor.js
dist/something-else.js
```
We can use `entryFilter` to filter the entry files. For example, we should remove `vendor.js` and `something-else.js` if they are not in our coverage scope.
```
dist/main.js
```
When inline or linked sourcemap exists to the entry file, the source files will be extracted from the sourcemap for the entry file, and the entry file will be removed if `logging` is not `debug`.
```
> src/index.js
> src/components/app.js
> node_modules/dependency/dist/dependency.js
```
We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
```
> src/index.js
> src/components/app.js
```
For example:
```js
const coverageOptions = {
entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
};
```
Or using `minimatch` pattern:
```js
const coverageOptions = {
entryFilter: "**/main.js",
sourceFilter: "**/src/**"
};
// supports multiple patterns:
const coverageOptions = {
entryFilter: {
'**/vendor.js': false,
'**/main.js': true
},
sourceFilter: {
'**/src/**': true
}
};
```
## onEnd Hook
For example, checking thresholds:
```js
const EC = require('eight-colors');
const coverageOptions = {
name: 'My Coverage Report',
outputDir: './coverage-reports',
onEnd: (coverageResults) => {
const thresholds = {
bytes: 80,
lines: 60
};
console.log('check thresholds ...', thresholds);
const errors = [];
const { summary } = coverageResults;
Object.keys(thresholds).forEach((k) => {
const pct = summary[k].pct;
if (pct < thresholds[k]) {
errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
}
});
if (errors.length) {
const errMsg = errors.join('\n');
console.log(EC.red(errMsg));
// throw new Error(errMsg);
// process.exit(1);
}
}
}
```
## Command Line
> The CLI will run the program as a [child process](https://nodejs.org/docs/latest/api/child_process.html) with `NODE_V8_COVERAGE=dir` until it exits gracefully, and generate the coverage report with the coverage data from the `dir`.
- Installing globally
```sh
npm i monocart-coverage-reports -g
mcr node ./test/specs/node.test.js -r v8,console-summary --lcov
```
- Locally in your project
```sh
npm i monocart-coverage-reports
npx mcr node ./test/specs/node.test.js -r v8,console-summary --lcov
```
- CLI Options
```sh
Usage: mcr [options] <command>
CLI to generate coverage reports
Arguments:
command command to execute
Options:
-V, --version output the version number
-c, --config <path> custom config file path
--logging <logging> off, error, info, debug
-n, --name <name> report name for title
-r, --reports <name[,name]> coverage reports to use
-o, --outputDir <dir> output dir for reports
-i, --inputDir <dir> input dir for merging raw files
--entryFilter <pattern> entry url filter
--sourceFilter <pattern> source path filter
--outputFile <path> output file for v8 report
--inline inline html for v8 report
--assetsPath <path> assets path if not inline
--lcov generate lcov.info file
--import <module> preload module at startup
--require <module> preload module at startup
-h, --help display help for command
```
- Loading config file by priority:
- Custom config file with `-c` or `--config`
- `mcr.config.js`
- `mcr.config.cjs`
- `mcr.config.mjs`
- `mcr.config.json` - json format
- `mcr.config.ts` (requires preloading the ts execution module)
- `.mcrrc.js`
- `.mcrrc` - json format
- Working with `tsx`, see [mcr-tsx](https://github.com/cenfun/mcr-tsx)
```sh
npx mcr --import tsx tsx ./src/example.ts
```
- Working with `ts-node`, see [mcr-ts-node](https://github.com/cenfun/mcr-ts-node)
## Compare Reports

@@ -359,19 +225,10 @@ | | Istanbul | V8 | V8 to Istanbul |

## Compare Workflows
- Istanbul Workflows
- 1, [Collecting Istanbul coverage data](#collecting-istanbul-coverage-data)
- 2, Adding coverage data and generating coverage report
- V8 Workflows
- 1, [Collecting V8 coverage data](#collecting-v8-coverage-data)
- 3, Adding coverage data and generating coverage report
## Collecting Istanbul Coverage Data
- Instrumenting source code
> Before collecting Istanbul coverage data, It requires your source code is instrumented with Istanbul
- webpack: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), example: [webpack.config-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-istanbul.js)
- webpack: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), example: [webpack.config-istanbul.js](./test/webpack.config-istanbul.js)
- rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
- vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
- Browser
- Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-istanbul.js)
- Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](./test/test-istanbul.js)
- Node.js

@@ -381,34 +238,42 @@ - Collecting coverage data from `global.__coverage__`

## Collecting V8 Coverage Data
- For source code: enable `sourcemap` and do not compress/minify:
- [webpack](https://webpack.js.org/configuration/): `devtool: source-map` and `mode: development`, example [webpack.config-v8.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-v8.js)
- Sourcemap for source code: enable `sourcemap` and do not compress/minify:
- [webpack](https://webpack.js.org/configuration/): `devtool: source-map` and `mode: development`, example [webpack.config-v8.js](./test/webpack.config-v8.js)
- [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true`
- [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
- [esbuild](https://esbuild.github.io/api/): `sourcemap: true` and `minify: false`
- [Manually Resolve the Sourcemap](#manually-resolve-the-sourcemap)
- Browser (Chromium Only)
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api):
- [Playwright example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js), and [anonymous](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-anonymous.js), [css](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-css.js)
- see [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
- [Collecting V8 Coverage Data with Playwright](#collecting-v8-coverage-data-with-playwright)
- [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
- Node.js
- see [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
- [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
- CDP
- see [Collecting V8 Coverage Data with `CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
- [Collecting V8 Coverage Data with `CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
## Manually Resolve the Sourcemap
> Sometimes, the sourcemap file cannot be successfully loaded with the `sourceMappingURL`, you can try to manually read the sourcemap file before the coverage data is added to the report.
### Collecting V8 Coverage Data with Playwright
```js
const jsCoverage = await page.coverage.stopJSCoverage();
jsCoverage.forEach((entry) => {
// read sourcemap for the my-dist.js manually
if (entry.url.endsWith('my-dist.js')) {
entry.sourceMap = JSON.parse(fs.readFileSync('dist/my-dist.js.map').toString('utf-8'));
}
});
await Promise.all([
page.coverage.startJSCoverage({
resetOnNavigation: false
}),
page.coverage.startCSSCoverage({
resetOnNavigation: false
})
]);
await MCR(coverageOptions).add(jsCoverage);
await page.goto("your page url");
const [jsCoverage, cssCoverage] = await Promise.all([
page.coverage.stopJSCoverage(),
page.coverage.stopCSSCoverage()
]);
const coverageData = [... jsCoverage, ... cssCoverage];
```
see [./test/test-v8.js](./test/test-v8.js), and [anonymous](./test/test-anonymous.js), [css](./test/test-css.js)
## Collecting Raw V8 Coverage Data with Puppeteer
> Puppeteer does not provide raw v8 coverage data by default. A simple conversion is required, see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
### Collecting Raw V8 Coverage Data with Puppeteer
```js

@@ -426,3 +291,3 @@ await Promise.all([

await page.goto(url);
await page.goto("your page url");

@@ -442,4 +307,5 @@ const [jsCoverage, cssCoverage] = await Promise.all([

```
see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
## Node.js V8 Coverage Report for Server Side
### Node.js V8 Coverage Report for Server Side
Possible solutions:

@@ -477,3 +343,3 @@ - [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`

## Collecting V8 Coverage Data with `CDPClient` API
### Collecting V8 Coverage Data with `CDPClient` API
- Work with node debugger `--inspect=9229`

@@ -522,89 +388,93 @@ ```js

## Multiprocessing Support
> The data will be added to `[outputDir]/.cache`, After the generation of the report, this data will be removed unless debugging has been enabled or a raw report has been used, see [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
- Main process, before the start of testing
### V8 Coverage Data API
- [V8 coverage report](https://v8.dev/blog/javascript-code-coverage) - Native support for JavaScript code coverage to V8. (Chromium only)
- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
- [Puppeteer Coverage class](https://pptr.dev/api/puppeteer.coverage)
- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
```js
const MCR = require('monocart-coverage-reports');
const coverageOptions = require('path-to/same-options.js');
const coverageReport = MCR(coverageOptions);
// clean previous cache before the start of testing
// unless the running environment is new and no cache
coverageReport.cleanCache();
```
// 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;
}
- Sub process 1, testing stage 1
```js
const MCR = require('monocart-coverage-reports');
const coverageOptions = require('path-to/same-options.js');
const coverageReport = MCR(coverageOptions);
// do not clean cache in the stage
await coverageReport.add(coverageData1);
```
// 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 functions 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;
}
- Sub process 2, testing stage 2
```js
const MCR = require('monocart-coverage-reports');
const coverageOptions = require('path-to/same-options.js');
const coverageReport = MCR(coverageOptions);
// do not clean cache in the stage
await coverageReport.add(coverageData2);
// 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[];
```
- Main process, after the completion of testing
```js
// generate coverage reports after the completion of testing
const MCR = require('monocart-coverage-reports');
const coverageOptions = require('path-to/same-options.js');
const coverageReport = MCR(coverageOptions);
// do not clean cache before generating reports
await coverageReport.generate();
## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
When V8 coverage data collected, it actually contains the data of all entry files, for example:
```
## Merge Coverage Reports
The following usage scenarios may require merging coverage reports:
- When the code is executed in different environments, like Node.js Server Side and browser Client Side (Next.js for instance). Each environment may generate its own coverage report. Merging them can give a more comprehensive view of the test coverage. see example [nextjs-with-playwright](https://github.com/cenfun/nextjs-with-playwright) for automatic report merging.
- When the code is subjected to different kinds of testing. For example, unit tests with Jest might cover certain parts of the code, while end-to-end tests with Playwright might cover other parts. Merging these different coverage reports can provide a holistic view of what code has been tested.
- When tests are run on different machines or different shards, each might produce its own coverage report. Merging these can give a complete picture of the test coverage across all machines or shards.
If the reports cannot be merged automatically, then here is how to manually merge the reports.
First, using the `raw` report to export the original coverage data to the specified directory.
dist/main.js
dist/vendor.js
dist/something-else.js
```
We can use `entryFilter` to filter the entry files. For example, we should remove `vendor.js` and `something-else.js` if they are not in our coverage scope.
```
dist/main.js
```
When inline or linked sourcemap exists to the entry file, the source files will be extracted from the sourcemap for the entry file, and the entry file will be removed if `logging` is not `debug`.
```
> src/index.js
> src/components/app.js
> node_modules/dependency/dist/dependency.js
```
We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
```
> src/index.js
> src/components/app.js
```
For example:
```js
const coverageOptions = {
name: 'My Unit Test Coverage Report',
outputDir: "./coverage-reports/unit",
reports: [
['raw', {
// relative path will be "./coverage-reports/unit/raw"
outputDir: "raw"
}],
['v8'],
['console-summary']
]
entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
};
```
Then, after all the tests are completed, generate a merged report with option `inputDir`:
Or using `minimatch` pattern:
```js
// esm syntax
import fs from "fs";
import { CoverageReport } from 'monocart-coverage-reports';
const coverageOptions = {
name: 'My Merged Coverage Report',
inputDir: [
'./coverage-reports/unit/raw',
'./coverage-reports/e2e/raw'
],
outputDir: './coverage-reports/merged',
reports: [
['v8'],
['console-summary']
],
onEnd: () => {
// remove the raw files if it useless
fs.rmSync('./coverage-reports/unit/raw', {
recursive: true,
force: true
})
entryFilter: "**/main.js",
sourceFilter: "**/src/**"
};
// supports multiple patterns:
const coverageOptions = {
entryFilter: {
'**/vendor.js': false,
'**/main.js': true
},
sourceFilter: {
'**/src/**': true
}
};
await new CoverageReport(coverageOptions).generate();
```

@@ -676,2 +546,33 @@

## onEnd Hook
For example, checking thresholds:
```js
const EC = require('eight-colors');
const coverageOptions = {
name: 'My Coverage Report',
outputDir: './coverage-reports',
onEnd: (coverageResults) => {
const thresholds = {
bytes: 80,
lines: 60
};
console.log('check thresholds ...', thresholds);
const errors = [];
const { summary } = coverageResults;
Object.keys(thresholds).forEach((k) => {
const pct = summary[k].pct;
if (pct < thresholds[k]) {
errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
}
});
if (errors.length) {
const errMsg = errors.join('\n');
console.log(EC.red(errMsg));
// throw new Error(errMsg);
// process.exit(1);
}
}
}
```
## Ignoring Uncovered Codes

@@ -699,84 +600,120 @@ To ignore codes, use the special comment which starts with `v8 ignore `:

## Chromium Coverage API
- [V8 coverage report](https://v8.dev/blog/javascript-code-coverage) - Native support for JavaScript code coverage to V8. (Chromium only)
- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
- [Puppeteer Coverage class](https://pptr.dev/api/puppeteer.coverage)
- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage)
## Multiprocessing Support
> The data will be added to `[outputDir]/.cache`, After the generation of the report, this data will be removed unless debugging has been enabled or a raw report has been used, see [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
- Main process, before the start of testing
```js
const MCR = require('monocart-coverage-reports');
const coverageOptions = require('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();
```
## V8 Coverage Data Format
- Sub process 1, testing stage 1
```js
// 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;
}
const MCR = require('monocart-coverage-reports');
const coverageOptions = require('path-to/same-options.js');
const mcr = MCR(coverageOptions);
// do not clean cache in the stage
await mcr.add(coverageData1);
```
// 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 functions 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;
}
- Sub process 2, testing stage 2
```js
const MCR = require('monocart-coverage-reports');
const coverageOptions = require('path-to/same-options.js');
const mcr = MCR(coverageOptions);
// do not clean cache in the stage
await mcr.add(coverageData2);
```
// 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[];
}
- Main process, after the completion of testing
```js
// generate coverage reports after the completion of testing
const MCR = require('monocart-coverage-reports');
const coverageOptions = require('path-to/same-options.js');
const mcr = MCR(coverageOptions);
// do not clean cache before generating reports
await mcr.generate();
```
export type V8CoverageData = ScriptCoverage[];
## Command Line
> The CLI will run the program as a [child process](https://nodejs.org/docs/latest/api/child_process.html) with `NODE_V8_COVERAGE=dir` until it exits gracefully, and generate the coverage report with the coverage data from the `dir`.
- Installing globally
```sh
npm i monocart-coverage-reports -g
mcr node ./test/specs/node.test.js -r v8,console-summary --lcov
```
see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
## How to convert V8 to Istanbul
### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
It is a popular library which is used to convert V8 coverage format to istanbul's coverage format. Most test frameworks are using it, such as [Jest](https://github.com/jestjs/jest/), [Vitest](https://github.com/vitest-dev/vitest), but it has two major problems:
- 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
```js
const a = tf ? 'true' : 'false';
^ ^ ^
m1 p m2
- Locally in your project
```sh
npm i monocart-coverage-reports
npx mcr node ./test/specs/node.test.js -r v8,console-summary --lcov
```
> `m1` and `m2` are two consecutive mappings, `p` is the position we looking for. However, we can only get the position of the `m1` or `m2` if we don't fix it to `p`. Especially the generated code is different from the original code, such as the code was minified, compressed or converted, it is difficult to find the exact position.
- 2, The coverage of functions and branches is incorrect. V8 only provided coverage at functions and it's blocks. But if a function is uncovered (count = 0), there is no information for it's blocks and sub-level functions. And also there are some problems about counting the functions and branches.
- CLI Options
```sh
Usage: mcr [options] [command]
### How Monocart Works
We implemented new converter:
- 1, Trying to fix the middle position if not found the exact mapping for the position.
- 2, Finding all functions, statements and branches by parsing the source code [AST](https://github.com/acornjs/acorn). However, there's a small issue, which is the V8 cannot provide effective branch coverage information for `AssignmentPattern`.
CLI to generate coverage reports
| AST | V8 |
| :---------------------| :------------- |
| AssignmentPattern | 🛇 Not Support |
| ConditionalExpression | ✔ |
| IfStatement | ✔ |
| LogicalExpression | ✔ |
| SwitchStatement | ✔ |
Arguments:
command command to execute
## Debug for Coverage and Sourcemap
> Sometimes, the coverage is not what we expect. The next step is to figure out why, and we can easily find out the answer step by step through debugging.
- Start debugging for v8 report with option `logging: 'debug'`
Options:
-V, --version output the version number
-c, --config <path> custom config file path
-l, --logging <logging> off, error, info, debug
-n, --name <name> report name for title
-r, --reports <name[,name]> coverage reports to use
-o, --outputDir <dir> output dir for reports
-i, --inputDir <dir> input dir for merging raw files
--entryFilter <pattern> entry url filter
--sourceFilter <pattern> source path filter
--outputFile <path> output file for v8 report
--inline inline html for v8 report
--assetsPath <path> assets path if not inline
--lcov generate lcov.info file
--import <module> preload module at startup
--require <module> preload module at startup
-h, --help display help for command
```
- Use `--` to separate sub CLI args
```sh
mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
```
## Config File
Loading config file by priority:
- Custom config file:
- CLI: `mcr --config <my-config-file-path>`
- API: `await mcr.loadConfig("my-config-file-path")`
- `mcr.config.js`
- `mcr.config.cjs`
- `mcr.config.mjs`
- `mcr.config.json` - json format
- `mcr.config.ts` (requires preloading the ts execution module)
- `.mcrrc.js`
- `.mcrrc` - json format
## Merge Coverage Reports
The following usage scenarios may require merging coverage reports:
- When the code is executed in different environments, like Node.js Server Side and browser Client Side (Next.js for instance). Each environment may generate its own coverage report. Merging them can give a more comprehensive view of the test coverage. see example [nextjs-with-playwright](https://github.com/cenfun/nextjs-with-playwright) for automatic report merging.
- When the code is subjected to different kinds of testing. For example, unit tests with Jest might cover certain parts of the code, while end-to-end tests with Playwright might cover other parts. Merging these different coverage reports can provide a holistic view of what code has been tested.
- When tests are run on different machines or different shards, each might produce its own coverage report. Merging these can give a complete picture of the test coverage across all machines or shards.
If the reports cannot be merged automatically, then here is how to manually merge the reports.
First, using the `raw` report to export the original coverage data to the specified directory.
```js
const coverageOptions = {
logging: 'debug',
name: 'My Unit Test Coverage Report',
outputDir: "./coverage-reports/unit",
reports: [
['raw', {
// relative path will be "./coverage-reports/unit/raw"
outputDir: "raw"
}],
['v8'],

@@ -787,12 +724,64 @@ ['console-summary']

```
When `logging` is `debug`, the raw report data will be preserved in `[outputDir]/.cache` or `[outputDir]/raw` if `raw` report is used. And the dist file will be preserved in the V8 list, and by opening the browser's devtool, it makes data verification visualization effortless.
![](./assets/debug-coverage.png)
Then, after all the tests are completed, generate a merged report with option `inputDir`:
```js
const fs = require('fs');
const { CoverageReport } = require('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',
- Check sourcemap with [Source Map Visualization](https://evanw.github.io/source-map-visualization/)
// 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
return filePath;
},
![](./assets/debug-sourcemap.png)
reports: [
['v8'],
['console-details']
],
onEnd: () => {
// remove the raw files if it useless
inputDir.forEach((p) => {
fs.rmSync(p, {
recursive: true,
force: true
});
});
}
};
await new CoverageReport(coverageOptions).generate();
```
## Common issues
- `Unparsable source`
### Unexpected coverage
In most cases, it happens when the coverage of the generated code is converted to the coverage of the original code through a sourcemap. In other words, it's an issue with the sourcemap. Most of the time, we can solve this by setting `minify` to `false` in the build tools configuration. Let's take a look at an example:
```js
const a = tf ? 'true' : 'false';
^ ^ ^
m1 p m2
```
`m1` and `m2` are two consecutive mappings, `p` is the position we looking for. However, we can only get the position of the `m1` or `m2` if we don't fix it to `p`. Especially the generated code is different from the original code, such as the code was minified, compressed or converted, it is difficult to find the exact position. You can try [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
How `MCR` Works:
- 1, Trying to fix the middle position if not found the exact mapping for the position. However, for non-JS code, such as Vue template, JSX, etc., it might be hard to find a perfect solution.
- 2, Finding all functions, statements and branches by parsing the source code [AST](https://github.com/acornjs/acorn). However, there's a small issue, which is the V8 cannot provide effective branch coverage information for `AssignmentPattern`.
### Unparsable source
It happens during the parsing of the source code into AST, if the source code is not in the standard ECMAScript. For example `ts`, `jsx` and so on. There is a option to fix it, which is to manually compile the source code for these files.

@@ -817,4 +806,32 @@ ```js

## Integration
## Debug for Coverage and Sourcemap
> Sometimes, the coverage is not what we expect. The next step is to figure out why, and we can easily find out the answer step by step through debugging.
- Start debugging for v8 report with option `logging: 'debug'`
```js
const coverageOptions = {
logging: 'debug',
reports: [
['v8'],
['console-summary']
]
};
```
When `logging` is `debug`, the raw report data will be preserved in `[outputDir]/.cache` or `[outputDir]/raw` if `raw` report is used. And the dist file will be preserved in the V8 list, and by opening the browser's devtool, it makes data verification visualization effortless.
![](./assets/debug-coverage.png)
- Check sourcemap with [Source Map Visualization](https://evanw.github.io/source-map-visualization/)
![](./assets/debug-sourcemap.png)
## Integration with Any Testing Framework
- API
- Collecting coverage data when any stage of the test is completed, and adding the coverage data to the coverage reporter. `await mcr.add(coverageData)`
- Generating the coverage reports after the completion of all tests. `await mcr.generate()`
- see [Multiprocessing Support](#multiprocessing-support)
- CLI
- Wrapping with any CLI. `mcr your-cli --your-arguments`
- see [Command line](#command-line)
## Integration Examples
### [Playwright](https://github.com/microsoft/playwright)

@@ -853,2 +870,25 @@ - [monocart-reporter](https://github.com/cenfun/monocart-reporter) - A Playwright custom reporter, supports generating [Code Coverage Report](https://github.com/cenfun/monocart-reporter?#code-coverage-report)

### [Mocha](https://github.com/mochajs/mocha)
```sh
mcr mocha ./test/**/*.js
```
```sh
mcr --import tsx mocha ./test/**/*.ts
```
- see [mcr-tsx](https://github.com/cenfun/mcr-tsx)
### [tsx](https://github.com/privatenumber/tsx)
```sh
mcr --import tsx tsx ./src/example.ts
```
- see [mcr-tsx](https://github.com/cenfun/mcr-tsx)
### [ts-node](https://github.com/TypeStrong/ts-node)
- see [mcr-ts-node](https://github.com/cenfun/mcr-ts-node)
### [AVA](https://github.com/avajs/ava)
```sh
mcr ava
```
### [Codecov](https://codecov.com/)

@@ -907,6 +947,2 @@ [![codecov](https://codecov.io/gh/cenfun/monocart-coverage-reports/graph/badge.svg?token=H0LW7UKYU3)](https://codecov.io/gh/cenfun/monocart-coverage-reports)

```
### Integration with Any Testing Framework
- Collecting coverage data when any stage of the test is completed, and adding the coverage data to the coverage reporter.
- Generating the coverage reports after the completion of all tests.
- see [Multiprocessing Support](#multiprocessing-support)

@@ -913,0 +949,0 @@ ### VSCode Extension

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc