Skip to content
🤖 Consolidated, AI-optimized BMAD docs: llms-full.txt. Fetch this plain text file for complete context.

Integrate Playwright Utils with TEA

Integrate @seontechnologies/playwright-utils with TEA to get production-ready fixtures, utilities, and patterns in your test suite.

A production-ready utility library that provides:

  • Typed API request helper
  • Authentication session management
  • Network recording and replay (HAR)
  • Network request interception
  • Async polling (recurse)
  • Structured logging
  • File validation (CSV, PDF, XLSX, ZIP)
  • Burn-in testing utilities
  • Network error monitoring

Repository: https://github.com/seontechnologies/playwright-utils

npm Package: @seontechnologies/playwright-utils

  • You want production-ready fixtures (not DIY)
  • Your team benefits from standardized patterns
  • You need utilities like API testing, auth handling, network mocking
  • You want TEA to generate tests using these utilities
  • You’re building reusable test infrastructure

Don’t use if:

  • You’re just learning testing (keep it simple first)
  • You have your own fixture library
  • You don’t need the utilities
  • BMad Method installed
  • TEA agent available
  • Test framework setup complete (Playwright)
  • Node.js v18 or later

Note: Playwright Utils is for Playwright only (not Cypress).

Terminal window
npm install -D @seontechnologies/playwright-utils

Edit _bmad/bmm/config.yaml:

tea_use_playwright_utils: true

Note: If you enabled this during BMad installation, it’s already set.

Terminal window
# Check package installed
npm list @seontechnologies/playwright-utils
# Check TEA config
grep tea_use_playwright_utils _bmad/bmm/config.yaml

Should show:

@seontechnologies/playwright-utils@2.x.x
tea_use_playwright_utils: true

Vanilla Playwright:

// Basic Playwright fixtures only
import { test, expect } from '@playwright/test';
test('api test', async ({ request }) => {
const response = await request.get('/api/users');
const users = await response.json();
expect(response.status()).toBe(200);
});

With Playwright Utils (Combined Fixtures):

// All utilities available via single import
import { test } from '@seontechnologies/playwright-utils/fixtures';
import { expect } from '@playwright/test';
test('api test', async ({ apiRequest, authToken, log }) => {
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/users',
headers: { Authorization: `Bearer ${authToken}` }
});
log.info('Fetched users', body);
expect(status).toBe(200);
});

With Playwright Utils (Selective Merge):

import { mergeTests } from '@playwright/test';
import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
import { test as logFixture } from '@seontechnologies/playwright-utils/log/fixtures';
export const test = mergeTests(apiRequestFixture, logFixture);
export { expect } from '@playwright/test';
test('api test', async ({ apiRequest, log }) => {
log.info('Fetching users');
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/users'
});
expect(status).toBe(200);
});

Without Playwright Utils:

// Manual API calls
test('should fetch profile', async ({ page, request }) => {
const response = await request.get('/api/profile');
const profile = await response.json();
// Manual parsing and validation
});

With Playwright Utils:

import { test } from '@seontechnologies/playwright-utils/api-request/fixtures';
test('should fetch profile', async ({ apiRequest }) => {
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/profile' // 'path' not 'url'
}).validateSchema(ProfileSchema); // Chained validation
expect(status).toBe(200);
// body is type-safe: { id: string, name: string, email: string }
});

Without Playwright Utils: Reviews against generic Playwright patterns

With Playwright Utils: Reviews against playwright-utils best practices:

  • Fixture composition patterns
  • Utility usage (apiRequest, authSession, etc.)
  • Network-first patterns
  • Structured logging

Without Playwright Utils:

  • Parallel sharding
  • Burn-in loops (basic shell scripts)
  • CI triggers (PR, push, schedule)
  • Artifact collection

With Playwright Utils: Enhanced with smart testing:

  • Burn-in utility (git diff-based, volume control)
  • Selective testing (skip config/docs/types changes)
  • Test prioritization by file changes

Typed HTTP client with schema validation.

Official Docs: https://seontechnologies.github.io/playwright-utils/api-request.html

Why Use This?

Vanilla Playwrightapi-request Utility
Manual await response.json()Automatic JSON parsing
response.status() + separate body parsingReturns { status, body } structure
No built-in retryAutomatic retry for 5xx errors
No schema validationSingle-line .validateSchema()
Verbose status checkingClean destructuring

Usage:

import { test } from '@seontechnologies/playwright-utils/api-request/fixtures';
import { expect } from '@playwright/test';
import { z } from 'zod';
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email()
});
test('should create user', async ({ apiRequest }) => {
const { status, body } = await apiRequest({
method: 'POST',
path: '/api/users', // Note: 'path' not 'url'
body: { name: 'Test User', email: 'test@example.com' } // Note: 'body' not 'data'
}).validateSchema(UserSchema); // Chained method (can await separately if needed)
expect(status).toBe(201);
expect(body.id).toBeDefined();
expect(body.email).toBe('test@example.com');
});

Benefits:

  • Returns { status, body } structure
  • Schema validation with .validateSchema() chained method
  • Automatic retry for 5xx errors
  • Type-safe response body

Authentication session management with token persistence.

Official Docs: https://seontechnologies.github.io/playwright-utils/auth-session.html

Why Use This?

Vanilla Playwright Authauth-session
Re-authenticate every test run (slow)Authenticate once, persist to disk
Single user per setupMulti-user support (roles, accounts)
No token expiration handlingAutomatic token renewal
Manual session managementProvider pattern (flexible auth)

Usage:

import { test } from '@seontechnologies/playwright-utils/auth-session/fixtures';
import { expect } from '@playwright/test';
test('should access protected route', async ({ page, authToken }) => {
// authToken automatically fetched and persisted
// No manual login needed - handled by fixture
await page.goto('/dashboard');
await expect(page).toHaveURL('/dashboard');
// Token is reused across tests (persisted to disk)
});

Configuration required (see auth-session docs for provider setup):

global-setup.ts
import { authStorageInit, setAuthProvider, authGlobalInit } from '@seontechnologies/playwright-utils/auth-session';
async function globalSetup() {
authStorageInit();
setAuthProvider(myCustomProvider); // Define your auth mechanism
await authGlobalInit(); // Fetch token once
}

Benefits:

  • Token fetched once, reused across all tests
  • Persisted to disk (faster subsequent runs)
  • Multi-user support via authOptions.userIdentifier
  • Automatic token renewal if expired

Record and replay network traffic (HAR) for offline testing.

Official Docs: https://seontechnologies.github.io/playwright-utils/network-recorder.html

Why Use This?

Vanilla Playwright HARnetwork-recorder
Manual routeFromHAR() configurationAutomatic HAR management with PW_NET_MODE
Separate record/playback test filesSame test, switch env var
No CRUD detectionStateful mocking (POST/PUT/DELETE work)
Manual HAR file pathsAuto-organized by test name

Usage:

import { test } from '@seontechnologies/playwright-utils/network-recorder/fixtures';
// Record mode: Set environment variable
process.env.PW_NET_MODE = 'record';
test('should work with recorded traffic', async ({ page, context, networkRecorder }) => {
// Setup recorder (records or replays based on PW_NET_MODE)
await networkRecorder.setup(context);
// Your normal test code
await page.goto('/dashboard');
await page.click('#add-item');
// First run (record): Saves traffic to HAR file
// Subsequent runs (playback): Uses HAR file, no backend needed
});

Switch modes:

Terminal window
# Record traffic
PW_NET_MODE=record npx playwright test
# Playback traffic (offline)
PW_NET_MODE=playback npx playwright test

Benefits:

  • Offline testing (no backend needed)
  • Deterministic responses (same every time)
  • Faster execution (no network latency)
  • Stateful mocking (CRUD operations work)

Spy or stub network requests with automatic JSON parsing.

Official Docs: https://seontechnologies.github.io/playwright-utils/intercept-network-call.html

Why Use This?

Vanilla PlaywrightinterceptNetworkCall
Route setup + response waiting (separate steps)Single declarative call
Manual await response.json()Automatic JSON parsing (responseJson)
Complex filter predicatesSimple glob patterns (**/api/**)
Verbose syntaxConcise, readable API

Usage:

import { test } from '@seontechnologies/playwright-utils/fixtures';
test('should handle API errors', async ({ page, interceptNetworkCall }) => {
// Stub API to return error (set up BEFORE navigation)
const profileCall = interceptNetworkCall({
method: 'GET',
url: '**/api/profile',
fulfillResponse: {
status: 500,
body: { error: 'Server error' }
}
});
await page.goto('/profile');
// Wait for the intercepted response
const { status, responseJson } = await profileCall;
expect(status).toBe(500);
expect(responseJson.error).toBe('Server error');
await expect(page.getByText('Server error occurred')).toBeVisible();
});

Benefits:

  • Automatic JSON parsing (responseJson ready to use)
  • Spy mode (observe real traffic) or stub mode (mock responses)
  • Glob pattern URL matching
  • Returns promise with { status, responseJson, requestJson }

Async polling for eventual consistency (Cypress-style).

Official Docs: https://seontechnologies.github.io/playwright-utils/recurse.html

Why Use This?

Manual Pollingrecurse Utility
while loops with waitForTimeoutSmart polling with exponential backoff
Hard-coded retry logicConfigurable timeout/interval
No logging visibilityOptional logging with custom messages
Verbose, error-proneClean, readable API

Usage:

import { test } from '@seontechnologies/playwright-utils/fixtures';
test('should wait for async job completion', async ({ apiRequest, recurse }) => {
// Start async job
const { body: job } = await apiRequest({
method: 'POST',
path: '/api/jobs'
});
// Poll until complete (smart waiting)
const completed = await recurse(
() => apiRequest({ method: 'GET', path: `/api/jobs/${job.id}` }),
(result) => result.body.status === 'completed',
{
timeout: 30000,
interval: 2000,
log: 'Waiting for job to complete'
}
});
expect(completed.body.status).toBe('completed');
});

Benefits:

  • Smart polling with configurable interval
  • Handles async jobs, background tasks
  • Optional logging for debugging
  • Better than hard waits or manual polling loops

Structured logging that integrates with Playwright reports.

Official Docs: https://seontechnologies.github.io/playwright-utils/log.html

Why Use This?

Console.log / printlog Utility
Not in test reportsIntegrated with Playwright reports
No step visualization.step() shows in Playwright UI
Manual object formattingLogs objects seamlessly
No structured outputJSON artifacts for debugging

Usage:

import { log } from '@seontechnologies/playwright-utils';
import { test, expect } from '@playwright/test';
test('should login', async ({ page }) => {
await log.info('Starting login test');
await page.goto('/login');
await log.step('Navigated to login page'); // Shows in Playwright UI
await page.getByLabel('Email').fill('test@example.com');
await log.debug('Filled email field');
await log.success('Login completed');
// Logs appear in test output and Playwright reports
});

Benefits:

  • Direct import (no fixture needed for basic usage)
  • Structured logs in test reports
  • .step() shows in Playwright UI
  • Logs objects seamlessly (no special handling needed)
  • Trace test execution

Read and validate CSV, PDF, XLSX, ZIP files.

Official Docs: https://seontechnologies.github.io/playwright-utils/file-utils.html

Why Use This?

Vanilla Playwrightfile-utils
~80 lines per CSV flow~10 lines end-to-end
Manual download event handlinghandleDownload() encapsulates all
External parsing librariesAuto-parsing (CSV, XLSX, PDF, ZIP)
No validation helpersBuilt-in validation (headers, row count)

Usage:

import { handleDownload, readCSV } from '@seontechnologies/playwright-utils/file-utils';
import { expect } from '@playwright/test';
import path from 'node:path';
const DOWNLOAD_DIR = path.join(__dirname, '../downloads');
test('should export valid CSV', async ({ page }) => {
// Handle download and get file path
const downloadPath = await handleDownload({
page,
downloadDir: DOWNLOAD_DIR,
trigger: () => page.click('button:has-text("Export")')
});
// Read and parse CSV
const csvResult = await readCSV({ filePath: downloadPath });
const { data, headers } = csvResult.content;
// Validate structure
expect(headers).toEqual(['Name', 'Email', 'Status']);
expect(data.length).toBeGreaterThan(0);
expect(data[0]).toMatchObject({
Name: expect.any(String),
Email: expect.any(String),
Status: expect.any(String)
});
});

Benefits:

  • Handles downloads automatically
  • Auto-parses CSV, XLSX, PDF, ZIP
  • Type-safe access to parsed data
  • Returns structured { headers, data }

Smart test selection with git diff analysis for CI optimization.

Official Docs: https://seontechnologies.github.io/playwright-utils/burn-in.html

Why Use This?

Playwright --only-changedburn-in Utility
Config changes trigger all testsSmart filtering (skip configs, types, docs)
All or nothingVolume control (run percentage)
No customizationCustom dependency analysis
Slow CI on minor changesFast CI with intelligent selection

Usage:

scripts/burn-in-changed.ts
import { runBurnIn } from '@seontechnologies/playwright-utils/burn-in';
async function main() {
await runBurnIn({
configPath: 'playwright.burn-in.config.ts',
baseBranch: 'main'
});
}
main().catch(console.error);

Config:

playwright.burn-in.config.ts
import type { BurnInConfig } from '@seontechnologies/playwright-utils/burn-in';
const config: BurnInConfig = {
skipBurnInPatterns: [
'**/config/**',
'**/*.md',
'**/*types*'
],
burnInTestPercentage: 0.3,
burnIn: {
repeatEach: 3,
retries: 1
}
};
export default config;

Package script:

{
"scripts": {
"test:burn-in": "tsx scripts/burn-in-changed.ts"
}
}

Benefits:

  • Ensure flake-free tests upfront - Never deal with test flake again
  • Smart filtering (skip config, types, docs changes)
  • Volume control (run percentage of affected tests)
  • Git diff-based test selection
  • Faster CI feedback

Automatically detect HTTP 4xx/5xx errors during tests.

Official Docs: https://seontechnologies.github.io/playwright-utils/network-error-monitor.html

Why Use This?

Vanilla Playwrightnetwork-error-monitor
UI passes, backend 500 ignoredAuto-fails on any 4xx/5xx
Manual error checkingZero boilerplate (auto-enabled)
Silent failures slip throughActs like Sentry for tests
No domino effect preventionLimits cascading failures

Usage:

import { test } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';
// That's it! Network monitoring is automatically enabled
test('should not have API errors', async ({ page }) => {
await page.goto('/dashboard');
await page.click('button');
// Test fails automatically if any HTTP 4xx/5xx errors occur
// Error message shows: "Network errors detected: 2 request(s) failed"
// GET 500 https://api.example.com/users
// POST 503 https://api.example.com/metrics
});

Opt-out for validation tests:

// When testing error scenarios, opt-out with annotation
test('should show error message on 404',
{ annotation: [{ type: 'skipNetworkMonitoring' }] }, // Array format
async ({ page }) => {
await page.goto('/invalid-page'); // Will 404
await expect(page.getByText('Page not found')).toBeVisible();
// Test won't fail on 404 because of annotation
}
);
// Or opt-out entire describe block
test.describe('error handling',
{ annotation: [{ type: 'skipNetworkMonitoring' }] },
() => {
test('handles 404', async ({ page }) => {
// Monitoring disabled for all tests in block
});
}
);

Benefits:

  • Auto-enabled (zero setup)
  • Catches silent backend failures (500, 503, 504)
  • Prevents domino effect (limits cascading failures from one bad endpoint)
  • Opt-out with annotations for validation tests
  • Structured error reporting (JSON artifacts)

Option 1: Use Package’s Combined Fixtures (Simplest)

// Import all utilities at once
import { test } from '@seontechnologies/playwright-utils/fixtures';
import { log } from '@seontechnologies/playwright-utils';
import { expect } from '@playwright/test';
test('api test', async ({ apiRequest, interceptNetworkCall }) => {
await log.info('Fetching users');
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/users'
});
expect(status).toBe(200);
});

Option 2: Create Custom Merged Fixtures (Selective)

File 1: support/merged-fixtures.ts

import { test as base, mergeTests } from '@playwright/test';
import { test as apiRequest } from '@seontechnologies/playwright-utils/api-request/fixtures';
import { test as interceptNetworkCall } from '@seontechnologies/playwright-utils/intercept-network-call/fixtures';
import { test as networkErrorMonitor } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';
import { log } from '@seontechnologies/playwright-utils';
// Merge only what you need
export const test = mergeTests(
base,
apiRequest,
interceptNetworkCall,
networkErrorMonitor
);
export const expect = base.expect;
export { log };

File 2: tests/api/users.spec.ts

import { test, expect, log } from '../support/merged-fixtures';
test('api test', async ({ apiRequest, interceptNetworkCall }) => {
await log.info('Fetching users');
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/users'
});
expect(status).toBe(200);
});

Contrast:

  • Option 1: All utilities available, zero setup
  • Option 2: Pick utilities you need, one central file

See working examples: https://github.com/seontechnologies/playwright-utils/tree/main/playwright/support

Problem: Cannot find module ‘@seontechnologies/playwright-utils/api-request’

Solution:

Terminal window
# Verify package installed
npm list @seontechnologies/playwright-utils
# Check package.json has correct version
"@seontechnologies/playwright-utils": "^2.0.0"
# Reinstall if needed
npm install -D @seontechnologies/playwright-utils

Problem: TEA generates tests without playwright-utils.

Causes:

  1. Config not set: tea_use_playwright_utils: false
  2. Workflow run before config change
  3. Package not installed

Solution:

Terminal window
# Check config
grep tea_use_playwright_utils _bmad/bmm/config.yaml
# Should show: tea_use_playwright_utils: true
# Start fresh chat (TEA loads config at start)

Problem: TypeScript errors on apiRequest response.

Cause: No schema validation.

Solution:

// Add Zod schema for type safety
import { z } from 'zod';
const ProfileSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email()
});
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/profile' // 'path' not 'url'
}).validateSchema(ProfileSchema); // Chained method
expect(status).toBe(200);
// body is typed as { id: string, name: string, email: string }

Getting Started:

Workflow Guides:

Other Customization:


Generated with BMad Method - TEA (Test Architect)