{"id":34694185,"url":"https://github.com/stevez/nextcov","last_synced_at":"2026-03-12T01:28:42.334Z","repository":{"id":328044542,"uuid":"1112565828","full_name":"stevez/nextcov","owner":"stevez","description":"Next.js code coverage for Playwright E2E tests using V8 coverage","archived":false,"fork":false,"pushed_at":"2026-03-05T22:32:58.000Z","size":965,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-06T02:04:29.383Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stevez.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-08T19:58:47.000Z","updated_at":"2026-03-05T22:32:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/stevez/nextcov","commit_stats":null,"previous_names":["stevez/nextcov"],"tags_count":46,"template":false,"template_full_name":null,"purl":"pkg:github/stevez/nextcov","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevez%2Fnextcov","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevez%2Fnextcov/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevez%2Fnextcov/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevez%2Fnextcov/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stevez","download_url":"https://codeload.github.com/stevez/nextcov/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevez%2Fnextcov/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30411227,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-12T00:40:14.898Z","status":"ssl_error","status_checked_at":"2026-03-12T00:40:08.439Z","response_time":84,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-24T22:27:21.115Z","updated_at":"2026-03-12T01:28:42.325Z","avatar_url":"https://github.com/stevez.png","language":"TypeScript","funding_links":[],"categories":["Utils"],"sub_categories":[],"readme":"# nextcov\n\n[![npm version](https://badge.fury.io/js/nextcov.svg)](https://badge.fury.io/js/nextcov)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nV8 code coverage for Next.js and Vite applications with Playwright E2E tests.\n\nMerge your Playwright E2E coverage with Vitest unit test coverage for complete coverage reports.\n\n## Why nextcov?\n\n### The React Server Components Testing Gap\n\nNext.js App Router introduced React Server Components (RSC) and async server components. These are notoriously difficult to unit test because:\n\n- **Server Components run only on the server** - They can't be rendered in jsdom or similar test environments\n- **Async components fetch data directly** - Mocking becomes complex and often unreliable\n- **Tight coupling with Next.js runtime** - Server actions, cookies, headers require the full framework\n\n**The practical solution?** Test server components through E2E tests with Playwright, where they run in their natural environment. But until now, there was no good way to get coverage for these tests.\n\n### The Coverage Problem\n\nBut this creates a coverage gap:\n- **Unit tests** (Vitest) cover client components, utilities, and hooks\n- **E2E tests** (Playwright) cover server components, pages, and user flows\n- **No unified coverage** - You're missing the full picture\n\nGetting accurate combined coverage is challenging because:\n- Playwright runs against production builds (bundled, minified code)\n- Source maps are needed to map back to original TypeScript/JSX\n- Different coverage formats need to be merged correctly\n\n### The Solution\n\n**nextcov** is the first tool to bridge this gap by:\n- Collecting V8 coverage from both client and server during E2E tests\n- Using source maps to map bundled code back to original sources\n- Producing Istanbul-compatible output that merges seamlessly with Vitest coverage\n\nNow you can finally see the complete coverage picture for your Next.js application.\n\n## Features\n\n- **Next.js + Vite support** - Works with Next.js and Vite applications\n- **Client + Server coverage** - Collects coverage from both browser and Node.js server (Next.js)\n- **Client-only mode** - For Vite apps, static sites, SPAs, or deployed environments\n- **Dev mode support** - Works with `next dev` (no build required), auto-detected\n- **Production mode support** - Works with `next build \u0026\u0026 next start` using external source maps\n- **Auto-detection** - Automatically detects dev vs production mode, no configuration needed\n- **V8 native coverage** - Uses Node.js built-in `NODE_V8_COVERAGE` for accurate server coverage\n- **Source map support** - Maps bundled code back to original TypeScript/JSX\n- **Vitest compatible** - Output merges seamlessly with Vitest coverage reports\n- **Playwright integration** - Simple fixtures for automatic coverage collection\n- **Istanbul format** - Generates standard coverage-final.json for tooling compatibility\n- **Multiple reporters** - HTML, LCOV, JSON, text-summary, and more\n- **ESM and CJS support** - Works with both ES modules and CommonJS projects\n\n## Inspiration\n\nThis project is inspired by and builds upon:\n- [Vitest](https://vitest.dev/) - For the V8 coverage approach and Istanbul integration\n- [ast-v8-to-istanbul](https://github.com/AriPerkkio/ast-v8-to-istanbul) - For AST-based V8 to Istanbul conversion\n- [monocart-coverage-reports](https://github.com/cenfun/monocart-coverage-reports) - For V8 coverage processing\n\n## Installation\n\n```bash\nnpm install nextcov --save-dev\n```\n\n## Requirements\n\n- Node.js \u003e= 20\n- Next.js 14+ or Vite 5+\n- Playwright 1.40+\n\n### Peer Dependencies\n\n```bash\nnpm install @playwright/test --save-dev\n```\n\n## Quick Setup with `nextcov init`\n\nThe fastest way to get started is with the `init` command:\n\n```bash\nnpx nextcov init\n```\n\nThis interactive command will:\n- Create `e2e/global-setup.ts` - Initialize coverage collection\n- Create `e2e/global-teardown.ts` - Finalize and generate reports\n- Create `e2e/fixtures/test-fixtures.ts` - Coverage collection fixture\n- Modify `playwright.config.ts` - Add nextcov configuration\n- Modify `package.json` - Add npm scripts (`dev:e2e`, `coverage:merge`)\n- Modify `next.config.ts` - Add E2E mode settings for source maps (Next.js only)\n\n### Options\n\n```bash\nnpx nextcov init                 # Interactive mode\nnpx nextcov init -y              # Use defaults, no prompts\nnpx nextcov init --client-only   # Client-only mode (no server coverage)\nnpx nextcov init --e2e-dir tests # Custom e2e directory\nnpx nextcov init --js            # Use JavaScript instead of TypeScript\nnpx nextcov init --force         # Overwrite existing files\n```\n\n### Coverage Mode\n\nDuring interactive setup, you'll be asked to choose a coverage mode:\n\n| Mode | Description | Use When |\n|------|-------------|----------|\n| **Full (client + server)** | Collects both browser and Node.js coverage | Next.js with `next dev` or `next start` |\n| **Client-only** | Only browser coverage, simpler setup | Vite apps, static sites, SPAs, deployed environments |\n\nAfter running `init`, follow the next steps shown to start collecting coverage.\n\n## Example Projects\n\n### nextcov-example\n\nSee [nextcov-example](https://github.com/stevez/nextcov-example) for a simple Next.js App Router application demonstrating nextcov with Playwright E2E tests.\n\n**Highlights:**\n- Simple todo CRUD application\n- 100% branch coverage achieved with E2E tests\n- Demonstrates coverage for client components with conditional rendering\n\n| Metric | Coverage |\n|--------|----------|\n| Statements | 100% |\n| Branches | 100% |\n| Functions | 100% |\n| Lines | 100% |\n\n### restaurant-reviews-platform\n\nSee [restaurant-reviews-platform](https://github.com/stevez/restaurant-reviews-platform) for a complete working example of nextcov integrated with a Next.js App Router application using Playwright E2E tests and Vitest unit tests.\n\n**Highlights:**\n- Full-stack Next.js application with authentication\n- Combines unit tests (Vitest) with E2E tests (Playwright)\n- Demonstrates merging coverage from multiple sources\n\n| Coverage Type | Lines | Description |\n|---------------|-------|-------------|\n| **Unit Tests** (Vitest) | ~80% | Client components, utilities, API routes |\n| **E2E Tests** (Playwright + nextcov) | ~46% | Server components, pages, user flows |\n| **Merged** | ~88% | Complete picture of your application |\n\n### Key Files (restaurant-reviews-platform)\n\n- [playwright.config.ts](https://github.com/stevez/restaurant-reviews-platform/blob/main/playwright.config.ts) - Playwright config with nextcov settings\n- [e2e/fixtures.ts](https://github.com/stevez/restaurant-reviews-platform/blob/main/e2e/fixtures.ts) - Coverage collection fixture\n- [e2e/global-setup.ts](https://github.com/stevez/restaurant-reviews-platform/blob/main/e2e/global-setup.ts) - Start server coverage (auto-detects dev/production)\n- [e2e/global-teardown.ts](https://github.com/stevez/restaurant-reviews-platform/blob/main/e2e/global-teardown.ts) - Coverage finalization\n- [next.config.js](https://github.com/stevez/restaurant-reviews-platform/blob/main/next.config.js) - Next.js source map configuration\n\n## Quick Start\n\n### 1. Configure Next.js for Source Maps\n\nIn your `next.config.js`:\n\n```js\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  // Enable source maps for production builds (required for coverage)\n  productionBrowserSourceMaps: true,\n\n  // Optional: Configure webpack for E2E mode\n  webpack: (config, { dev }) =\u003e {\n    if (process.env.E2E_MODE) {\n      // Use full source maps for accurate coverage\n      config.devtool = 'source-map'\n\n      // Disable minification to preserve readable code\n      config.optimization = {\n        ...config.optimization,\n        minimize: false,\n      }\n    }\n    return config\n  },\n}\n\nmodule.exports = nextConfig\n```\n\n### 2. Configure Playwright with nextcov\n\nIn your `playwright.config.ts`:\n\n```typescript\nimport { defineConfig, devices } from '@playwright/test'\nimport type { NextcovConfig } from 'nextcov'\n\n// Extend Playwright config type to include nextcov\ntype PlaywrightConfigWithNextcov = Parameters\u003ctypeof defineConfig\u003e[0] \u0026 {\n  nextcov?: NextcovConfig\n}\n\n// Export nextcov config separately for use in global-teardown\nexport const nextcov: NextcovConfig = {\n  cdpPort: 9230,\n  buildDir: '.next',           // Next.js build output directory (use 'dist' if customized)\n  outputDir: 'coverage/e2e',\n  sourceRoot: './src',\n  include: ['src/**/*.{ts,tsx,js,jsx}'],\n  exclude: [\n    'src/**/__tests__/**',\n    'src/**/*.test.{ts,tsx}',\n    'src/**/*.spec.{ts,tsx}',\n  ],\n  reporters: ['html', 'lcov', 'json', 'text-summary'],\n  log: true,                   // Enable verbose logging (default: false)\n}\n\nconst config: PlaywrightConfigWithNextcov = {\n  testDir: './e2e',\n  globalSetup: './e2e/global-setup.ts',\n  globalTeardown: './e2e/global-teardown.ts',\n  use: {\n    baseURL: 'http://localhost:3000',\n  },\n  projects: [\n    {\n      name: 'chromium',\n      use: { ...devices['Desktop Chrome'] },\n    },\n  ],\n  nextcov,\n}\n\nexport default defineConfig(config)\n```\n\n### 3. Add Coverage Fixture\n\nCreate `e2e/fixtures.ts`:\n\n```typescript\nimport { test as base, expect } from '@playwright/test'\nimport { collectClientCoverage } from 'nextcov/playwright'\n\nexport const test = base.extend({\n  // Auto-collect v8 coverage for each test\n  coverage: [\n    async ({ page }, use, testInfo) =\u003e {\n      await collectClientCoverage(page, testInfo, use)\n    },\n    { scope: 'test', auto: true },\n  ],\n})\n\nexport { expect }\n```\n\n### 4. Add Global Setup and Teardown\n\nCreate `e2e/global-setup.ts`:\n\n```typescript\nimport * as path from 'path'\nimport { initCoverage, loadNextcovConfig } from 'nextcov/playwright'\n\nexport default async function globalSetup() {\n  // Load config from playwright.config.ts\n  const config = await loadNextcovConfig(\n    path.join(process.cwd(), 'playwright.config.ts')\n  )\n  // Initialize coverage collection (works for both client-only and full modes)\n  await initCoverage(config)\n}\n```\n\nCreate `e2e/global-teardown.ts`:\n\n```typescript\nimport * as path from 'path'\nimport { finalizeCoverage } from 'nextcov/playwright'\nimport { loadNextcovConfig } from 'nextcov'\n\nexport default async function globalTeardown() {\n  // Load config from playwright.config.ts\n  const config = await loadNextcovConfig(\n    path.join(process.cwd(), 'playwright.config.ts')\n  )\n  await finalizeCoverage(config)\n}\n```\n\n### 5. Write Tests Using the Fixture\n\nIn your test files (`e2e/example.spec.ts`):\n\n```typescript\nimport { test, expect } from './fixtures'\n\ntest('should load home page', async ({ page }) =\u003e {\n  await page.goto('/')\n  await expect(page.getByRole('heading')).toBeVisible()\n})\n```\n\n### 6. Run Tests\n\n```bash\n# Build Next.js with source maps (use E2E_MODE for optimal coverage)\nE2E_MODE=true npm run build\n\n# Start the server with V8 coverage enabled and run tests\nNODE_V8_COVERAGE=.v8-coverage NODE_OPTIONS='--inspect=9230' npm run start \u0026\nnpx playwright test\n\n# Or use start-server-and-test for better cross-platform support\nnpx start-server-and-test 'NODE_V8_COVERAGE=.v8-coverage NODE_OPTIONS=--inspect=9230 npm start' http://localhost:3000 'npx playwright test'\n```\n\nThe key environment variables:\n- `NODE_V8_COVERAGE=.v8-coverage` - Enables Node.js to collect V8 coverage data\n- `NODE_OPTIONS='--inspect=9230'` - Enables CDP connection for triggering coverage flush\n\n## Development Mode Coverage\n\nnextcov supports collecting coverage directly from `next dev` without requiring a production build. This is useful for faster iteration during development.\n\n### Auto-Detection\n\nnextcov **automatically detects** whether you're running in dev mode or production mode. You don't need to configure anything - just use the same `globalSetup` and `globalTeardown` for both modes.\n\nHow it works:\n- **Dev mode** (`next dev --inspect=9230`): Next.js spawns a worker process on port 9231 (inspect port + 1). nextcov connects to the worker via CDP and uses `Profiler.startPreciseCoverage()` to collect coverage.\n- **Production mode** (`next start --inspect=9230`): Next.js runs on port 9230 directly. nextcov uses `NODE_V8_COVERAGE` env var to collect coverage, triggered via CDP.\n\nThe auto-detection output looks like:\n```\n📊 Auto-detecting server mode...\n  Trying dev mode (worker port 9231)...\n  ✓ Dev mode detected (webpack eval scripts found)\n  ✓ Server coverage collection started\n```\n\nOr for production mode:\n```\n📊 Auto-detecting server mode...\n  Trying dev mode (worker port 9231)...\n  ⚠️ Failed to connect to CDP (dev mode): Error: connect ECONNREFUSED\n  ℹ️ Production mode will be used (NODE_V8_COVERAGE + port 9230)\n```\n\n### Running Tests Against Dev Server\n\n```bash\n# Start Next.js dev server with V8 coverage and inspector enabled\nNODE_V8_COVERAGE=.v8-coverage NODE_OPTIONS='--inspect=9230' npm run dev \u0026\n\n# Run Playwright tests\nnpx playwright test\n```\n\n### Dev Mode vs Production Mode\n\n| Aspect | Dev Mode | Production Mode |\n|--------|----------|-----------------|\n| **Server Command** | `next dev` | `next build \u0026\u0026 next start` |\n| **Source Maps** | Inline (base64 in JS) | External (.map files) |\n| **Build Required** | No | Yes |\n| **Hot Reload** | Yes | No |\n| **Build Directory** | Not used (inline source maps) | Configurable (`buildDir`) |\n| **CDP Port** | `cdpPort + 1` (e.g., 9231) | `cdpPort` (e.g., 9230) |\n| **Performance** | Slower | Faster |\n| **Recommended For** | Development iteration | CI/CD, final coverage |\n\n### When to Use Each Mode\n\n- **Dev Mode**: Quick feedback during development, testing new features\n- **Production Mode**: CI pipelines, accurate production-like coverage, final reports\n\nBoth modes produce identical Istanbul-compatible output that can be merged with Vitest coverage.\n\n## Vite Support\n\nnextcov supports Vite applications with client-only coverage. Vite serves source files directly with inline source maps, making coverage collection straightforward.\n\n### Quick Setup for Vite\n\n```bash\nnpx nextcov init --client-only\n```\n\nThis creates the necessary files for client-only coverage collection. Then configure your `playwright.config.ts`:\n\n```typescript\nimport { defineConfig, devices } from '@playwright/test'\nimport type { NextcovConfig } from 'nextcov'\n\nexport const nextcov: NextcovConfig = {\n  outputDir: 'coverage/e2e',\n  sourceRoot: './src',\n  collectServer: false,  // Client-only mode for Vite\n  include: ['src/**/*.{ts,tsx,js,jsx}'],\n  exclude: [\n    'src/**/__tests__/**',\n    'src/**/*.test.{ts,tsx}',\n    'src/**/*.spec.{ts,tsx}',\n  ],\n  reporters: ['html', 'lcov', 'json', 'text-summary'],\n}\n\nexport default defineConfig({\n  globalSetup: './e2e/global-setup.ts',\n  globalTeardown: './e2e/global-teardown.ts',\n  testDir: './e2e',\n  use: {\n    baseURL: 'http://localhost:5173',  // Vite default port\n  },\n  webServer: {\n    command: 'npm run dev',\n    url: 'http://localhost:5173',\n    reuseExistingServer: !process.env.CI,\n  },\n  // ... other config\n})\n```\n\nRun your tests:\n\n```bash\nnpx playwright test\n```\n\nCoverage reports will be generated at `coverage/e2e/`.\n\n## Client-Only Mode\n\nFor scenarios where you don't need server-side coverage, use `collectServer: false`. This is useful for:\n\n- **Vite applications** - React, Vue, Svelte apps built with Vite\n- **Static sites** - Next.js static exports (`next export` or `output: 'export'`)\n- **SPAs** - Single page applications with external/serverless backends\n- **Deployed environments** - Testing against staging or production URLs\n- **Simpler setup** - No `NODE_V8_COVERAGE` or `--inspect` flags needed\n\n### Configuration\n\nDisable server coverage in your `playwright.config.ts`:\n\n```typescript\nexport const nextcov: NextcovConfig = {\n  collectServer: false,  // Skip all server coverage collection\n  outputDir: 'coverage/e2e',\n  reporters: ['html', 'lcov', 'json', 'text-summary'],\n}\n```\n\n### Setup\n\nWith `collectServer: false`, the setup is simpler (no `--inspect` flags needed):\n\n**1. Coverage fixture** (`e2e/fixtures.ts`) - same as full mode:\n```typescript\nimport { test as base, expect } from '@playwright/test'\nimport { collectClientCoverage } from 'nextcov/playwright'\n\nexport const test = base.extend({\n  coverage: [\n    async ({ page }, use, testInfo) =\u003e {\n      await collectClientCoverage(page, testInfo, use)\n    },\n    { scope: 'test', auto: true },\n  ],\n})\n\nexport { expect }\n```\n\n**2. Global setup** (`e2e/global-setup.ts`):\n```typescript\nimport * as path from 'path'\nimport { initCoverage, loadNextcovConfig } from 'nextcov/playwright'\n\nexport default async function globalSetup() {\n  const config = await loadNextcovConfig(path.join(process.cwd(), 'playwright.config.ts'))\n  await initCoverage(config)  // Initializes client-only mode\n}\n```\n\n**3. Global teardown** (`e2e/global-teardown.ts`):\n```typescript\nimport * as path from 'path'\nimport { finalizeCoverage, loadNextcovConfig } from 'nextcov/playwright'\n\nexport default async function globalTeardown() {\n  const config = await loadNextcovConfig(path.join(process.cwd(), 'playwright.config.ts'))\n  await finalizeCoverage(config)  // Only processes client coverage\n}\n```\n\n**4. Run tests** - no special server flags needed:\n```bash\n# Just start your server normally and run tests\nnpm start \u0026\nnpx playwright test\n\n# Or test against a deployed environment\nnpx playwright test --config=playwright.staging.config.ts\n```\n\n### When to Use Client-Only Mode\n\n| Scenario | Use `collectServer: false`? |\n|----------|------------------|\n| Testing Next.js with `next dev` or `next start` | No - use full mode for server coverage |\n| Testing static export (`next export`) | Yes |\n| Testing against deployed staging/production | Yes |\n| Testing SPA with external API | Yes |\n| Quick local testing without inspector setup | Yes |\n\n### Behavior\n\nWhen `collectServer: false`:\n- `startServerCoverage()` becomes a no-op (safe to call, does nothing)\n- `finalizeCoverage()` only processes client-side coverage from Playwright\n- No CDP connection attempts are made\n\n### Server-Only Mode\n\nFor scenarios where you only want server coverage (e.g., API testing without browser), use `collectClient: false`:\n\n```typescript\nexport const nextcov: NextcovConfig = {\n  collectClient: false,  // Skip client coverage collection\n  outputDir: 'coverage/e2e',\n}\n```\n\nWhen `collectClient: false`:\n- `collectClientCoverage()` still needs to be called (for test fixtures), but collected data is ignored during finalization\n- `finalizeCoverage()` only processes server-side coverage\n\n## Merging with Vitest Coverage\n\nThe main power of nextcov is combining E2E coverage with unit test coverage.\n\n### Configure Vitest for Coverage\n\nIn your `vitest.config.ts`:\n\n```typescript\nimport { defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  test: {\n    coverage: {\n      provider: 'v8',\n      reporter: ['json', 'html'],\n      reportsDirectory: './coverage/unit',\n    },\n  },\n})\n```\n\n### Using the CLI (Recommended)\n\nThe simplest way to merge coverage is using the `nextcov` CLI:\n\n```bash\n# Merge unit and E2E coverage\nnpx nextcov merge coverage/unit coverage/e2e -o coverage/merged\n\n# Merge multiple coverage directories\nnpx nextcov merge coverage/unit coverage/e2e coverage/browser -o coverage/all\n\n# Customize reporters\nnpx nextcov merge coverage/unit coverage/e2e --reporters html,lcov,json\n```\n\nAdd to your `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"coverage:merge\": \"npx nextcov merge coverage/unit coverage/integration -o coverage/merged\"\n  }\n}\n```\n\n### CLI Reference\n\n```\nUsage: npx nextcov merge \u003cdirs...\u003e [options]\n\nMerge multiple coverage directories into a single report.\n\nBy default, coverage directives (import statements, 'use client', 'use server')\nare stripped from the coverage data before merging. This ensures accurate merged\ncoverage when combining unit/component tests with E2E tests.\n\nArguments:\n  dirs                  Coverage directories to merge (must contain coverage-final.json)\n\nOptions:\n  -o, --output \u003cdir\u003e    Output directory for merged report (default: ./coverage/merged)\n  --reporters \u003clist\u003e    Comma-separated reporters: html,lcov,json,text-summary (default: html,lcov,json,text-summary)\n  --no-strip            Disable stripping of import statements and directives\n  --help                Show this help message\n\nExamples:\n  npx nextcov merge coverage/unit coverage/integration\n  npx nextcov merge coverage/unit coverage/e2e coverage/browser -o coverage/merged\n  npx nextcov merge coverage/unit coverage/integration --reporters html,lcov\n```\n\n### Why Strip Directives?\n\nWhen merging unit/component test coverage with E2E test coverage, you may encounter mismatched statement counts for the same file. This happens because:\n\n- **Unit/component tests** (Vitest) see import statements and directives as executable statements\n- **E2E tests** (Next.js bundled code) don't include imports or directives in coverage data\n\nThe `--no-strip` option is available if you want to preserve the original coverage data, but the default stripping behavior produces more accurate merged reports.\n\n### Using the API (Advanced)\n\nFor more control, you can use the programmatic API:\n\n```typescript\nimport * as path from 'path'\nimport {\n  mergeCoverage,\n  printCoverageSummary,\n  printCoverageComparison,\n} from 'nextcov'\n\nconst projectRoot = process.cwd()\n\nasync function main() {\n  console.log('Merging coverage reports...\\n')\n\n  const result = await mergeCoverage({\n    unitCoveragePath: path.join(projectRoot, 'coverage/unit/coverage-final.json'),\n    e2eCoveragePath: path.join(projectRoot, 'coverage/e2e/coverage-final.json'),\n    outputDir: path.join(projectRoot, 'coverage/merged'),\n    projectRoot,\n    verbose: true,\n  })\n\n  if (!result) {\n    console.error('Failed to merge coverage')\n    process.exit(1)\n  }\n\n  // Print merged summary\n  printCoverageSummary(result.summary, 'Merged Coverage Summary')\n\n  // Print comparison\n  if (result.unitSummary) {\n    printCoverageComparison(result.unitSummary, result.e2eSummary, result.summary)\n  }\n\n  // List E2E-only files\n  if (result.e2eOnlyFiles.length \u003e 0) {\n    console.log(`\\nE2E-only files (${result.e2eOnlyFiles.length}):`)\n    for (const file of result.e2eOnlyFiles) {\n      console.log(`  - ${file}`)\n    }\n  }\n}\n\nmain().catch(console.error)\n```\n\nRun with:\n\n```bash\nnpx ts-node --esm scripts/merge-coverage.ts\n```\n\n## CLI Commands\n\nnextcov provides three CLI commands for different workflows:\n\n### `nextcov init` - Interactive Setup\n\nThe fastest way to get started with nextcov. Creates all necessary configuration files and modifies your project setup.\n\n```bash\n# Interactive mode - prompts for options\nnpx nextcov init\n\n# Use defaults (no prompts)\nnpx nextcov init -y\n\n# Client-only mode (Vite, static sites, SPAs)\nnpx nextcov init --client-only\n\n# Custom E2E directory\nnpx nextcov init --e2e-dir tests\n\n# Use JavaScript instead of TypeScript\nnpx nextcov init --js\n\n# Overwrite existing files\nnpx nextcov init --force\n```\n\n**What it creates:**\n- `e2e/global-setup.ts` - Initialize coverage collection\n- `e2e/global-teardown.ts` - Finalize and generate reports\n- `e2e/fixtures/test-fixtures.ts` - Coverage collection fixture\n- Modifies `playwright.config.ts` - Adds nextcov configuration\n- Modifies `package.json` - Adds npm scripts\n- Modifies `next.config.ts` - Adds source map settings (Next.js only)\n\nSee [Quick Setup with `nextcov init`](#quick-setup-with-nextcov-init) for detailed documentation.\n\n### `nextcov merge` - Merge Coverage Reports\n\nMerge multiple coverage directories into a single unified report. Useful for combining unit tests, component tests, and E2E tests.\n\n```bash\n# Basic usage - merge two coverage directories\nnpx nextcov merge coverage/unit coverage/e2e\n\n# Specify output directory\nnpx nextcov merge coverage/unit coverage/e2e -o coverage/merged\n\n# Merge multiple directories\nnpx nextcov merge coverage/unit coverage/component coverage/e2e\n\n# Customize reporters\nnpx nextcov merge coverage/unit coverage/e2e --reporters html,lcov,json\n\n# Disable coverage directive stripping\nnpx nextcov merge coverage/unit coverage/e2e --no-strip\n```\n\n**CLI Reference:**\n\n```\nUsage: npx nextcov merge \u003cdirs...\u003e [options]\n\nMerge multiple coverage directories into a single report.\n\nArguments:\n  dirs                  Coverage directories to merge (must contain coverage-final.json)\n\nOptions:\n  -o, --output \u003cdir\u003e    Output directory for merged report (default: ./coverage/merged)\n  --reporters \u003clist\u003e    Comma-separated reporters: html,lcov,json,text-summary\n                        (default: html,lcov,json,text-summary)\n  --no-strip            Disable stripping of import statements and directives\n  --help                Show this help message\n\nExamples:\n  npx nextcov merge coverage/unit coverage/e2e\n  npx nextcov merge coverage/unit coverage/e2e -o coverage/all\n  npx nextcov merge coverage/unit coverage/e2e --reporters html,lcov\n```\n\n**Why strip directives?**\n\nBy default, `nextcov merge` strips import statements and directives (`'use client'`, `'use server'`) from coverage data before merging. This ensures accurate merged coverage when combining different test types:\n\n- **Unit/component tests** (Vitest) count imports and directives as statements\n- **E2E tests** (bundled code) don't include imports or directives\n\nUse `--no-strip` if you need to preserve the original coverage data.\n\n**Add to package.json:**\n\n```json\n{\n  \"scripts\": {\n    \"coverage:merge\": \"nextcov merge coverage/unit coverage/e2e -o coverage/merged\"\n  }\n}\n```\n\nSee [Merging with Vitest Coverage](#merging-with-vitest-coverage) for detailed documentation.\n\n### `nextcov check` - Check Project Configuration\n\nCheck project configuration for issues that affect V8 coverage accuracy.\n\n```bash\n# Run config check\nnpx nextcov check\n\n# JSON output for CI\nnpx nextcov check --json\n\n# Verbose output\nnpx nextcov check --verbose\n```\n\n**Project configuration checks:**\n\n| Check | Severity | Description |\n|-------|----------|-------------|\n| Outdated browserslist | error | Browsers don't support `?.` and `??` |\n| Babel detected | error | May transpile modern syntax |\n| Playwright not found | error | Required for nextcov |\n| Missing browserslist | warning | `?.` and `??` may be transpiled |\n| Jest detected | warning | Consider Vitest for V8 coverage |\n| Source maps not enabled | warning | Missing `productionBrowserSourceMaps` |\n| Vitest not found | info | Recommended for V8 coverage |\n\n**Exit codes:**\n- `0` - No configuration errors found\n- `1` - Configuration errors found\n- `2` - Error during check\n\n## API Reference\n\n### Playwright Integration (`nextcov/playwright`)\n\n#### `initCoverage(config?)`\n\nInitializes coverage collection. This is the recommended function to call in globalSetup. It handles both client-only and full (client + server) modes:\n\n- **Client-only mode** (`collectServer: false`): Just initializes logging/timing settings. No server connection is made.\n- **Full mode** (`collectServer: true`): Connects to the Next.js server via CDP to collect server-side coverage.\n\n```typescript\nimport { initCoverage, loadNextcovConfig } from 'nextcov/playwright'\n\nconst config = await loadNextcovConfig('./playwright.config.ts')\nawait initCoverage(config)\n```\n\n#### `startServerCoverage(config?)`\n\nStarts server-side coverage collection. Lower-level function called by `initCoverage` for full mode. Auto-detects dev mode vs production mode.\n\n```typescript\nimport { startServerCoverage, loadNextcovConfig } from 'nextcov/playwright'\n\nconst config = await loadNextcovConfig('./playwright.config.ts')\nawait startServerCoverage(config)\n```\n\nReturns `true` if dev mode was detected, `false` for production mode.\n\n#### `collectClientCoverage(page, testInfo, use, config?)`\n\nCollects V8 coverage for a single test. Use in a Playwright fixture.\n\n```typescript\nawait collectClientCoverage(page, testInfo, use)\n```\n\n**Optional config:**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `transformUrl` | `(url: string) =\u003e string` | Rewrite coverage entry URLs before filtering/saving. Useful for Chrome extensions or other non-Vite environments where source URLs don't match the default pattern. |\n\n**Chrome extension example:**\n\n```typescript\nimport { pathToFileURL } from 'node:url'\n\nawait collectClientCoverage(page, testInfo, async () =\u003e {\n  await page.goto(`chrome-extension://${id}/panel.html`)\n  await use(page)\n}, {\n  transformUrl: (url) =\u003e {\n    if (!url.startsWith('chrome-extension://')) return url\n    return pathToFileURL(path.join(distDir, new URL(url).pathname)).href\n  },\n})\n```\n\n#### `finalizeCoverage(options?)`\n\nFinalizes coverage collection and generates reports. Call in globalTeardown.\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `projectRoot` | `string` | `process.cwd()` | Project root directory |\n| `buildDir` | `string` | `'.next'` | Next.js build output directory |\n| `outputDir` | `string` | `'coverage/e2e'` | Output directory for reports |\n| `sourceRoot` | `string` | `'./src'` | Source root relative to project |\n| `include` | `string[]` | `['src/**/*']` | Glob patterns to include |\n| `exclude` | `string[]` | `['node_modules/**']` | Glob patterns to exclude |\n| `reporters` | `string[]` | `['html', 'lcov', 'json']` | Report formats |\n| `collectServer` | `boolean` | `true` | Collect server-side coverage (set `false` for static sites, SPAs) |\n| `collectClient` | `boolean` | `true` | Collect client-side coverage from Playwright |\n| `cleanup` | `boolean` | `true` | Clean up temp files |\n| `cdpPort` | `number` | `9230` | CDP port for triggering v8.takeCoverage() |\n| `log` | `boolean` | `false` | Enable verbose logging output |\n\n### Main API (`nextcov`)\n\nThe main `nextcov` entry point exports a minimal public API. Most users should import from `nextcov/playwright` instead (see above).\n\n#### Configuration Types\n\nUsed primarily for TypeScript definitions in `playwright.config.ts`:\n\n```typescript\nimport type { NextcovConfig } from 'nextcov'\n\nexport const nextcov: NextcovConfig = {\n  outputDir: 'coverage/e2e',\n  sourceRoot: './src',\n  // ... other options\n}\n```\n\n#### `loadNextcovConfig(configPath?)`\n\nLoads nextcov configuration from playwright.config.ts. Typically used in global-setup/teardown files.\n\n```typescript\nimport { loadNextcovConfig } from 'nextcov'\n\nconst config = await loadNextcovConfig('./e2e/playwright.config.ts')\n```\n\n#### `mergeCoverage(options)`\n\nProgrammatically merge coverage reports. For most use cases, prefer the CLI: `npx nextcov merge`.\n\n```typescript\nimport { mergeCoverage } from 'nextcov'\n\nconst result = await mergeCoverage({\n  unitCoveragePath: './coverage/unit/coverage-final.json',\n  e2eCoveragePath: './coverage/e2e/coverage-final.json',\n  outputDir: './coverage/merged',\n  reporters: ['html', 'lcov', 'json'],\n  verbose: false,\n  projectRoot: process.cwd(),\n})\n```\n\n#### Helper Functions\n\nFor custom merge scripts:\n\n```typescript\nimport {\n  printCoverageSummary,\n  printCoverageComparison,\n  type MergeCoverageResult,\n} from 'nextcov'\n\n// Print formatted coverage summary\nprintCoverageSummary(result.summary, 'Merged Coverage')\n\n// Print comparison between unit and E2E coverage\nprintCoverageComparison(result.unitSummary, result.e2eSummary, result.summary)\n```\n\n## How It Works\n\n1. **Coverage Collection**\n   - Client: Uses Playwright's CDP integration to collect V8 coverage from the browser\n   - Server: Uses Node.js `NODE_V8_COVERAGE` env var to collect coverage, triggered via CDP `v8.takeCoverage()`\n\n2. **Source Mapping**\n   - Loads source maps from Next.js build output (`.next/`)\n   - Handles inline source maps and external `.map` files\n   - Maps bundled JavaScript back to original TypeScript/JSX\n\n3. **Format Conversion**\n   - Converts V8 coverage format to Istanbul format using AST analysis\n   - Preserves accurate line, function, and branch coverage\n\n4. **Merging**\n   - Merges coverage from multiple sources (unit tests, E2E tests)\n   - Uses intelligent strategies to combine coverage data\n   - Handles different instrumentation structures\n\n5. **Report Generation**\n   - Generates Istanbul-compatible reports (HTML, LCOV, JSON, etc.)\n   - Compatible with standard coverage tools and CI integrations\n\n## Troubleshooting\n\n### 0% Coverage\n\n- Ensure `productionBrowserSourceMaps: true` is set in `next.config.js`\n- Verify source maps exist in `.next/static/chunks/*.map`\n- Check that `E2E_MODE=true` is set when building\n\n### Server Coverage Not Working\n\n- Ensure Next.js is started with `NODE_V8_COVERAGE=.v8-coverage` and `NODE_OPTIONS='--inspect=9230'`\n- Verify the CDP port matches your config\n- Check that `globalTeardown` calls `finalizeCoverage()`\n\n### Source Maps Not Found\n\n- Run `npm run build` with `E2E_MODE=true`\n- Check `.next/static/chunks/` for `.map` files\n- Ensure webpack `devtool` is set to `'source-map'`\n\n### Dev Mode Coverage Not Working\n\n- Ensure Next.js dev server is started with `NODE_V8_COVERAGE=.v8-coverage` and `NODE_OPTIONS='--inspect=9230'`\n- Check that your source files are in the `sourceRoot` directory (default: `src`)\n\n### Inconsistent Branch Counts Between Unit and E2E Tests\n\nIf you notice E2E coverage has more branches than unit tests for the same file, it's likely because Next.js is transpiling optional chaining (`?.`) and nullish coalescing (`??`) operators.\n\n**The problem:**\n- Source code: `existingReview?.rating ?? 5`\n- Transpiled: `existingReview === null || existingReview === void 0 ? void 0 : existingReview.rating`\n- Unit tests see 1 branch (source), E2E sees 3 branches (transpiled)\n\n**The solution:** Add a `browserslist` to your `package.json` targeting modern browsers:\n\n```json\n{\n  \"browserslist\": [\n    \"chrome 111\",\n    \"edge 111\",\n    \"firefox 111\",\n    \"safari 16.4\"\n  ]\n}\n```\n\nThis tells Next.js SWC to preserve `?.` and `??` operators since modern browsers support them natively. After adding this, rebuild your app and branch counts should be consistent.\n\n**Note:** These browser versions match the [Next.js recommended modern targets](https://nextjs.org/docs/architecture/supported-browsers). Adjust based on your actual browser support requirements.\n\n### Slow Coverage Processing\n\nIf coverage processing takes a very long time (30+ seconds), you may have large bundled dependencies. V8 coverage works on the bundled output, so large libraries bundled into your app will slow down source map processing.\n\n**Worker thread configuration:**\n\nnextcov uses worker threads to parallelize AST processing for large bundles. You can tune this with:\n\n```bash\n# Disable workers (run in main thread) - useful for debugging or low-core CI\nNEXTCOV_WORKERS=0 npx playwright test\n\n# Use specific number of workers (default: auto-detected based on CPU cores)\nNEXTCOV_WORKERS=4 npx playwright test\n```\n\nWorker thread settings:\n- `0` = Main thread only (fastest for 2-core CI runners, avoids worker overhead)\n- `1` = Single worker (not recommended)\n- `2+` = Parallel workers (default auto-selects based on CPU cores, max 8)\n\n**Common culprits:**\n- `react-icons` - Barrel exports bundle entire icon sets even when importing a few icons\n- Large UI component libraries\n- Unoptimized imports from `lodash`, `@mui/icons-material`, etc.\n\n**Solutions:**\n1. **Use direct imports** instead of barrel imports:\n   ```typescript\n   // Bad - bundles entire icon set\n   import { FiEdit } from 'react-icons/fi'\n\n   // Good - import only what you need\n   import FiEdit from 'react-icons/fi/FiEdit'\n   ```\n\n2. **Use inline SVGs** for icons you use frequently:\n   ```tsx\n   // Best for small icon sets - zero runtime cost\n   export const EditIcon = ({ size = 24 }) =\u003e (\n     \u003csvg width={size} height={size} viewBox=\"0 0 24 24\"\u003e\n       \u003cpath d=\"...\" /\u003e\n     \u003c/svg\u003e\n   )\n   ```\n\n3. **Enable optimizePackageImports** in Next.js config:\n   ```js\n   experimental: {\n     optimizePackageImports: ['react-icons', 'lodash'],\n   }\n   ```\n\n4. **Check your bundle size**: If `.next/server/app/page.js` is several MB, you likely have bundle bloat. A lean app should have page bundles under 500KB.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevez%2Fnextcov","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstevez%2Fnextcov","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevez%2Fnextcov/lists"}