Use production-ready, fixture-based utilities from @seontechnologies/playwright-utils for common Playwright testing patterns. Build test helpers as pure functions first, then wrap in framework-specific fixtures for composability and reuse. Works equally well for pure API testing (no browser) and UI testing.
Writing Playwright utilities from scratch for every project leads to:
@seontechnologies/playwright-utils provides:
mergeTests to combine utilitiesnpm install -D @seontechnologies/playwright-utils
Peer Dependencies:
@playwright/test >= 1.54.1 (required)ajv >= 8.0.0 (optional - for JSON Schema validation)zod >= 3.0.0 (optional - for Zod schema validation)| Utility | Purpose | Test Context |
|---|---|---|
| api-request | Typed HTTP client with schema validation and retry | API/Backend |
| recurse | Polling for async operations, background jobs | API/Backend |
| auth-session | Token persistence, multi-user, service-to-service | API/Backend/UI |
| log | Playwright report-integrated logging | API/Backend/UI |
| file-utils | CSV/XLSX/PDF/ZIP reading & validation | API/Backend/UI |
| burn-in | Smart test selection with git diff | CI/CD |
| network-recorder | HAR record/playback for offline testing | UI only |
| intercept-network-call | Network spy/stub with auto JSON parsing | UI only |
| network-error-monitor | Automatic HTTP 4xx/5xx detection | UI only |
Note: 6 of 9 utilities work without a browser. Only 3 are UI-specific (network-recorder, intercept-network-call, network-error-monitor).
Context: All utilities follow the same architectural pattern - pure function as core, fixture as wrapper.
Implementation:
// Direct import (pass Playwright context explicitly)
import { apiRequest } from '@seontechnologies/playwright-utils';
test('direct usage', async ({ request }) => {
const { status, body } = await apiRequest({
request, // Must pass request context
method: 'GET',
path: '/api/users',
});
});
// Fixture import (context injected automatically)
import { test } from '@seontechnologies/playwright-utils/fixtures';
test('fixture usage', async ({ apiRequest }) => {
const { status, body } = await apiRequest({
// No need to pass request context
method: 'GET',
path: '/api/users',
});
});
Key Points:
Context: Import only what you need to keep bundle sizes small.
Implementation:
// Import specific utility
import { apiRequest } from '@seontechnologies/playwright-utils/api-request';
// Import specific fixture
import { test } from '@seontechnologies/playwright-utils/api-request/fixtures';
// Import everything (use sparingly)
import { apiRequest, recurse, log } from '@seontechnologies/playwright-utils';
Key Points:
Context: Combine multiple playwright-utils fixtures with your own custom fixtures.
Implementation:
// playwright/support/merged-fixtures.ts
import { mergeTests } from '@playwright/test';
import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures';
import { test as recurseFixture } from '@seontechnologies/playwright-utils/recurse/fixtures';
import { test as logFixture } from '@seontechnologies/playwright-utils/log/fixtures';
// Merge all fixtures into one test object
export const test = mergeTests(apiRequestFixture, authFixture, recurseFixture, logFixture);
export { expect } from '@playwright/test';
// In your tests
import { test, expect } from '../support/merged-fixtures';
test('all utilities available', async ({ apiRequest, authToken, recurse, log }) => {
await log.step('Making authenticated API request');
const { body } = await apiRequest({
method: 'GET',
path: '/api/protected',
headers: { Authorization: `Bearer ${authToken}` },
});
await recurse(
() => apiRequest({ method: 'GET', path: `/status/${body.id}` }),
(res) => res.body.ready === true,
);
});
Key Points:
mergeTests combines multiple fixtures without conflicts1. Start with logging (zero breaking changes):
import { log } from '@seontechnologies/playwright-utils';
test('existing test', async ({ page }) => {
await log.step('Navigate to page'); // Just add logging
await page.goto('/dashboard');
// Rest of test unchanged
});
2. Add API utilities (for API tests):
import { test } from '@seontechnologies/playwright-utils/api-request/fixtures';
test('API test', async ({ apiRequest }) => {
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/users',
});
expect(status).toBe(200);
});
3. Expand to network utilities (for UI tests):
import { test } from '@seontechnologies/playwright-utils/fixtures';
test('UI with network control', async ({ page, interceptNetworkCall }) => {
const usersCall = interceptNetworkCall({
url: '**/api/users',
});
await page.goto('/dashboard');
const { responseJson } = await usersCall;
expect(responseJson).toHaveLength(10);
});
4. Full integration (merged fixtures):
Create merged-fixtures.ts and use across all tests.
api-request.md - HTTP client with schema validationnetwork-recorder.md - HAR-based offline testingauth-session.md - Token managementintercept-network-call.md - Network interceptionrecurse.md - Polling patternslog.md - Logging utilityfile-utils.md - File operationsfixtures-composition.md - Advanced mergeTests patterns❌ Don't mix direct and fixture imports in same test:
import { apiRequest } from '@seontechnologies/playwright-utils';
import { test } from '@seontechnologies/playwright-utils/auth-session/fixtures';
test('bad', async ({ request, authToken }) => {
// Confusing - mixing direct (needs request) and fixture (has authToken)
await apiRequest({ request, method: 'GET', path: '/api/users' });
});
✅ Use consistent import style:
import { test } from '../support/merged-fixtures';
test('good', async ({ apiRequest, authToken }) => {
// Clean - all from fixtures
await apiRequest({ method: 'GET', path: '/api/users' });
});
❌ Don't import everything when you need one utility:
import * as utils from '@seontechnologies/playwright-utils'; // Large bundle
✅ Use subpath imports:
import { apiRequest } from '@seontechnologies/playwright-utils/api-request'; // Small bundle
The official @seontechnologies/playwright-utils repository provides working examples of all patterns described in these fragments.
Repository: https://github.com/seontechnologies/playwright-utils
Key resources:
playwright/tests - All utilities in actionplaywright.config.ts, playwright/support/merged-fixtures.ts.github/workflows/ - GitHub Actions with sharding, parallelizationQuick start:
git clone https://github.com/seontechnologies/playwright-utils.git
cd playwright-utils
nvm use
npm install
npm run test:pw-ui # Explore tests with Playwright UI
npm run test:pw
All patterns in TEA fragments are production-tested in this repository.