Skip to content

Playwright

@tracecov/playwright records coverage from Playwright's API testing — the request fixture and any APIRequestContext — and merges results from all workers into one report.

Requires @playwright/test 1.30 or later.

npm install -D @tracecov/playwright

Setup

Add the reporter to playwright.config.ts:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: { baseURL: 'https://api.example.com' },
  reporter: [
    ['list'],
    ['@tracecov/playwright/reporter', {
      schema: 'openapi.json',
      report: { html: 'coverage.html' },
      thresholds: { operations: 80 },
    }],
  ],
});

Import test from @tracecov/playwright instead of @playwright/test. Its request fixture is instrumented, so every call it makes is recorded — no changes to the test body:

import { expect } from '@playwright/test';
import { test } from '@tracecov/playwright';

test('lists items', async ({ request }) => {
  const response = await request.get('/items?limit=10');
  expect(response.ok()).toBeTruthy();
});

Headers set with extraHTTPHeaders in use are recorded with every call, and a header passed per call overrides them. Authorization never counts toward coverage: OpenAPI ignores a header parameter by that name.

Requests are recorded whether the test passes or fails. When the run ends, the reporter writes coverage.html. A thresholds breach prints one line per metric and fails the run:

tracecov: Operations       50.0% / 80% required

The reporter takes the same report and thresholds options as the Vitest and Jest plugins, and reports the same six metrics — operations, parameters, keywords, examples, responses, response keywords. See Coverage Concepts for what each measures.

Instrumenting a context you build yourself

Wrap the context with wrapRequest, and call flush() when the test ends so the reporter picks up its requests. flush() saves everything the worker recorded so far, so calling it after every test counts each request once. The worker records against the schema the reporter loads, so keep the reporter in your config:

import { request, test } from '@playwright/test';
import { createDirRecorder, interactionsDir, wrapRequest } from '@tracecov/playwright';

test('lists items with a custom context', async () => {
  const extraHTTPHeaders = { 'X-Tenant': 'acme' };
  const context = await request.newContext({ baseURL: 'https://api.example.com', extraHTTPHeaders });
  const { recorder, flush } = createDirRecorder(interactionsDir());
  wrapRequest(context, recorder, { extraHTTPHeaders });
  try {
    await context.get('/items?limit=10');
  } finally {
    flush();
    await context.dispose();
  }
});

Pass the context's extraHTTPHeaders to wrapRequest as well: Playwright sends them, but gives no way to read them back from the context.

Wrapping an already-wrapped context switches it to the new recorder; requests are not counted twice.

Concurrent runs

If two Playwright runs share a working directory, give each its own TRACECOV_PLAYWRIGHT_DIR:

TRACECOV_PLAYWRIGHT_DIR=/tmp/tracecov-run-1 npx playwright test

Not captured

  • Redirects. The final resolved URL is recorded, not the original one.
  • Network errors. Requests that fail without a response are not recorded.