20 KiB
| title | description |
|---|---|
| Integrate Playwright Utils with TEA | Add production-ready fixtures and utilities to your TEA-generated tests |
Integrate Playwright Utils with TEA
Integrate @seontechnologies/playwright-utils with TEA to get production-ready fixtures, utilities, and patterns in your test suite.
What is Playwright Utils?
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
When to Use This
- 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
Prerequisites
- 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).
Installation
Step 1: Install Package
npm install -D @seontechnologies/playwright-utils
Step 2: Enable in TEA Config
Edit _bmad/bmm/config.yaml:
tea_use_playwright_utils: true
Note: If you enabled this during installation (npx bmad-method@alpha install), it's already set.
Step 3: Verify Installation
# 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
What Changes When Enabled
*framework Workflow
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);
});
*atdd and *automate Workflows
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 }
});
*test-review Workflow
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
*ci Workflow
Without Playwright Utils: Basic CI configuration
With Playwright Utils: Enhanced CI with:
- Burn-in utility for smart test selection
- Selective testing based on git diff
- Test prioritization
Available Utilities
api-request
Typed HTTP client with schema validation.
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); // Note: chained method
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
auth-session
Authentication session management with token persistence.
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
network-recorder
Record and replay network traffic (HAR) for offline testing.
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:
# 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)
intercept-network-call
Spy or stub network requests with automatic JSON parsing.
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 (
responseJsonready to use) - Spy mode (observe real traffic) or stub mode (mock responses)
- Glob pattern URL matching
- Returns promise with
{ status, responseJson, requestJson }
recurse
Async polling for eventual consistency (Cypress-style).
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
log
Structured logging that integrates with Playwright reports.
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- Supports object logging with
.debug() - Trace test execution
file-utils
Read and validate CSV, PDF, XLSX, ZIP files.
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 }
burn-in
Smart test selection with git diff analysis for CI optimization.
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:
- Smart filtering (skip config, types, docs changes)
- Volume control (run percentage of affected tests)
- Git diff-based test selection
- Faster CI feedback
network-error-monitor
Automatically detect HTTP 4xx/5xx errors during tests.
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
- Opt-out with annotations
- Structured error reporting
Fixture Composition
Combine utilities using mergeTests:
Option 1: Use 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('full test', async ({ apiRequest, authToken, interceptNetworkCall }) => {
await log.info('Starting test'); // log is direct import
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/data',
headers: { Authorization: `Bearer ${authToken}` }
});
await log.info('Data fetched', body);
expect(status).toBe(200);
});
Note: log is imported directly (not a fixture). authToken requires auth-session provider setup.
Option 2: Merge Individual Fixtures (Selective)
import { test as base } from '@playwright/test';
import { mergeTests } from '@playwright/test';
import { test as apiRequestFixture } from '@seontechnologies/playwright-utils/api-request/fixtures';
import { test as recurseFixture } from '@seontechnologies/playwright-utils/recurse/fixtures';
import { log } from '@seontechnologies/playwright-utils';
// Merge only the fixtures you need
export const test = mergeTests(
apiRequestFixture,
recurseFixture
);
export { expect } from '@playwright/test';
// Use merged utilities in tests
test('selective test', async ({ apiRequest, recurse }) => {
await log.info('Starting test'); // log is direct import, not fixture
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/data'
});
await log.info('Data fetched', body);
expect(status).toBe(200);
});
Note: log is a direct utility (not a fixture), so import it separately.
Recommended: Use Option 1 (combined fixtures) unless you need fine control over which utilities are included.
Configuration
Environment Variables
# .env
PLAYWRIGHT_UTILS_LOG_LEVEL=debug # debug | info | warn | error
PLAYWRIGHT_UTILS_RETRY_ATTEMPTS=3
PLAYWRIGHT_UTILS_TIMEOUT=30000
Playwright Config
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
// Playwright Utils works with standard Playwright config
baseURL: process.env.BASE_URL || 'http://localhost:3000',
extraHTTPHeaders: {
// Add headers used by utilities
}
}
});
Troubleshooting
Import Errors
Problem: Cannot find module '@seontechnologies/playwright-utils/api-request'
Solution:
# 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
TEA Not Using Utilities
Problem: TEA generates tests without playwright-utils.
Causes:
- Config not set:
tea_use_playwright_utils: false - Workflow run before config change
- Package not installed
Solution:
# 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)
Type Errors with apiRequest
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 }
Migration Guide
Migrating Existing Tests
Before (Vanilla Playwright):
test('should access protected route', async ({ page, request }) => {
// Manual auth token fetch
const response = await request.post('/api/auth/login', {
data: { email: 'test@example.com', password: 'pass' }
});
const { token } = await response.json();
// Manual token storage
await page.goto('/dashboard');
await page.evaluate((token) => {
localStorage.setItem('authToken', token);
}, token);
await expect(page).toHaveURL('/dashboard');
});
After (With Playwright Utils):
import { test } from '@seontechnologies/playwright-utils/auth-session/fixtures';
test('should access protected route', async ({ page, authToken }) => {
// authToken automatically fetched and persisted by fixture
await page.goto('/dashboard');
// Token is already in place (no manual storage needed)
await expect(page).toHaveURL('/dashboard');
});
Benefits:
- Token fetched once, reused across all tests (persisted to disk)
- No manual token storage or management
- Automatic token renewal if expired
- Multi-user support via
authOptions.userIdentifier - 10 lines → 5 lines (less code)
Related Guides
Getting Started:
- TEA Lite Quickstart Tutorial - Learn TEA basics
- How to Set Up Test Framework - Initial framework setup
Workflow Guides:
- How to Run ATDD - Generate tests with utilities
- How to Run Automate - Expand coverage with utilities
- How to Run Test Review - Review against PW-Utils patterns
Other Customization:
- Enable MCP Enhancements - Live browser verification
Understanding the Concepts
- Fixture Architecture - Pure function → fixture pattern
- Network-First Patterns - Network utilities explained
- Test Quality Standards - Patterns PW-Utils enforces
Reference
- TEA Configuration - tea_use_playwright_utils option
- Knowledge Base Index - Playwright Utils fragments
- Glossary - Playwright Utils term
- Official PW-Utils Docs - Complete API reference
Generated with BMad Method - TEA (Test Architect)