Files
BMAD-METHOD/docs/tea/how-to/customization/integrate-playwright-utils.md
Alex Verkhovsky 91f6c41be1 docs: radical reduction of documentation scope for v6 beta (#1406)
* docs: radical reduction of documentation scope for v6 beta

Archive and basement unreviewed content to ship a focused, minimal doc set.

Changes:
- Archive stale how-to workflow guides (will rewrite for v6)
- Archive outdated explanation and reference content
- Move unreviewed content to basement for later review
- Reorganize TEA docs into dedicated /tea/ section
- Add workflow-map visual reference page
- Simplify getting-started tutorial and sidebar navigation
- Add explanation pages: brainstorming, adversarial-review, party-mode,
  quick-flow, advanced-elicitation
- Fix base URL handling for subdirectory deployments (GitHub Pages forks)

The goal is a minimal, accurate doc set for beta rather than
comprehensive but potentially misleading content.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: restructure BMM and agents documentation by consolidating and flattening index files.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 14:00:26 -06:00

23 KiB

title, description
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 BMad installation, 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:

  • 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

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

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):

// 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.

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 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.

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 (responseJson ready 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).

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

log

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

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

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:

// 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

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 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)

Fixture Composition

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

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:

  1. Config not set: tea_use_playwright_utils: false
  2. Workflow run before config change
  3. 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

Getting Started:

Workflow Guides:

Other Customization:

Understanding the Concepts

Reference


Generated with BMad Method - TEA (Test Architect)