ScreenshotNeo

BlogHow-to

How to Get Complete Code Coverage With Cypress

Instrument your app, collect coverage from Cypress tests, and use the report to find meaningful gaps across frontend, component, and backend code.

By the ScreenshotNeo team4 October 202610 min read

Cypress code coverage requires two pieces: instrument the application so it records which statements, branches, functions, and lines execute, then collect those counters with @cypress/code-coverage. Cypress does not add instrumentation to your application automatically. After the tests run, inspect the report and add targeted tests for important uncovered behavior.

“Complete” coverage means you have deliberately included the source files and behaviors that matter to your project. A 100% number alone does not prove that tests contain useful assertions or would catch regressions.

This guide covers end-to-end (E2E), component, and optional backend coverage. Cypress’s code coverage guide is the primary reference for the workflow.

1. Decide what code belongs in the report

Write down the scope before configuring tools. Common scopes include frontend application source, component-tested source, Cypress specs themselves, and a Node.js backend. These do not all appear automatically in one report: each requires instrumentation and, where relevant, collection setup.

  • Exclude generated output, dependencies, and test files unless you have a specific reason to measure them.
  • Check that source maps lead reports back to original source files, rather than only bundled output.
  • Prioritize business rules, error handling, and conditional behavior when deciding which gaps to test.

2. Instrument the application code

Choose an instrumentation route that fits your build system. These approaches are alternatives, not steps to combine indiscriminately.

Approach Use it when Key consideration
NYC instrumentation You want a separate step that writes instrumented files. Point the app build or server at the instrumented output.
Babel with Istanbul Your application is transpiled with Babel. Enable it for Cypress runs to avoid duplicate instrumentation in other test pipelines.
Vite plugin Your app is built with Vite. Set include, exclude, and extension patterns to match actual source files.

Option A: instrument a source directory with NYC

NYC can instrument a source directory into a separate output directory. The following command is the Cypress guide’s example; adapt the paths to your project:

npx nyc instrument --compact=false src instrumented

The --compact=false option makes generated code easier to inspect. Ensure your test build actually serves or imports the instrumented output; merely creating it does not instrument the application currently under test.

Option B: instrument Babel output only for Cypress

Install the Istanbul Babel plugin if it is not already part of your build:

npm install --save-dev babel-plugin-istanbul

Configure Istanbul under a Cypress-specific Babel environment. Keep your existing presets and plugins as needed:

{
  "presets": ["@babel/preset-react"],
  "plugins": ["@babel/plugin-proposal-class-properties"],
  "env": {
    "cypress": {
      "plugins": ["istanbul"]
    }
  }
}

Set BABEL_ENV=cypress when launching Cypress so other tools such as Jest do not also receive Cypress-only instrumentation:

{
  "scripts": {
    "test:e2e": "BABEL_ENV=cypress cypress run",
    "test:ct": "BABEL_ENV=cypress cypress run --component"
  }
}

On platforms where setting environment variables inline this way is not supported by the shell, use your project’s cross-platform environment-variable approach. Preserve the intended value, cypress.

Option C: instrument a Vite app

Install vite-plugin-istanbul:

npm install --save-dev vite-plugin-istanbul

Add it to the Vite configuration and tailor the paths and extensions to your project. For Vue single-file components include .vue; TypeScript sources may need .ts.

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import istanbul from 'vite-plugin-istanbul'

export default defineConfig({
  plugins: [
    vue(),
    istanbul({
      include: 'src/*',
      exclude: ['node_modules', 'test/'],
      extension: ['.js', '.ts', '.vue'],
      requireEnv: true,
    }),
  ],
})

With requireEnv: true, enable instrumentation explicitly when running the coverage build, for example with VITE_COVERAGE=true. The plugin’s configuration and current compatibility details can change; consult its installed documentation if the option shape differs in your version. Once Vite instruments the application, browser coverage counters are exposed as window.__coverage__.

3. Install and register the Cypress coverage collector

Install the plugin as a development dependency, then import its support module and register its Node task. The examples below use the current v4 configuration model. The plugin’s v4 migration notes state that Cypress 15.10 deprecated Cypress.env() and moves plugin configuration from env to expose; v4 requires Cypress 15.10 or later. Check the plugin repository for version-specific setup.

npm install --save-dev @cypress/code-coverage

For E2E tests, import support in the E2E support file:

// cypress/support/e2e.js
import '@cypress/code-coverage/support'

Register the task in the Cypress configuration and return the configuration object:

// cypress.config.js
const { defineConfig } = require('cypress')
const registerCodeCoverageTasks = require('@cypress/code-coverage/task')

module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      registerCodeCoverageTasks(on, config)
      return config
    },
  },
})

In a TypeScript configuration, use the corresponding imports:

import { defineConfig } from 'cypress'
import registerCodeCoverageTasks from '@cypress/code-coverage/task'

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      registerCodeCoverageTasks(on, config)
      return config
    },
  },
})

If you change configuration values in setupNodeEvents, return the resulting config so Cypress receives them. If the package or Cypress version you use has different module or configuration requirements, follow that version’s instructions.

4. Configure component tests separately

An E2E support import does not collect component-test coverage. Import the plugin in the component support file as well, and make sure the component configuration registers the coverage task. Vite projects use the instrumentation plugin in the component dev-server build; Webpack projects need Istanbul in the component bundling or transpilation rules.

// cypress/support/component.js
import '@cypress/code-coverage/support'

Keep both testing types in scope only if that is what you want the report to represent. If E2E and component runs write into the same coverage output, run them in the intended sequence and preserve the collected artifacts so the report includes the data you expect.

5. Add backend coverage when the server is in scope

Browser instrumentation measures code executed in the browser. It does not automatically measure server-side application code. For a Node backend, start the server under NYC and expose its coverage object so the plugin can fetch and merge it.

{
  "scripts": {
    "start": "node server",
    "start:coverage": "nyc --silent node server"
  }
}

For Express, register the plugin middleware in the instrumented server:

const express = require('express')
const app = express()

if (global.__coverage__) {
  require('@cypress/code-coverage/middleware/express')(app)
}

For other server frameworks, provide a GET /__coverage__ endpoint that returns the server’s global.__coverage__ object. Restrict that diagnostic endpoint to the test environment; it can expose internal coverage data and should not be made available on a public production server.

For v4, configure the endpoint using expose in the Cypress config:

const { defineConfig } = require('cypress')
const registerCodeCoverageTasks = require('@cypress/code-coverage/task')

module.exports = defineConfig({
  expose: {
    codeCoverage: {
      url: 'http://localhost:3000/__coverage__',
    },
  },
  e2e: {
    setupNodeEvents(on, config) {
      registerCodeCoverageTasks(on, config)
      return config
    },
  },
})

Older plugin versions may document this setting under env.codeCoverage. Do not combine old and new configuration examples; use the shape that matches the installed plugin version. See the migration notes.

6. Run tests and read the report

Run the relevant Cypress suite using the instrumentation-enabled build. The plugin saves raw coverage data under .nyc_output and generates an HTML report that can be opened at coverage/index.html. For a terminal summary or another reporter, run:

npx nyc report --reporter=text-summary

NYC supports other report formats; choose one that fits local review or your CI artifact workflow. Preserve the coverage directory as a CI build artifact if developers need to inspect reports after a job ends.

Review statements, branches, functions, and lines. A missed branch can point to a missing negative case, permission condition, boundary value, or error response. Add tests around uncovered behavior that matters, and assert the expected outcome rather than merely executing the path.

7. Make coverage meaningful

  1. Confirm scope. Verify the report includes intended first-party files and omits dependencies and generated bundles.
  2. Inspect the lowest-covered important files. Start with business-critical logic, security checks, data validation, and error handling.
  3. Read uncovered branches in context. Determine which input or state reaches each path.
  4. Add a focused test and an assertion. Check observable behavior, not only that a line ran.
  5. Review the report again. Confirm the intended file and branch changed.
  6. Set thresholds only after you understand your baseline. A threshold is a guardrail against regression, not a substitute for deciding which tests matter.

Cypress’s documentation illustrates that a real-world 100% result may require multiple test types. Treat 100% as a scoped project goal if useful, not as a universal quality target.

8. Source-code coverage and UI Coverage are different

Source-code coverage answers which instrumented lines, functions, statements, and branches executed. Cypress Cloud UI Coverage answers which interactive UI elements tests exercised, using Test Replay data. It requires a recorded Cloud run, Test Replay, Cypress v13 or later, and UI Coverage enabled for the organization; Cypress documents it as separate from standard Cloud plans. These measurements complement each other, but one does not replace the other. See the official UI Coverage overview.

Or skip the browser setup

If your task also needs a clean screenshot of a page for a visual test, bug report, or documentation, ScreenshotNeo is a website screenshot API and MCP server for developers. A GET request returns a PNG, JPEG, WebP, or PDF. For browser-based Cypress coverage you still need the instrumentation workflow above; ScreenshotNeo handles page capture.

One-call capture with cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request options and response details. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers indicate the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account for 1,000 screenshots a month, with no card.

Troubleshooting

Symptom Likely cause What to check
The report is empty or shows no files. The app was not instrumented, or the coverage support/task setup is missing. Inspect the served app for window.__coverage__; verify the support import, registered task, and returned config.
Only E2E coverage appears. The component support file does not import the plugin. Add the support import to component support and instrument the component dev-server build.
Coverage data exists but paths are bundled or unreadable. Source maps are missing or misconfigured. Enable source maps in the relevant build and confirm report paths map to original source.
Some files never appear. Include/exclude patterns or extensions omit them. Check Vite plugin patterns, file extensions, and whether tests actually import or load the source.
Jest reports duplicate instrumentation or counters. Istanbul is enabled globally across Jest and Cypress builds. Scope Babel Istanbul to the Cypress environment and set BABEL_ENV=cypress only for Cypress scripts.
Backend coverage is absent. The server is not instrumented, its endpoint is inaccessible, or the plugin URL is configured for the wrong version. Start the server with NYC, verify the test-only endpoint returns coverage data, and check expose versus legacy env.
Plugin setup fails on Cypress 15.10 or later. An older plugin configuration may rely on deprecated Cypress.env(). Check the installed plugin version and follow its v4 migration instructions; v4 requires Cypress 15.10 or newer.
Coverage percentage changes between runs unexpectedly. Different specs, app states, or backend requests ran, or stale artifacts were combined. Compare the test set and build inputs, and start CI jobs with the intended clean artifact state.

Performance, reliability, and cost

Instrumentation adds counters to application code and produces coverage artifacts. Keep it scoped to Cypress runs when it conflicts with other builds, and use a dedicated coverage build if production-like runs must remain uninstrumented. The cited Cypress guide provides setup rather than universal runtime or storage benchmarks, so measure the effect in your own pipeline if timing matters.

For reliable reports, use consistent test scope and build inputs, ensure source maps resolve, and preserve the generated coverage artifact from the same CI job as the test run. Treat missing data as a collection or instrumentation issue before interpreting a low percentage. NYC, Babel/Istanbul, the Vite instrumentation plugin, and the Cypress coverage plugin are configuration dependencies; keep their versions compatible with the Cypress and build tooling versions in the project. No special paid coverage service is required by the workflow described here.

FAQ

Does Cypress automatically instrument application source?

No. The application’s build or transpilation pipeline must add coverage counters before Cypress can collect them.

Can Cypress combine frontend and backend coverage?

Yes. Instrument the backend, expose its coverage object through the supported middleware or endpoint, and configure the plugin to fetch it so it can merge the data.

Does getting 100% coverage mean the tests are complete?

No. It means the selected instrumented code ran. Assertions and thoughtful test cases are still needed to check behavior.

Does E2E setup cover component tests too?

No. Component tests need the coverage support import in their own support file and instrumentation in their component build.

Primary references