Integrate Playwright Utils with TEA
Integrate Playwright Utils with TEA
Section titled â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?
Section titled â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
Section titled â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
Section titled â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
Section titled âInstallationâStep 1: Install Package
Section titled âStep 1: Install Packageânpm install -D @seontechnologies/playwright-utilsStep 2: Enable in TEA Config
Section titled âStep 2: Enable in TEA ConfigâEdit _bmad/bmm/config.yaml:
tea_use_playwright_utils: trueNote: If you enabled this during BMad installation, itâs already set.
Step 3: Verify Installation
Section titled âStep 3: Verify Installationâ# Check package installednpm list @seontechnologies/playwright-utils
# Check TEA configgrep tea_use_playwright_utils _bmad/bmm/config.yamlShould show:
@seontechnologies/playwright-utils@2.x.xtea_use_playwright_utils: trueWhat Changes When Enabled
Section titled âWhat Changes When Enabledâframework Workflow
Section titled âframework WorkflowâVanilla Playwright:
// Basic Playwright fixtures onlyimport { 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 importimport { 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
Section titled âatdd and automate WorkflowsâWithout Playwright Utils:
// Manual API callstest('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
Section titled â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
Section titled âci Workflowâ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
Available Utilities
Section titled âAvailable Utilitiesâapi-request
Section titled âapi-requestâTyped HTTP client with schema validation.
Official Docs: https://seontechnologies.github.io/playwright-utils/api-request.html
Why Use This?
| Vanilla Playwright | api-request Utility |
|---|---|
Manual await response.json() | Automatic JSON parsing |
response.status() + separate body parsing | Returns { status, body } structure |
| No built-in retry | Automatic retry for 5xx errors |
| No schema validation | Single-line .validateSchema() |
| Verbose status checking | Clean 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
auth-session
Section titled âauth-sessionâAuthentication session management with token persistence.
Official Docs: https://seontechnologies.github.io/playwright-utils/auth-session.html
Why Use This?
| Vanilla Playwright Auth | auth-session |
|---|---|
| Re-authenticate every test run (slow) | Authenticate once, persist to disk |
| Single user per setup | Multi-user support (roles, accounts) |
| No token expiration handling | Automatic token renewal |
| Manual session management | Provider 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):
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
Section titled ânetwork-recorderâ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 HAR | network-recorder |
|---|---|
Manual routeFromHAR() configuration | Automatic HAR management with PW_NET_MODE |
| Separate record/playback test files | Same test, switch env var |
| No CRUD detection | Stateful mocking (POST/PUT/DELETE work) |
| Manual HAR file paths | Auto-organized by test name |
Usage:
import { test } from '@seontechnologies/playwright-utils/network-recorder/fixtures';
// Record mode: Set environment variableprocess.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 trafficPW_NET_MODE=record npx playwright test
# Playback traffic (offline)PW_NET_MODE=playback npx playwright testBenefits:
- Offline testing (no backend needed)
- Deterministic responses (same every time)
- Faster execution (no network latency)
- Stateful mocking (CRUD operations work)
intercept-network-call
Section titled âintercept-network-callâ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 Playwright | interceptNetworkCall |
|---|---|
| Route setup + response waiting (separate steps) | Single declarative call |
Manual await response.json() | Automatic JSON parsing (responseJson) |
| Complex filter predicates | Simple glob patterns (**/api/**) |
| Verbose syntax | Concise, 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 (
responseJsonready to use) - Spy mode (observe real traffic) or stub mode (mock responses)
- Glob pattern URL matching
- Returns promise with
{ status, responseJson, requestJson }
recurse
Section titled ârecurseâAsync polling for eventual consistency (Cypress-style).
Official Docs: https://seontechnologies.github.io/playwright-utils/recurse.html
Why Use This?
| Manual Polling | recurse Utility |
|---|---|
while loops with waitForTimeout | Smart polling with exponential backoff |
| Hard-coded retry logic | Configurable timeout/interval |
| No logging visibility | Optional logging with custom messages |
| Verbose, error-prone | Clean, 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 / print | log Utility |
|---|---|
| Not in test reports | Integrated with Playwright reports |
| No step visualization | .step() shows in Playwright UI |
| Manual object formatting | Logs objects seamlessly |
| No structured output | JSON 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
file-utils
Section titled âfile-utilsâRead and validate CSV, PDF, XLSX, ZIP files.
Official Docs: https://seontechnologies.github.io/playwright-utils/file-utils.html
Why Use This?
| Vanilla Playwright | file-utils |
|---|---|
| ~80 lines per CSV flow | ~10 lines end-to-end |
| Manual download event handling | handleDownload() encapsulates all |
| External parsing libraries | Auto-parsing (CSV, XLSX, PDF, ZIP) |
| No validation helpers | Built-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 }
burn-in
Section titled âburn-inâ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-changed | burn-in Utility |
|---|---|
| Config changes trigger all tests | Smart filtering (skip configs, types, docs) |
| All or nothing | Volume control (run percentage) |
| No customization | Custom dependency analysis |
| Slow CI on minor changes | Fast CI with intelligent selection |
Usage:
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:
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
network-error-monitor
Section titled ânetwork-error-monitorâAutomatically detect HTTP 4xx/5xx errors during tests.
Official Docs: https://seontechnologies.github.io/playwright-utils/network-error-monitor.html
Why Use This?
| Vanilla Playwright | network-error-monitor |
|---|---|
| UI passes, backend 500 ignored | Auto-fails on any 4xx/5xx |
| Manual error checking | Zero boilerplate (auto-enabled) |
| Silent failures slip through | Acts like Sentry for tests |
| No domino effect prevention | Limits cascading failures |
Usage:
import { test } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures';
// That's it! Network monitoring is automatically enabledtest('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 annotationtest('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 blocktest.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)
Fixture Composition
Section titled âFixture CompositionâOption 1: Use Packageâs Combined Fixtures (Simplest)
// Import all utilities at onceimport { 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 needexport 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
Troubleshooting
Section titled âTroubleshootingâImport Errors
Section titled âImport ErrorsâProblem: Cannot find module â@seontechnologies/playwright-utils/api-requestâ
Solution:
# Verify package installednpm list @seontechnologies/playwright-utils
# Check package.json has correct version"@seontechnologies/playwright-utils": "^2.0.0"
# Reinstall if needednpm install -D @seontechnologies/playwright-utilsTEA Not Using Utilities
Section titled â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 configgrep 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
Section titled âType Errors with apiRequestâProblem: TypeScript errors on apiRequest response.
Cause: No schema validation.
Solution:
// Add Zod schema for type safetyimport { 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
Section titled âMigration GuideâRelated Guides
Section titled â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
Section titled âUnderstanding the Conceptsâ- Testing as Engineering - Why Playwright Utils matters (part of TEAâs three-part solution)
- Fixture Architecture - Pure function â fixture pattern
- Network-First Patterns - Network utilities explained
- Test Quality Standards - Patterns PW-Utils enforces
Reference
Section titled â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)