Reusable Playwright test utilities — declarative network mocking, smart retry, accessibility assertions, and stable screenshot naming. Stop re-writing the same fixtures in every project.
Every Playwright suite ends up re-implementing the same handful of helpers: intercepting network calls, retrying a flaky step, checking accessibility, and naming screenshots consistently. playwright-test-utils packages those into a small, well-tested, framework-idiomatic library so you can delete the boilerplate.
The matching/timing logic is pure and unit-tested; the browser-facing helpers are thin wrappers over Playwright's own APIs.
mockNetwork— declarative, first-match-wins request mocking. Fulfill with JSON/body, set status, or abort to simulate failures.retry— exponential-backoff retry for genuinely flaky steps, with anonRetryhook. Surfaces the final error instead of hiding it.assertAccessible— one-line axe-core accessibility assertion with a readable, actionable failure message.screenshotName— deterministic, filesystem-safe baseline names for visual diffs.- Tiny surface, typed throughout. Tree-shakeable named exports;
@playwright/teststays a peer dependency.
npm install -D playwright-test-utils@playwright/test is a peer dependency (you almost certainly already have it):
npm install -D @playwright/testRequires Node.js >= 18.
import { test, expect } from '@playwright/test';
import { mockNetwork } from 'playwright-test-utils';
test('renders users from the API', async ({ page }) => {
await mockNetwork(page, [
{ url: 'https://api.example.com/users', json: [{ id: 1, name: 'Ada' }] },
{ url: 'https://api.example.com/*', status: 404 }, // catch-all fallback
{ url: /\/analytics\//, abort: true }, // block trackers
]);
await page.goto('/');
await expect(page.getByText('Ada')).toBeVisible();
});import { retry } from 'playwright-test-utils';
const token = await retry(() => fetchAuthToken(), {
retries: 3,
delayMs: 200,
onRetry: (err, attempt) => console.warn(`auth retry #${attempt}:`, err),
});import { assertAccessible } from 'playwright-test-utils';
test('home page is accessible', async ({ page }) => {
await page.goto('/');
await assertAccessible(page, { disableRules: ['color-contrast'] });
});import { screenshotName } from 'playwright-test-utils';
test('home', async ({ page }, testInfo) => {
await page.goto('/');
await page.screenshot({
path: `screenshots/${screenshotName({ project: testInfo.project.name, title: testInfo.title })}`,
});
// -> screenshots/chromium--home.png
});Before — copy-pasted into every spec:
await page.route('**/*', (route) => {
const url = route.request().url();
if (url.includes('/api/users')) {
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(users) });
}
return route.continue();
});After:
await mockNetwork(page, [{ url: '**/api/users', json: users }]);Runnable Playwright specs live in examples/:
npm install
npx playwright install chromium
npm run example- v0.2 —
mockNetworkrequest assertions (expect(mock).toHaveBeenRequested()) - v0.3 —
retryintegration as a Playwright fixture (test.extend) - v0.4 — accessibility results as an attached HTML report artifact
- v0.5 — test-data factory helpers
- v1.0 — stable API + cookbook docs
See CONTRIBUTING.md. The pure logic is covered by Vitest unit
tests (npm test); browser behavior is demonstrated by the Playwright examples.
git clone https://github.com/fkhb90/playwright-test-utils.git
cd playwright-test-utils
npm install
npm test- url-filter-analyzer — lint, test, and analyze ad-block / URL filter lists.
MIT © fkhb90