Monocart Coverage Reports

repository·main·Indexed 41 days ago

https://github.com/cenfun/monocart-coverage-reports

A JavaScript code coverage tool (v2.13.0) that generates reports from native V8 or Istanbul coverage data. It supports a wide range of built-in formats including HTML, LCOV, JSON, and Cobertura, as well as custom reporters. The tool includes a CDPClient API for collecting coverage via the Chrome DevTools Protocol and integrates with Playwright, Puppeteer, and Selenium.

Tokens
15.8K
Snippets
47
Records
58
Agent score
98%

What's inside Monocart Coverage Reports

  1. Compare V8 and Istanbul coverage data capabilities

    main

    When using V8 data, Monocart can automatically convert it to Istanbul format for Istanbul-compatible reports. However, the capabilities differ depending on the source format:

    FeatureIstanbul DataV8 DataV8 to Istanbul Conversion
    Bytes❌✅❌
    Statements✅✅✅
    Branches✅✅✅
    Functions✅✅✅
    Lines✅✅✅
    Execution counts✅✅✅
    CSS coverage❌✅✅
    Minified code❌✅❌
  2. Ignore code blocks using V8 or C8 comments

    main

    You can exclude specific lines or blocks from coverage using special comments. Monocart supports standard V8 ignore syntax as well as c8 and nodejs syntax.

    V8 Syntax:

    • /* v8 ignore start */ / /* v8 ignore stop */ (block)
    • /* v8 ignore next */ (next line)
    • /* v8 ignore next N */ (next N lines)

    C8/NodeJS Syntax:

    • /* c8 ignore start */ / /* c8 ignore stop */
    • /* node:coverage disable */ / /* node:coverage enable */

    To disable comment-based ignoring entirely, set v8Ignore: false in your options.

  3. Understand the V8 Coverage Data format

    main

    V8 coverage is structured as an array of ScriptCoverage objects. Monocart Coverage Reports extends the standard V8 entries with additional metadata to make reporting easier.

    Standard V8 Types:

    • CoverageRange: Defines a startOffset, endOffset, and count for a specific code segment.
    • FunctionCoverage: Contains a functionName, an array of ranges, and isBlockCoverage status.
    • ScriptCoverage: Contains a scriptId, url, and an array of functions.

    Monocart Extensions (added to each entry):

    • url: Script URL or file path.
    • type: Either "js" or "css".
    • source: (JS) The actual source code text.
    • sourceMap: (JS) The parsed sourcemap object.
    • scriptOffset: (JS) Byte offset for the script wrapper (useful for vm modules).
    • distFile: (JS) The distribution bundle file this source was unpacked from.
    • empty: boolean - true if the entry was added via the all option (0% coverage).
    • fake: boolean - true if the source is not original (e.g. compiled from .ts).
  4. Collecting Istanbul coverage data

    main

    To collect Istanbul coverage, you must first instrument your source code. Common methods include:

    Instrumentation

    • Webpack (with Babel): Use babel-plugin-istanbul.
    • CLI: Use nyc instrument or the istanbul-lib-instrument API.
    • Vite: Use vite-plugin-istanbul.
    • Rollup: Use rollup-plugin-istanbul.
    • SWC: Use swc-plugin-coverage-instrument.

    Data Collection

    • Browser: Collect data from window.__coverage__.
    • Node.js: Collect data from global.__coverage__.
    • CDP: Use the getIstanbulCoverage() method via the CDPClient API.
  5. Use the Monocart Coverage Reports API

    main

    You can use the library via a shorthand factory function MCR() or by instantiating the CoverageReport class. Both approaches create the same instance.

    If you prefer class instantiation or need to call loadConfig() separately, use new CoverageReport().

    const MCR = require('monocart-coverage-reports');
    const mcr = MCR({
        name: 'My Coverage Report - 2024-02-28',
        outputDir: './coverage-reports',
        reports: ["v8", "console-details"],
        cleanCache: true
    });
    await mcr.add(coverageData);
    await mcr.generate();
  6. Prepare source code for V8 coverage collection

    main

    To ensure coverage maps correctly to your original source code, you must enable sourcemaps in your build tool. Additionally, for accurate results, disable tree-shaking and minification where possible.

    Build Tool Configurations:

    • webpack: Set devtool: source-map and mode: development.
    • rollup: Set sourcemap: true and treeshake: false.
    • esbuild: Set sourcemap: true, treeShaking: false, and minify: false.
    • vite: Set sourcemap: true and minify: false.
  7. Use the Monocart Coverage Reports CLI

    main

    The CLI runs your program as a child process with NODE_V8_COVERAGE=dir until it exits gracefully, then generates reports from that directory.

    Global Installation:

    npm i monocart-coverage-reports -g
    mcr node ./test/specs/node.test.js -r v8,console-details --lcov

    Local Installation:

    npm i monocart-coverage-reports
    npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov

    Key CLI Features:

    • Preloading Modules: Use --import <module> (for ESM, requires Node.js >= 18.19) or --require <module> (for CJS) to support TypeScript/JSX runtimes like tsx or ts-node.
    • Environment Variables: Use --env [path] to load a .env file before the child process starts (requires Node.js >= 20.6).
    • Argument Separation: Use -- to separate CLI arguments for mcr from arguments intended for the sub-command.
    • Merging: Use the merge command to combine raw coverage data from multiple inputDirs without running a new process.
    npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
  8. Collect V8 coverage from Node.js

    main

    There are several ways to collect V8 coverage in a Node.js environment:

    1. Environment Variable: Set NODE_V8_COVERAGE=<dir> before running your program. Coverage data is saved to <dir> upon graceful exit.
      • Example: cross-env NODE_V8_COVERAGE=.temp/v8-coverage-env node test.js
    2. V8 API: Use v8.takeCoverage() to write coverage to disk on demand without waiting for process exit.
    3. Inspector API: Connect to the V8 inspector to enable and take coverage.
    4. CDP API: Enable Node debugging and use the Chrome DevTools Protocol.
    5. Child Process: Combine NODE_V8_COVERAGE with child process execution.
  9. Implement multiprocessing support with MCR

    main

    To collect coverage across multiple processes (e.g., different test stages), use the monocart-coverage-reports API to manage a shared cache. Data is stored in [outputDir]/.cache during the process and removed after report generation.

    Workflow:

    1. Main Process (Start): Initialize MCR and call mcr.cleanCache() to clear previous runs.
    2. Sub-processes: In each testing stage, initialize MCR with the same options and call await mcr.add(coverageData) to append data to the cache.
    3. Main Process (End): After all tests finish, call await mcr.generate() to produce the final report.
    const MCR = require('monocart-coverage-reports');
    const coverageOptions = require('path-to/same-options.js');
    
    // 1. Main Process: Setup
    const mcr = MCR(coverageOptions);
    mcr.cleanCache();
    
    // 2. Sub-process: Add data
    // (In your test runner/worker)
    const workerMcr = MCR(coverageOptions);
    await workerMcr.add(coverageData);
    
    // 3. Main Process: Finalize
    await mcr.generate();
  10. Merge Coverage Reports Manually

    main

    When coverage is generated in different environments (e.g., Node.js server vs. Browser client) or different test types (e.g., Jest unit tests vs. Playwright E2E tests), you may need to merge them.

    Manual Merging Workflow:

    1. Export raw coverage data from your test runners to specific directories using the raw report type.
    2. Create a script using CoverageReport from monocart-coverage-reports.
    3. Provide inputDir containing the paths to the raw data.
    4. Use sourcePath to unify file paths if the same file has different relative paths in different environments.
    5. Use entryFilter and sourceFilter to include/exclude specific files (e.g., node_modules).

    CLI Merge Command:

    mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged
    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',
    
        // 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.replace("unit-dist/", "").replace("e2e-dist/", "");
        },
    
        reports: [
            ['v8'],
            ['console-details']
        ]
    };
    await new CoverageReport(coverageOptions).generate();
  11. Include untested files in the report using the all option

    main

    By default, untested files are omitted from reports. To include them with 0% coverage, use the all option.

    all can be:

    • A string or array of strings representing directories.
    • An object containing a dir (directory) and a filter (to specify file types like js or css).
    • An object containing a transformer function to compile files (e.g., .ts or .vue) using tools like @swc/core so they can be analyzed by the AST parser.
    // Simple directory inclusion
    const coverageOptions = {
        all: './src',
    };
    
    // Advanced configuration with filtering and transformation
    const coverageOptions = {
        all: {
            dir: ['./src'],
            filter: {
                '**/ignored-*.js': false,
                '**/*.html': false,
                '**/*.scss': "css",
                '**/*': true
            },
            transformer: async (entry) => {
                // Example using SWC to transform TypeScript
                const { code, map } = await swc.transform(entry.source, {
                    filename: path.basename(entry.url),
                    sourceMaps: true,
                    isModule: true,
                    jsc: { parser: { syntax: "typescript", jsx: true } }
                });
                entry.source = code;
                entry.sourceMap = JSON.parse(map);
            }
        }
    };