vitest-coverage-merge

Merge Vitest unit and browser component test coverage with automatic normalization
50,475
Install
npm install vitest-coverage-merge
Latest Version:0.2.0
License:MIT
Last Updated:Jan 12, 2026
Links: GitHub  ·  npm
Maintainer: steve_zhang

vitest-coverage-merge

Merge Vitest coverage from unit tests (jsdom) and browser component tests.

📝 UPDATE (January 2025):

As of v0.2.0, normalization is now OFF by default. Use --normalize if you need to strip import statements and directives.

The Problem

When running Vitest with both jsdom (unit tests) and browser mode (component tests), the coverage reports have different statement counts:

Environment Import Handling
jsdom V8 doesn't count imports as statements
Real browser V8 counts imports as executable statements

This makes it impossible to accurately merge coverage without normalization.

The Solution

vitest-coverage-merge provides smart merging of coverage data. When you encounter statement count mismatches, you can use the --normalize flag to strip import statements and Next.js directives ('use client', 'use server') before merging.

Installation

npm install -D vitest-coverage-merge

Usage

CLI

# Merge unit and component coverage
npx vitest-coverage-merge coverage/unit coverage/component -o coverage/merged

# Merge multiple sources
npx vitest-coverage-merge coverage/unit coverage/component coverage/e2e -o coverage/all

# Merge with normalization (strips imports/directives)
npx vitest-coverage-merge coverage/unit coverage/component -o coverage/merged --normalize

Options

vitest-coverage-merge <dir1> <dir2> [dir3...] -o <output>

Arguments:
  <dir1> <dir2>    Coverage directories to merge (at least 2 required)
                   Each directory should contain coverage-final.json

Options:
  -o, --output     Output directory for merged coverage (required)
  --normalize      Strip import statements and directives before merging
  -h, --help       Show help
  -v, --version    Show version

Programmatic API

import { mergeCoverage, normalizeCoverage } from 'vitest-coverage-merge'

// Merge coverage directories
const result = await mergeCoverage({
  inputDirs: ['coverage/unit', 'coverage/component'],
  outputDir: 'coverage/merged',
  normalize: false, // default (set to true to strip imports/directives)
  reporters: ['json', 'lcov', 'html'], // default
})

console.log(result.statements.pct) // e.g., 85.5

Example Vitest Setup

vitest.config.ts (unit tests)

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'jsdom',
    include: ['src/**/*.test.{ts,tsx}'],
    exclude: ['src/**/*.browser.test.{ts,tsx}'],
    coverage: {
      enabled: true,
      provider: 'v8',
      reportsDirectory: './coverage/unit',
      reporter: ['json', 'lcov', 'html'],
    },
  },
})

vitest.component.config.ts (browser tests)

import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'

export default defineConfig({
  test: {
    include: ['src/**/*.browser.test.{ts,tsx}'],
    coverage: {
      enabled: true,
      provider: 'v8',
      reportsDirectory: './coverage/component',
      reporter: ['json', 'lcov', 'html'],
    },
    browser: {
      enabled: true,
      provider: playwright(),
      instances: [{ browser: 'chromium' }],
    },
  },
})

package.json scripts

{
  "scripts": {
    "test": "npm run test:unit && npm run test:component",
    "test:unit": "vitest run",
    "test:component": "vitest run --config vitest.component.config.ts",
    "coverage:merge": "vitest-coverage-merge coverage/unit coverage/component -o coverage/merged"
  }
}

Output

The tool generates:

  • coverage-final.json - Istanbul coverage data
  • lcov.info - LCOV format for CI tools
  • index.html - HTML report (in lcov-report folder)

How It Works

  1. Load coverage-final.json from each input directory
  2. Normalize (optional, with --normalize flag) by stripping:
    • ESM import statements (import ... from '...')
    • React/Next.js directives ('use client', 'use server') - if present
  3. Smart merge using one of two strategies:
    • Default (no --normalize): "More items wins" - prefers source with more coverage items, giving you the union of all structures
    • With --normalize: "Fewer items wins" - prefers sources without directive statements (browser-style coverage)
  4. Merge execution counts using max strategy (takes highest count for each item)
  5. Generate reports (JSON, LCOV, HTML)

Note: This tool works with any ESM-based Vitest project (React, Vue, Svelte, vanilla JS/TS, etc.). The React/Next.js directive stripping only applies if those directives are present in your codebase - for non-React projects, it simply has no effect.

Why Not Use Vitest's Built-in Merge?

Vitest's --merge-reports is designed for sharded test runs, not for merging coverage from different environments (jsdom vs browser). It doesn't handle the statement count differences caused by how V8 treats imports differently in each environment.

Related Tools

License

MIT

Related Packages

@testforgejs/vue-test-core

Core testing utilities and pipeline orchestration for the TestForge component te...

622 0
@ztf-comp/auto-test-web

Auto-test Web: Vitest generation and reports

636
vitest-browser-vue

Render Vue components in Vitest Browser Mode

5,213,984 79
@ohos-ports/vitest-browser-vue

Render Vue components in Vitest Browser Mode

0 6
@i18n-micro/test-utils

Testing helpers for Nuxt I18n Micro (translations, locales, formatting mocks).

152,219 251

Version History

Version Released License
0.2.0 MIT
0.1.0 MIT