playwright-checkpoint
v0.3.0
Published
Structured page snapshots for Playwright — screenshots, accessibility, web vitals, and auto-generated documentation from your e2e tests
Maintainers
Readme
playwright-checkpoint
Structured page checkpoints for Playwright: screenshots, HTML snapshots, accessibility, web vitals, console/network errors, metadata, and post-run report generation.
Quick start
npm install -D playwright-checkpoint @playwright/testUse the built-in fixtures right away:
import { test, expect } from 'playwright-checkpoint';Add the drop-in HTML report teardown:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
globalTeardown: 'playwright-checkpoint/teardown',
});Example test:
import { test, expect } from 'playwright-checkpoint';
test('sign in', async ({ page, checkpoint }) => {
await page.goto('https://example.com/login');
await checkpoint('Login page', {
description: 'Open the login page and verify the sign-in form is visible.',
step: 1,
});
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('correct horse battery staple');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL(/dashboard/);
await checkpoint('Signed in dashboard', {
description: 'Submit valid credentials and confirm the dashboard loads.',
step: 2,
});
});After the run, checkpoint manifests are written into Playwright's test-results output tree and the default teardown generates an HTML report into ./report.
What it captures
All built-in collectors are registered automatically.
| Collector | Default | What it captures | Main artifact |
| --- | --- | --- | --- |
| screenshot | On | PNG screenshot for each checkpoint | page.png |
| html | On | Full page HTML at checkpoint time | page.html |
| axe | On | Accessibility audit via @axe-core/playwright when available | axe.json |
| web-vitals | On | CLS, FCP, LCP, INP, TTFB and related navigation timings | web-vitals.json |
| console | On | New console errors and page errors since the last checkpoint | console-errors.json |
| network | On | Failed requests and HTTP 4xx/5xx responses since the last checkpoint | failed-requests.json |
| metadata | On | Title, description, canonical URL, OG tags, language, viewport, JSON-LD | metadata.json |
| aria-snapshot | Off | Accessibility tree snapshot for machine-readable a11y state | aria-snapshot.json |
| dom-stats | Off | DOM size/depth and element counts | dom-stats.json |
| forms | Off | Visible form field state with configurable redaction | form-state.json |
| storage | Off | Cookie metadata and localStorage key/value state (optional values) | storage-state.json |
| network-timing | Off | Incremental response timing and transfer size breakdown | network-timing.json |
Notes:
@axe-core/playwrightis an optional dependency. If it is unavailable, theaxecollector skips gracefully.- Screenshot options such as
fullPageandhighlightSelectorare passed per checkpoint. - Extended collectors (
aria-snapshot,dom-stats,forms,storage,network-timing) are opt-in by default. - Collector artifacts are also attached to the Playwright test result when possible.
Configuration
1. Global configuration with createCheckpoint()
Use createCheckpoint() when you want project-wide defaults or custom collectors.
import { createCheckpoint, expect } from 'playwright-checkpoint';
export const { test } = createCheckpoint({
collectors: {
axe: { timeoutMs: 10_000 },
network: true,
console: true,
},
});
export { expect };createCheckpoint() returns a Playwright test object extended with these fixtures:
checkpoint(name, options?)checkpointManifesttestCheckpointConfigdeviceProfile
2. Per-test configuration with testCheckpointConfig
Override defaults inside a single test or in a beforeEach.
import { createCheckpoint, expect } from 'playwright-checkpoint';
const { test } = createCheckpoint();
test('checkout on mobile', async ({ page, checkpoint, testCheckpointConfig }) => {
testCheckpointConfig.set({
description: 'Mobile checkout happy path.',
collectors: {
'web-vitals': true,
axe: { timeoutMs: 15_000 },
},
});
await page.goto('https://example.com/checkout');
await checkpoint('Cart review');
});The per-test config merges onto global config. Collector overrides resolve in this order:
- Global defaults from
createCheckpoint() - Per-test config from
testCheckpointConfig.set() - Per-checkpoint options from
checkpoint(name, options)
3. Per-checkpoint options
Actual supported checkpoint-level options are:
await checkpoint('Review order', {
description: 'Review the order summary before placing the order.',
step: 3,
fullPage: true,
highlightSelector: '[data-testid="order-summary"]',
collectors: {
axe: false,
screenshot: true,
},
});Available checkpoint options:
description?: stringstep?: numberfullPage?: booleanhighlightSelector?: stringcollectors?: Partial<Record<string, boolean | CollectorOptions>>
Custom collectors
A collector implements CheckpointCollector and returns structured data, artifacts, and a summary.
import fs from 'node:fs/promises';
import path from 'node:path';
import { createCheckpoint, type CheckpointCollector } from 'playwright-checkpoint';
const urlCollector: CheckpointCollector = {
name: 'url-fragment',
defaultEnabled: true,
async collect(ctx) {
const outputPath = path.join(ctx.checkpointDir, 'url-fragment.json');
const data = {
href: ctx.page.url(),
checkpoint: ctx.checkpointName,
};
await fs.writeFile(outputPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
return {
data,
artifacts: [
{
name: 'url-fragment',
path: outputPath,
contentType: 'application/json',
},
],
summary: {
href: data.href,
},
};
},
};
export const { test } = createCheckpoint({
custom: [urlCollector],
collectors: {
'url-fragment': true,
},
});You can also register built-ins or shared collectors explicitly:
import { registerBuiltinCollector } from 'playwright-checkpoint';
registerBuiltinCollector(urlCollector);Report generation
There are three report-generation paths.
1. Drop-in global teardown
For the default HTML report only:
export default defineConfig({
globalTeardown: 'playwright-checkpoint/teardown',
});That entry point:
- loads manifests from
./test-resultsby default - writes reports to
./reportby default - enables the built-in HTML reporter
- logs results to the console
- never crashes Playwright teardown on report errors
Optional environment overrides:
PLAYWRIGHT_CHECKPOINT_RESULTS_DIRPLAYWRIGHT_CHECKPOINT_REPORT_DIR
2. CLI
Generate reports after a run:
npx playwright-checkpoint reportCustom directories:
npx playwright-checkpoint report \
--results-dir ./test-results \
--output-dir ./reportChoose reporters explicitly:
npx playwright-checkpoint report --reporter html,markdown
npx playwright-checkpoint report --reporter markdown --output-dir ./docs/help3. Programmatic API
Use runReporters() when you want markdown articles, custom reporters, or advanced config.
import { runReporters } from 'playwright-checkpoint';
await runReporters(
{
reporters: {
html: true,
markdown: {
frontmatter: true,
includeTags: ['@user-journey'],
},
},
},
'./test-results',
'./report',
);Built-in reporters
HTML report
- Enabled by default in
runReporters(). - Enabled by the drop-in teardown.
- Writes
index.htmlinto the chosen output directory. - Groups runs by story and shows checkpoint artifacts, summaries, and per-project runs.
Markdown article generator
- Reporter name:
markdown - Default: disabled
- Output: one Markdown file per story/test
- Copies screenshot assets into the report output folder by default
Supported markdown reporter config:
{
reporters: {
markdown: {
storiesDir: '.',
screenshotsDir: 'screenshots',
includeTags: ['@user-journey'],
preferredProject: 'desktop-light',
header: 'Intro copy shown before the steps.',
footer: 'Outro copy shown after the steps.',
frontmatter: true,
imagePathPrefix: '/static/help',
copyScreenshots: true,
},
},
}Behavior:
- Generates one article per story/test title
- Strips
@tagsfrom the article title and filename - Orders steps by explicit
step, then capture order - Uses
descriptionwhen present - Falls back to an auto-generated sentence from page title + URL
- Includes URL and breadcrumb text for each step
- Only includes stories that either:
- have at least one checkpoint with
descriptionorstep, or - match
includeTags
- have at least one checkpoint with
Custom reporters
A custom reporter implements ReportGenerator.
import fs from 'node:fs/promises';
import path from 'node:path';
import {
registerBuiltinReporter,
type ReportGenerator,
} from 'playwright-checkpoint';
const jsonSummaryReporter: ReportGenerator = {
name: 'json-summary',
validateConfig(config) {
return config != null && typeof config === 'object' && !Array.isArray(config);
},
async generate(context) {
const outputPath = path.join(context.outputDir, 'summary.json');
await fs.mkdir(context.outputDir, { recursive: true });
await fs.writeFile(
outputPath,
`${JSON.stringify(
{
runCount: context.runs.length,
manifests: context.manifests.length,
},
null,
2,
)}\n`,
'utf8',
);
return {
files: [outputPath],
summary: `Generated JSON summary for ${context.runs.length} runs.`,
};
},
};
registerBuiltinReporter(jsonSummaryReporter);Then run it programmatically or via the CLI:
await runReporters(
{
reporters: {
html: false,
'json-summary': {},
},
},
'./test-results',
'./report',
);Help article generation
Markdown articles work best when your checkpoints are annotated.
await checkpoint('Navigate to the login page', {
step: 1,
description: 'Open the login page and confirm the email/password fields are visible.',
});
await checkpoint('Enter credentials and sign in', {
step: 2,
description: 'Fill in valid credentials and submit the form.',
});Generate articles from the CLI:
npx playwright-checkpoint report --reporter markdown --output-dir ./docs/helpOnly include tests tagged for docs:
await runReporters(
{
reporters: {
html: false,
markdown: {
includeTags: ['@user-journey'],
frontmatter: true,
},
},
},
'./test-results',
'./docs/help',
);Example output shape:
# Sign in to your account
## Step 1: Navigate to the login page

**URL:** `/login`
**Breadcrumb:** login
Open the login page and confirm the email/password fields are visible.CLI reference
playwright-checkpoint report
Generate reports from checkpoint manifests.
playwright-checkpoint report [--results-dir ./test-results] [--output-dir ./report] [--reporter html,markdown]Flags:
--results-dir <path>: directory containing checkpoint manifests--output-dir <path>: destination directory for generated reports--reporter <names>: comma-separated reporter list-h,--help: usage help
Defaults:
resultsDir = ./test-resultsoutputDir = ./reportreporters = html
API reference
Main runtime exports
From playwright-checkpoint:
test— default Playwright test fixture set with checkpoint supportexpect— re-export from@playwright/testcreateCheckpoint(config?)createDeviceProfile(testInfo)settlePage(page)
Checkpoint helpers
Also exported for advanced use:
manifestTags(testInfo)checkpointSlug(name, existing)collectPageTitle(page)resolveCollectors(globalConfig, testConfig, checkpointOptions)createCheckpointManifestRecord(testInfo)writeCheckpointManifest(testInfo, manifest)captureCheckpointRecord(args)runCollectorSetup(collectors, page, testInfo)runCollectorTeardown(collectors, page, testInfo)sanitizeSegment(value)warn(message, error?)
Built-in collectors
Exported collector instances:
screenshotCollectorhtmlCollectoraxeCollectorwebVitalsCollectorconsoleCollectornetworkCollectormetadataCollectorariaSnapshotCollectordomStatsCollectorformsCollectorstorageCollectornetworkTimingCollector
Collector registry utilities:
registerBuiltinCollector(collector)registerBuiltinCollectors(collectors)getBuiltinCollectors()
Reporting exports
runReporters(config, testResultsDir, outputDir)loadRuns(testResultsDir)dedupeRuns(runs)groupByStory(runs)orderedCheckpointNames(runs)registerBuiltinReporter(reporter)htmlReportermarkdownReporter
Types
Key exported types include:
CheckpointRecordCheckpointManifestCheckpointOptionsCheckpointConfigTestCheckpointConfigCheckpointCollectorCollectorResultCollectorArtifactCollectorContextCollectorOptionsResolvedCollectorConfigCollectorConfigBoundingBoxScreenshotCollectorDataHtmlCollectorDataAxeCollectorDataWebVitalRatingWebVitalMetricWebVitalsSnapshotConsoleErrorRecordFailedRequestRecordPageMetadataAriaSnapshotCollectorDataDomStatsCollectorDataFormFieldValueFormFieldStateFormsCollectorDataStorageCookieStateStorageEntryStateStorageCollectorDataNetworkTimingBreakdownNetworkTimingRecordNetworkTimingCollectorDataReporterConfigReportGeneratorReportGeneratorContextReportGeneratorResultReportGenerationResultsRunRecord
Page Object Model pattern
A good pattern is to keep navigation and actions in the page object, and pass the checkpoint fixture into methods that should produce documentation.
import type { Page } from '@playwright/test';
import { test, expect } from 'playwright-checkpoint';
type CheckpointFn = Parameters<typeof test>[1] extends (args: infer T, ...rest: never[]) => unknown
? T extends { checkpoint: infer C }
? C
: never
: never;
class LoginPage {
constructor(private readonly page: Page) {}
async open(checkpoint: CheckpointFn) {
await this.page.goto('https://example.com/login');
await checkpoint('Login page', {
step: 1,
description: 'Open the login page and verify the form is present.',
});
}
async signIn(email: string, password: string, checkpoint: CheckpointFn) {
await this.page.getByLabel('Email').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Sign in' }).click();
await checkpoint('Signed in', {
step: 2,
description: 'Submit valid credentials and wait for the authenticated destination.',
});
}
}
test('login flow', async ({ page, checkpoint }) => {
const loginPage = new LoginPage(page);
await loginPage.open(checkpoint);
await loginPage.signIn('[email protected]', 'secret', checkpoint);
await expect(page).toHaveURL(/dashboard/);
});If you prefer, you can define your own local alias for the checkpoint function type instead of extracting it from test.
Development notes
- Build:
npm run build - Lint:
npm run lint - Type-check:
npm run check-types - Tests:
npm test
License
MIT
