Learn Playwright - Test Coverage & Performance
Episode 11 of 23

Learn Playwright - Test Coverage & Performance

This episode covers how to measure unit, integration, and functional test coverage, digging into performance insights with tracing and browser metrics, measuring page load and responsiveness, and introducing snapshot testing to detect visual changes.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

How much of the application is really tested? And how fast does the page actually load? This episode 11 addresses two questions that often get skipped while teams are busy adding tests: coverage — making sure all layers are tested — and performance — making sure the application isn't just correct, but also fast.

Both share the same tools: tracing and browser metrics that can be captured directly from Playwright. You'll learn to measure how broad your suite's coverage is, read page timing the way real users experience it, and create visual snapshots that catch unexpected changes in the future.

Measuring Coverage for Different Test Types

A Coverage Map Based on the Test Pyramid

The test pyramid provides direction: many unit tests at the base, some integration tests in the middle, a few end-to-end tests at the top. To measure coverage comprehensively, you need to monitor those three layers separately:

  • Unit coverage: the percentage of code lines and branches executed by unit tests.
  • Integration coverage: interactions between modules, services, and databases.
  • Functional/end-to-end coverage: complete business flows through the interface.

Manual End-to-End Coverage

Playwright doesn't automatically count application line coverage — that's the job of tools at the unit layer. For the end-to-end layer, coverage is measured semantically: an application feature map where each feature maps to a set of tests. A simple example:

Feature-to-test map
login feature → tests/login.spec.ts
checkout feature → tests/checkout.spec.ts
dashboard feature → tests/dashboard.spec.ts

With this map, you can see which features have no tests at all. Healthy end-to-end coverage isn't about line percentages, but about every important business path having its own protection.

Measuring Unit Coverage with C8 or V8

For the unit layer, use standard coverage tools like Istanbul or V8 coverage on your unit test runner. Integrate the results into CI so coverage drops are immediately visible. Remember: coverage numbers are just a signal — what matters more is that edge cases and critical flows are genuinely tested.

Performance Insights with Tracing and Browser Performance

Capturing Timing with the Performance API

Modern browsers store timing for every page, which you can read directly from the Playwright context:

JSRead performance timing
import { test, expect } from '@playwright/test';
 
test('measure page load', async ({ page }) => {
  await page.goto('/home');
  const timing = await page.evaluate(() => {
    const { navigationStart, loadEventEnd, domContentLoadedEventEnd } = performance.timing;
    return {
      loadMs: loadEventEnd - navigationStart,
      domReadyMs: domContentLoadedEventEnd - navigationStart,
    };
  });
  console.log('load duration:', timing.loadMs);
});

page.evaluate(() => performance.timing) executes JavaScript inside the page and returns the result to the test. With this, you can measure load duration for every test and flag regressions when the numbers balloon.

Core Web Vitals from the Context

For user-facing metrics like Largest Contentful Paint, Playwright makes it easy to capture data via PerformanceObserver:

JSGet LCP from the page
const lcp = await page.evaluate(async () => {
  return await new Promise((resolve) => {
    new PerformanceObserver((list) => {
      const entries = list.getEntries();
      resolve(entries.at(-1)?.startTime ?? 0);
    }).observe({ type: 'largest-contentful-paint', buffered: true });
  });
});
console.log('LCP (ms):', lcp);

The PerformanceObserver captures LCP values in real time. By collecting these metrics across several tests, you build a performance data bank that can be compared across releases.

Tracing to Investigate Bottlenecks

A trace isn't only for debugging tests — it's also for understanding application behavior:

JSEnable tracing for investigation
test.use({ trace: 'on' });
 
test('investigate checkout performance', async ({ page }) => {
  await page.goto('/checkout');
  // actions ...
});

test.use({ trace: 'on' }) creates a trace for this test. In the trace viewer, the Network and Timing sections show how long each request took — a good starting point for finding slow APIs or large resources.

Measuring Page Load and Responsiveness

Monitoring Interaction Time

Beyond loading, measure how quickly the page responds to an interaction — that's the essence of responsiveness:

JSMeasure click-to-response duration
const start = Date.now();
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Saved')).toBeVisible();
console.log('responsiveness (ms):', Date.now() - start);

By measuring the span from click until the resulting element appears, you get an end-to-end picture of how responsive a single action is. Make this a helper so all tests use the same metric.

Setting Thresholds

Metrics without thresholds are just numbers. Set reasonable values and let tests monitor them:

JSPerformance threshold assertion
expect(lcp).toBeLessThan(2500);

expect(lcp).toBeLessThan(2500) turns a measurement into an assertion. If LCP exceeds 2.5 seconds, the test fails — a performance regression is caught before reaching production.

Snapshot Testing and Visual Change Detection

The Snapshot Concept

Snapshot testing captures a baseline image of the page and compares it with the latest version:

JSPage snapshot
await expect(page).toHaveScreenshot('home.png');

On the first run, page.toHaveScreenshot('home.png') stores a baseline image; on subsequent runs it compares against the current image. Any pixel difference fails the test, indicating a visual change that hasn't been approved.

Managing Baselines in the Repo

Baseline snapshots are stored in a __screenshots__ folder or per your configuration, and must be committed to the repository. When a visual change is intentional, update the baseline with npx playwright test --update-snapshots. The full details of visual regression, including thresholding and layout drift, will be covered in episode 16.

Update snapshot baselines
npx playwright test --update-snapshots

Closing

Episode 11 equipped you with two monitoring lenses: coverage that maps every feature to tests at all layers of the pyramid, and performance measured directly from within the browser via the timing API, Core Web Vitals, and tracing. Snapshot testing starts introducing you to automatic visual change detection.

Key takeaways:

  • End-to-end coverage is measured by mapping features to tests, not just line percentages.
  • page.evaluate captures timing and Core Web Vitals from inside the page.
  • Traces help find slow APIs and resources that cause bottlenecks.
  • Turn metrics into assertions with thresholds so regressions are detected.
  • The toHaveScreenshot snapshot is the first alarm for visual changes.

In the next episode we'll discuss API testing and network interception — mocking API responses with route interception, testing offline mode and network failures, validating backend behavior through UI flows, and integration with API test tools.

Learn Playwright - Test Coverage & Performance | Learn Playwright