CI pipeline is green, and your team members are sitting in a code review, wondering: what do some of those tests actually affect? A passing suite indicates nothing about whether your validation logic, error handlers, or permission checks ever ran. This might be the reason why your team ships a feature and weeks later defects start showing in production. Using Cypress, a frontend test automation tool for regression testing, is a popular solution here. This guide walks through setting up Cypress code coverage the right way. It covers instrumenting your build, registering the plugin, merging parallel CI runs, and inspecting the reports.
Coverage exposes what your tests actually skip. A suite can pass every run while discount logic, payment validation, or error fallback paths sit completely untouched.
Three steps make coverage work: instrument the build with Istanbul, register the plugin in cypress.config.js, and import the support file. Skip any of these and your reports come back empty.
The report tracks four metrics: statements, branches, functions, and lines. Branch coverage tends to catch the most, since a function can show full line coverage while one side of a conditional never runs.
Parallel CI runs need manual merging. Each worker has to save its .nyc_output directory as an artifact, then you combine everything with nyc merge before generating the final report. Skip this step and only one worker’s data survives.
This guide walks you through setting up Cypress code coverage the right way, so a passing pipeline actually means something 👇
What Is Code Coverage in Cypress?
Code coverage tracks which parts of your source code execute while your Cypress tests run. Picture it as a map showing exactly which routes your tests travel through the codebase, and which corners they never visit.
Here’s the key distinction: your Cypress report shows pass or fail. Coverage shows whether the test actually reached the logic that matters. A test can load a checkout page, click submit, and see a success message. It passes, but if it never triggered your discount calculation or payment validation, those sections stay untested behind a green checkmark.
Cypress can’t measure this alone. Your application first needs instrumentation, usually through Istanbul, which inserts counters around code blocks during the build. When Cypress interacts with your app, those counters record every hit. From there, the @cypress/code-coverage plugin collects the data and passes it to NYC for reporting.
This creates a three-part chain your setup depends on:
Instrumentation: Istanbul inserts counters into your test build
Execution: Cypress runs and triggers those counters
Collection: the plugin gathers the data and NYC turns it into a report
Skip the instrumentation step, and the rest of the chain has nothing to collect. That’s the single most common reason teams end up staring at an empty report.
Cypress Code Coverage vs UI Coverage
Code coverage and UI Coverage answer different questions, and mixing them up leads to a false sense of confidence in either direction.
Approach
What it measures
Instrumentation required
Best for
Code coverage
Executed statements, branches, functions, and lines
Yes, via Istanbul
Finding untested logic paths, conditionals, and error handlers
UI Coverage
Interactive elements and application states exercised by tests
No
Finding buttons, forms, and screens your test suite never touches
Code coverage reveals application logic your tests never executed. UI Coverage reveals interface elements your tests never touched, buttons never clicked, form fields never filled, states never triggered. A test suite can reach 90% code coverage while entire sections of the UI stay untested, since a single code path often backs several different screens or interactions. The two metrics answer separate questions, and reading one as a stand-in for the other hides real gaps in either the logic or the interface.
Why Set Up Code Coverage for Cypress Tests
Coverage exposes what your regression suite quietly ignores. Tests might exercise the happy path through account creation without ever hitting the check that blocks duplicate emails, or a pricing module might only get tested with standard discounts, leaving promo codes and bulk pricing edge cases completely untouched.
Once coverage data exists, it turns those blind spots into something your team can act on instead of something everyone just assumes is fine. Here’s where that visibility pays off in practice:
A pull request can show whether tests passed and whether the new code was actually reached, making it harder to merge a test that opens the right page without touching the logic it’s supposed to verify
If your team is running 200 browser tests and CI time keeps growing, coverage often reveals duplication, like ten different scenarios exercising the same login flow
With that visibility, expensive E2E tests can get swapped for faster component or API tests, without losing coverage of the same code paths
Code coverage is not typically measured with E2E tests. What Cypress is offering is a feature that provides a list of UI elements that were not interacted with by any tests. It can be valuable if you have an un-manageable quantity of tests that you don't understand very well and want to see what isn't being "covered."
Getting coverage working means coordinating your build tool, test framework, and reporting plugin, and the order matters more than it might seem. Miss a step, like forgetting to instrument your build before running tests, and you’ll end up debugging an empty report instead of reading a useful one.
Before starting, make sure Node.js and npm are installed, you have write access to your repo’s build config (Vite, Webpack, or CRA setup), and permission to edit cypress.config.js. Here’s the sequence:
Install the plugin. Run npm install --save-dev @cypress/code-coverage from your project root. The plugin ships with NYC bundled in, so a separate NYC install isn’t needed. The instrumentation package depends on your build pipeline:
# Vite
npm install --save-dev vite-plugin-istanbul
# Babel or Webpack
npm install --save-dev babel-plugin-istanbul
Instrument your application code. This step trips up most setups, since Cypress won’t instrument your source automatically. For Vite projects, add the plugin directly to your config:
Once this plugin runs, Cypress picks up coverage automatically from window.__coverage__ with no further wiring. For Webpack, use babel-plugin-istanbul in your Babel config instead. Create React App projects can pair react-app-rewired with Istanbul middleware. Whichever bundler your project uses, the test build needs coverage counters that never ship to production.
Register the plugin in Cypress config. Open cypress.config.js (or .ts) and register the coverage task inside setupNodeEvents, then return config at the end. Skipping that return breaks the setup silently.
Add the support file import. Add import '@cypress/code-coverage/support' to both cypress/support/e2e.ts and cypress/support/component.ts. Importing it only in e2e.ts wires up E2E coverage but leaves component test coverage uncollected.
Configure NYC. Create .nycrc or .nycrc.json at your project root. Set "all": true so every source file gets included, even ones never loaded during tests, and add "exclude" patterns for node_modules, test files, and generated code. Without this, the reported percentage looks artificially low.
Run Cypress and check the report. Execute npx cypress run from your terminal. Coverage data lands in .nyc_output/, and reports generate in the coverage/ directory. Open coverage/lcov-report/index.html in any browser for the interactive breakdown, color coded by how well each file is covered.
Setting Up Coverage for Component Tests
Component testing mounts individual UI pieces, like a button or a form, without loading your full application. Instrumentation follows the same principle here, but the configuration needs a small adjustment since components mount through a different runner than E2E tests do.
First, confirm your bundler instruments component code during the dev server build. If vite-plugin-istanbul is already registered for E2E, it typically covers component tests too. For Webpack setups, make sure babel-plugin-istanbul applies to your component source files, not just your app’s entry point, since component tests often bypass the normal bootstrap.
Register the plugin the same way you did for E2E: require('@cypress/code-coverage/task')(on, config) inside your component config. Then import the support file in cypress/support/component.js.
One catch: components that mock their dependencies produce misleading numbers. If you mount a component and stub its API client, the real client code never shows up in the report. That’s expected. The fix is combining results, running component and E2E tests together and merging their outputs, which the next section covers.
Testing gaps oftentimes intersect with requirements-related issues. aqua cloud, an AI-driven test and requirement management solution, connects test coverage directly to requirements and business logic. This way, your team sees which lines ran and whether every critical requirement actually has validation behind it. aqua’s AI Intelligence is grounded in your own project documentation through RAG, so instead of generic suggestions, it flags untested requirements and generates test cases that match your team’s actual terminology and workflows. You get real-time dashboards tracking requirements coverage, automated gap detection, and full traceability from business need to test execution. More than that, aqua has native integrations with Jira for bidirectional sync, Azure DevOps, Confluence, 12+ more tools, and REST API, so coverage data lives where your team already works.
Each worker should save its coverage JSON under a unique name rather than the default .nyc_output/ path, since parallel jobs writing to the same directory overwrite each other’s data. Once every worker finishes, download all the named artifacts into one folder, then merge and report from a clean output directory:
The –temp-dir flag tells the report step exactly which merged output to read, removing any ambiguity about the data source.
Most CI platforms, including GitHub Actions, GitLab CI, CircleCI, and Jenkins, support artifact collection. Configure each worker to save its .nyc_output/ directory as an artifact, and make sure each one writes to a uniquely named path so workers don’t overwrite each other. Once every worker finishes, a final reporting job downloads all the artifacts into a shared .nyc_output/ directory. From there:
Run nyc merge .nyc_output merged-coverage.json to combine the raw data
Run nyc report --reporter=html --reporter=text --reporter=lcov to generate the unified report
The same logic applies when combining component and E2E coverage. Run them as separate jobs, preserve both output directories, merge them in a third job, and you get one report reflecting both isolated component logic and full browser scenarios.
Understanding the Coverage Report
A Cypress coverage report breaks down into four metrics rather than a single percentage, and each one highlights a different kind of gap in your test suite. Reading them together gives a far more accurate picture than glancing at the overall number.
Before diving into the file-by-file breakdown, it helps to know what each metric actually measures:
Statements: individual executable instructions
Branches: whether both sides of a conditional (if/else, switch, ternary) were reached
Functions: whether each declared function was called at least once
Lines: which source lines executed, though one line can hold multiple statements
Branch coverage usually reveals the most. A function can show full line coverage while one side of an important conditional never runs once.
The HTML report shows a color-coded file tree: green for strong coverage, yellow for partial, red for serious gaps. Click into any file and you’ll see annotated source code, with execution counts in the margin showing how many times each line ran. A line hit 247 times probably lives inside a loop, while a line hit once might be an edge-case handler nobody’s testing directly.
Pay attention to one setting in particular: by default, NYC only reports files that were actually loaded during tests. That default can inflate your percentage, since a feature nobody tested at all simply disappears from the calculation rather than counting against it. Set “all”: true alongside an explicit include pattern so every matching source file appears, even ones the tests never touched, at 0% coverage.
Cypress as a tool will help validate E2E user workflows but not necessarily the code coverage. There are other tools like SonarQube/Codecov which can help in evaluating the code coverage. We use different tools for different purpose. And you might not be able to find an all-in-one tool.
A coverage report only helps if something acts on it. NYC supports failing a build when coverage drops below a set threshold, so your pipeline enforces a minimum bar instead of just displaying a number nobody checks.
Branch coverage usually needs a slightly lower threshold than statements or lines, since conditional logic multiplies the number of paths a test suite has to hit. Setting branches too high too early tends to block merges over edge cases nobody’s reached yet, not real regressions.
Common Setup Issues and How to Fix Them
Even a careful setup runs into snags along the way, and most of them trace back to a handful of recurring causes. Knowing what to check first saves a lot of time spent guessing.
Here are the issues that come up most often, and what to check first when they do:
Empty or zero-percent reports: usually means your application isn’t instrumented. Check that Istanbul or Babel instrumentation runs for your test build, and confirm NODE_ENV isn’t set to production, which disables dev-only plugins.
Coverage only reflects one worker: parallel runs are overwriting each other. Save each worker’s output to a uniquely named directory, then merge with NYC before generating the final report.
Coverage attributed to the wrong files: broken source maps are the culprit. Enable sourcemap: true in your build config and confirm .map files generate alongside your test build.
Plugin tasks not registered: you’re missing require('@cypress/code-coverage/task')(on, config) inside setupNodeEvents. Without it, Cypress can’t save or retrieve coverage data.
NYC configuration ignored: check .nycrc for trailing commas or bad exclude patterns. Run npx nyc report manually to confirm your settings are actually being applied.
Coverage drops after a refactor: this often just means old code was only “tested” incidentally. Review the diff, then decide whether the affected area needs new tests.
Full-stack coverage config not picked up: in @cypress/code-coverage v4, backend coverage configuration moved from env.codeCoverage to expose.codeCoverage, following the removal of the older Cypress.env() workflow in Cypress 16. Any full-stack setup still referencing env.codeCoverage needs updating for current plugin versions.
Best Practices for Using Code Coverage in Cypress
Getting coverage running is only half the job. Building it into how your team works day to day, from PR reviews to sprint planning, is what turns raw numbers into something worth acting on.
The practices below cover the areas where teams tend to get the most value with the least ongoing effort:
Don’t treat coverage as proof of quality. A test can execute a line without ever asserting its behavior. Coverage tells you what ran, not how well it was verified.
Set different thresholds for different code. Payment logic and authentication deserve strict branch coverage. Configuration wrappers and generated files can carry looser thresholds. NYC supports per-directory thresholds, so use them.
Combine coverage across test types. Merge component, E2E, and API test coverage into one report so your Cypress suite isn’t penalized for missing every internal utility.
Track coverage deltas in pull requests. Tools like Codecov or Coveralls can comment directly on PRs, making it obvious when new code ships without tests.
Use coverage to trim redundant tests. When multiple E2E tests hit the same code paths, swap some for faster component or API tests without losing confidence.
Document your exclusions. Generated code and vendor adapters clutter reports, but exclusions without explanation tend to hide real gaps over time.
Coverage reports give your team valuable insights into code. However, they don’t tell you whether your testing strategy covers what the business actually needs. aqua cloud, an AI-powered test and requirement management platform, addresses exactly that by mapping test execution back to requirements and user stories. This way, nothing critical slips through unnoticed. The AI Intelligence is trained specifically for QA and grounded in your project’s own documentation. It flags coverage gaps against your team’s actual specifications and generates test cases that match real project context instead of generic templates. Real-time dashboards show exactly which requirements still lack validation, and with 10+ native automation integrations, including Jenkins, JMeter, SoapUI, Ranorex, Database Oracle and MSSQL, PowerShell, UnixShell, and Capture. With all that, coverage data flows straight into the tools your team already runs.
Achieve 100% strategic test coverage with AI-powered requirement traceability
Setting up Cypress code coverage gives your team a clear picture of what’s actually being tested, instead of a binary pass or fail. It takes real setup work: instrumentation, plugin configuration, and careful merging across parallel runs. The payoff is a regression suite your team can trust, one that flags gaps in validation logic and error handling before they reach production.
Does Cypress support code coverage out of the box?
No. Cypress needs the @cypress/code-coverage plugin and an instrumented application build. Configure Istanbul through Vite, Webpack, or Babel to insert coverage counters, then install the plugin to collect and report that data.
Can I combine code coverage from tests run in parallel?
Yes. Save the .nyc_output/ directory from each CI worker as an artifact, download them into a shared directory, then run nyc merge followed by nyc report. Skip the merge and only one worker’s results show up.
What metrics does a Cypress code coverage report show?
Four: statements, branches, functions, and lines. Branch coverage tends to be the most useful for spotting untested logic, since it catches conditionals where only one path ever executes.
Does 100% coverage mean my application is fully tested?
No. Coverage only shows that a line executed, not that a test verified its behavior. A scenario can trigger a function without asserting anything meaningful, so high coverage numbers still need real test design behind them.
How do I exclude test files and node_modules from the coverage report?
Add an "exclude" array to your .nycrc file listing patterns like node_modules/**, **/*.test.js, and config files. Without exclusions, the reported percentage drops artificially since irrelevant files count against the total.
Why does my coverage report show 0% for files I know are tested?
This usually points to a source map issue or a file your bundler never instrumented. Confirm sourcemap: true is set in your build config and that the file falls under your bundler’s Istanbul or Babel instrumentation rules.
Join our community of enthusiastic experts! Get new posts from the aqua blog directly in your inbox. QA trends, community discussion overviews, insightful tips — you’ll love it!
We're committed to your privacy. Aqua uses the information you provide to us to contact you about our relevant content, products, and services. You may unsubscribe from these communications at any time. For more information, check out our Privacy policy.
X
🤖 Exciting new updates to aqua AI Assistant are now available! 🎉