In modern software development, moving fast and breaking things might work for startups, but for most teams, confidence in code quality is non-negotiable. One of the best tools to build that confidence is end-to-end (E2E) test coverage. The goal is to not only verify the application’s behavior but also to measure which parts of the code are executed during tests.
In this article, we’ll dive into why E2E test coverage matters, how you can implement it using Playwright and Istanbul, and what a coverage report looks like in Coveralls.
Why Does E2E Coverage Matter?
1. It Reflects the Real User Experience
E2E tests don’t care how your app is built internally. They care about how it behaves. This makes them an excellent safety net for catching issues that lower-level tests miss—especially regressions introduced by UI or API changes.
2. Ensures Critical Flows Are Tested
By measuring E2E coverage, you can identify whether essential user flows (like login, checkout, or form submissions) are adequately tested. Without this, you risk shipping features that break under real-world conditions.
3. Prevents Regressions
Teams often make changes with confidence that “unit tests passed.” But what if the component’s integration with the broader system is broken? E2E tests act as regression tests, protecting core workflows.
4. Bridges the Gap Between Dev and QA
When integrated into CI/CD pipelines, E2E tests provide immediate feedback to both developers and testers. This shared visibility promotes better collaboration and faster issue resolution.
Code Coverage: Measuring What Matters
Code coverage tells us what parts of the codebase are being executed during tests. With tools like Istanbul integrated with Playwright, we can now measure E2E test coverage, not just at the unit level.
Why is this important?
- It highlights untested areas of the UI and logic.
- It helps eliminate false confidence (i.e., assuming something is tested just because a test exists).
- It gives product owners and testers quantifiable insights into testing depth.
The Tools: Istanbul + Playwright
With modern tools like Playwright (for browser automation) and Istanbul (for code coverage instrumentation), it’s easier than ever to implement E2E coverage tracking.
You can now:
- Instrument your React or JavaScript application using
babel-plugin-istanbul
. - Run automated browser tests via Playwright.
- Collect real-time coverage data during test execution.
- Generate visual reports showing exactly which lines, functions, and branches were covered.
This means your coverage report reflects what real users would see and do—making it far more relevant than unit test coverage alone.
How to integrate it to your project
Instrument your React app with babel-plugin-istanbul
///config-overrides.js
if (process.env.USE_BABEL_PLUGIN_ISTANBUL) {
console.log('Enabling babel-plugin-istanbul for code coverage');
babelOptions.plugins.push('istanbul');
}
Then run your app open the terminal:
$ USE_BABEL_PLUGIN_ISTANBUL=1 npm start
Add the event listener to collect the coverage:
///baseFixtures.ts
await context.addInitScript(() =>
window.addEventListener('beforeunload', () =>
(window as any).collectIstanbulCoverage(
JSON.stringify((window as any).__coverage__)
)
)
);
// Expose function to browser context for collecting coverage data
await context.exposeFunction(
"collectIstanbulCoverage",
(coverageJSON: string) => {
if (coverageJSON) {
// Write coverage data to a unique file
fs.writeFileSync(
path.join(
istanbulCLIOutput,
`playwright_coverage_${generateUUID()}.json`
),
coverageJSON
);
}
}
);
This snippet ensures the coverage object is captured just before the browser closes the page.
Pro Tip
If you want to see live the function coverage for each file to your console of the browser use the following script:
Object.entries(__coverage__).forEach(([file, data]) => {
console.log(`\n📁 File: ${file}`);
console.log(“🧠 Function coverage:”);
const fnMap = data.fnMap;
const fCounts = data.f;
Object.entries(fnMap).forEach(([fnId, fnMeta]) => {
const name = fnMeta.name && fnMeta.name !== “(anonymous)” ? fnMeta.name : “(anonymous)”;
const count = fCounts[fnId];
const loc = `Start Line:(${fnMeta.loc.start.line})`;
console.log(` 🔹 ${name} ${loc} → called ${count} time(s)`); }); });
After running the pipeline you can find html report or use Coveralls to have a details report like this:

With red color are the uncovered lines of your React app and ofcourse you will get a percentage for entire app or for a specific file.
Common Objections (and Why They Don’t Hold Up)
“E2E tests are slow and flaky.”
Yes, they can be. But with proper test isolation, setup/teardown strategies, and modern frameworks like Playwright, flakiness is largely avoidable.
“We already have unit tests.”
That’s great! But unit tests can’t catch UI bugs, integration failures, or broken user flows. They’re necessary, but not sufficient.
- Playwright ensures realistic browser interactions.
- Istanbul ensures you measure exactly what got executed.
- Combined, they give you real-world test coverage metrics.
Conclusion
As testers, it’s our job to champion the processes and tools that give the clearest picture of application health. And when it comes to mimicking user behavior and validating entire workflows, nothing beats well-instrumented E2E tests.
- E2E code coverage fills a critical gap in our testing strategy.
- Playwright and Istanbul form a powerful combination for achieving this.
- Instrumentation, collection, and reporting are the core steps.
- This approach significantly enhances confidence in our E2E test suites.
But need always to remember…coverage is a metric, not a guarantee of quality!
Full example : https://github.com/jpourdanis/playwright-test-coverage