JavaScript Integration
@tracecov/core is a coverage engine for Node.js: give it your OpenAPI specification and the HTTP interactions your tests make, and it reports which operations, parameters, and JSON Schema keywords you exercised. Feed it interactions with record() from your HTTP client's interceptor or middleware, or import recorded traffic from HAR, Postman, or VCR files.
Installation
npm install @tracecov/core
# or
yarn add @tracecov/core
# or
pnpm add @tracecov/core
Requirements:
- Node.js ≥ 18
- ESM project (
"type": "module"inpackage.json) or TypeScript with"moduleResolution": "nodenext"or"bundler"intsconfig.json - CommonJS projects: use
const { CoverageMap } = await import('@tracecov/core')
@tracecov/core is a native Node.js addon and does not run in browser bundles.
Recording interactions
Load your spec, then record each request/response pair:
import { CoverageMap } from '@tracecov/core';
const coverage = CoverageMap.fromPath('openapi.json');
const start = Date.now();
const res = await fetch('https://api.example.com/users?limit=10');
coverage.record(
{ method: 'GET', url: 'https://api.example.com/users?limit=10', headers: {} },
{ statusCode: res.status, elapsed: (Date.now() - start) / 1000 },
);
fromPath reads JSON or YAML; fromDict takes a parsed object; fromUrl fetches over HTTP (await it).
Call record() wherever you make requests — most clients let you do it once in an interceptor or middleware. Pass the request body as a Buffer or string in body to also measure request-body keyword coverage. Annotate a network failure with recordError(method, url, message). For Axios, @tracecov/axios wires this up for you.
Automatic capture with Axios
@tracecov/axios attaches interceptors that record every request an Axios client makes — success, 4xx/5xx, and network failures — including request bodies, so you never call record() by hand:
npm install @tracecov/axios
import { CoverageMap } from '@tracecov/core';
import { instrumentAxios } from '@tracecov/axios';
import axios from 'axios';
const coverage = CoverageMap.fromPath('openapi.json');
const client = axios.create({ baseURL: 'https://api.example.com' });
instrumentAxios(client, coverage);
// Every call through `client` is now recorded.
await client.get('/users', { params: { limit: 10 } });
coverage.saveHtmlReport({ outputFile: 'coverage.html' });
instrumentAxios records the URL Axios actually sends plus the request body and its media type. If recording ever throws it warns and continues; pass { onError } to handle it yourself.
Automatic capture with fetch
@tracecov/fetch wraps fetch so every call is recorded — no record() by hand. Patch the global fetch (returns a restore function), or wrap a specific one:
npm install @tracecov/fetch
import { CoverageMap } from '@tracecov/core';
import { instrumentFetch } from '@tracecov/fetch';
const coverage = CoverageMap.fromPath('openapi.json');
const restore = instrumentFetch(coverage); // patches globalThis.fetch
await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ name: 'a' }),
});
restore(); // undo the patch, e.g. in afterAll
coverage.saveHtmlReport({ outputFile: 'coverage.html' });
Don't want to touch the global? wrapFetch(fetch, coverage) returns an instrumented fetch you call directly. Both accept the same { onError } option.
Express
@tracecov/express records coverage from the server side: one call captures every request the app handles — supertest, a real HTTP client, or a browser — so it works with the in-process test style that never touches a client you can instrument. Works on Express 4 and 5.
npm install @tracecov/express
import express from 'express';
import request from 'supertest';
import { CoverageMap } from '@tracecov/core';
import { instrumentExpress } from '@tracecov/express';
const app = express();
app.use(express.json());
app.get('/items', (req, res) => res.json([]));
const coverage = CoverageMap.fromPath('openapi.json');
instrumentExpress(app, coverage);
await request(app).get('/items?limit=10'); // recorded
coverage.saveHtmlReport({ outputFile: 'coverage.html' });
It hooks the app's request dispatch, so it captures whatever drives the app — supertest, fetch against a running server, or a browser — including request bodies. Pass { onError } to handle recording failures yourself.
Vitest
Vitest runs each test file in its own worker process, so a single CoverageMap never sees every file's requests. @tracecov/vitest gives each worker a map, merges them in the reporter, and writes one report for the whole run — from one plugin line and one setup line. It's client-agnostic: feed it with @tracecov/fetch, @tracecov/axios, or record() by hand.
npm install -D @tracecov/vitest
Add the plugin to vitest.config.ts:
import { tracecov } from '@tracecov/vitest';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [
tracecov({
schema: 'openapi.json',
setup: './tracecov.setup.ts',
report: { html: 'coverage.html' },
thresholds: { operations: 80 },
}),
],
});
Then create the setup file it points at — grab a coverage map and wire it to your client:
// tracecov.setup.ts
import { instrumentFetch } from '@tracecov/fetch';
import { collectCoverage } from '@tracecov/vitest/setup';
instrumentFetch(collectCoverage()); // or: instrumentAxios(client, collectCoverage())
Run your suite as usual. Every request your tests send is recorded, merged across workers, and written to the configured report(s) when the run ends. A thresholds breach sets a non-zero exit code, so CI fails when coverage drops.
report accepts html, json, and markdown output paths (plus title for HTML); thresholds takes any of operations, parameters, keywords, examples, responses. schema and setup are resolved from the working directory, or from root if you set it in your Vitest config. collectCoverage() throws a clear error if the plugin isn't configured.
Jest
@tracecov/jest does the same for Jest — each worker's fetch interactions are collected and merged into one report at the end of the run. It's self-contained CommonJS, so it needs no --experimental-vm-modules; works on Jest 29 and 30.
npm install -D @tracecov/jest
// jest.config.js
module.exports = {
setupFilesAfterEnv: ['./tracecov.setup.js'],
reporters: [
'default',
['@tracecov/jest/reporter', { schema: 'openapi.json', report: { html: 'coverage.html' } }],
],
};
// tracecov.setup.js
const { collectCoverage, instrumentFetch } = require('@tracecov/jest');
instrumentFetch(collectCoverage());
Same report/thresholds options as the Vitest plugin. The setup file captures fetch; the reporter merges every worker's interactions and a thresholds breach fails the run.
Playwright
@tracecov/playwright records coverage from Playwright's API testing — the request fixture and any APIRequestContext. Playwright runs test files in worker processes, so workers buffer their interactions to a shared directory and the reporter merges them into one report in the main process.
npm install -D @tracecov/playwright
Add the reporter to playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [
['list'],
['@tracecov/playwright/reporter', {
schema: 'openapi.json',
report: { html: 'coverage.html' },
thresholds: { operations: 80 },
}],
],
});
Then import test from @tracecov/playwright instead of @playwright/test — its request fixture is instrumented, so every call it makes is recorded:
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();
});
Same report/thresholds options as the Vitest and Jest plugins. Requests are recorded whether the test passes or fails, and a thresholds breach fails the run.
To instrument a context you build yourself, wrap it with wrapRequest(context, recorder). Re-wrapping an already-wrapped context repoints it at the new recorder rather than double-counting.
Two things it does not capture:
- Context-level
extraHTTPHeaders. Playwright merges those in internally and exposes no getter, so headers set on the context (auth tokens, for example) do not count toward header-parameter coverage. Pass them per call to have them recorded. - Redirects. The final resolved URL is recorded, not the original one.
Workers and the reporter find each other through a directory derived from the working directory. If two runs share a working directory, set TRACECOV_PLAYWRIGHT_DIR to give each its own.
Writing a report
coverage.saveHtmlReport({ outputFile: 'coverage.html' });
generateHtmlReport / generateJsonReport / generateMarkdownReport / generateTextReport return a string; saveHtmlReport / saveJsonReport / saveMarkdownReport write a file. See JSON Report Format for the machine-readable schema.
Enforcing thresholds
const violations = coverage.checkThresholds({ operations: 80, keywords: 70 });
for (const v of violations) {
console.error(`${v.dimension}: ${v.actual.toFixed(1)}% < ${v.minimum}%`);
}
Each violation reports the dimension, its actual percentage, and the minimum required. Exit non-zero when violations is non-empty to fail your CI.
Importing recorded traffic
Professional Edition
HAR and Postman import require TraceCov Professional.
Already have traffic captured? Analyse it without running new tests:
import fs from 'node:fs';
import { CoverageMap } from '@tracecov/core';
const coverage = CoverageMap.fromPath('openapi.json');
coverage.recordFromHar(JSON.parse(fs.readFileSync('traffic.har', 'utf-8')));
coverage.saveHtmlReport({ outputFile: 'coverage.html' });
recordFromPostman and recordFromVcr take a parsed Postman collection (v1.0, v2.0, v2.1) or VCR cassette the same way. HAR v1.2 and v1.3 are supported (Chrome DevTools, Firefox, Postman, and other standard exporters).
Full method-by-method documentation is in the API reference.