From 00b541f5d406e9c4161a2984b72acce4ed774495 Mon Sep 17 00:00:00 2001 From: Murat K Ozcan <34237651+muratkeremozcan@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:34:08 -0600 Subject: [PATCH] feat: playwright-utils integration (#954) * feat: playwright-utils integration * removed the temp plan file, and addressed changelog * feat: edited the installer question for pw-utils * feat: even more n00b friendly install prompt * Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/bmm/_module-installer/install-config.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Murat Ozcan Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 10 + README.md | 4 +- .../bmm/_module-installer/install-config.yaml | 10 +- src/modules/bmm/docs/test-architecture.md | 26 +- .../bmm/testarch/knowledge/api-request.md | 303 ++++++++++++++ .../bmm/testarch/knowledge/auth-session.md | 356 ++++++++++++++++ src/modules/bmm/testarch/knowledge/burn-in.md | 273 +++++++++++++ .../bmm/testarch/knowledge/file-utils.md | 260 ++++++++++++ .../knowledge/fixtures-composition.md | 382 ++++++++++++++++++ .../knowledge/intercept-network-call.md | 280 +++++++++++++ src/modules/bmm/testarch/knowledge/log.md | 294 ++++++++++++++ .../knowledge/network-error-monitor.md | 272 +++++++++++++ .../testarch/knowledge/network-recorder.md | 265 ++++++++++++ .../bmm/testarch/knowledge/overview.md | 284 +++++++++++++ src/modules/bmm/testarch/knowledge/recurse.md | 296 ++++++++++++++ src/modules/bmm/testarch/tea-index.csv | 11 + .../workflows/testarch/atdd/instructions.md | 26 +- .../testarch/automate/instructions.md | 25 +- .../bmm/workflows/testarch/ci/instructions.md | 19 +- .../testarch/framework/instructions.md | 28 +- .../testarch/test-design/instructions.md | 10 +- .../testarch/test-review/instructions.md | 36 +- 22 files changed, 3449 insertions(+), 21 deletions(-) create mode 100644 src/modules/bmm/testarch/knowledge/api-request.md create mode 100644 src/modules/bmm/testarch/knowledge/auth-session.md create mode 100644 src/modules/bmm/testarch/knowledge/burn-in.md create mode 100644 src/modules/bmm/testarch/knowledge/file-utils.md create mode 100644 src/modules/bmm/testarch/knowledge/fixtures-composition.md create mode 100644 src/modules/bmm/testarch/knowledge/intercept-network-call.md create mode 100644 src/modules/bmm/testarch/knowledge/log.md create mode 100644 src/modules/bmm/testarch/knowledge/network-error-monitor.md create mode 100644 src/modules/bmm/testarch/knowledge/network-recorder.md create mode 100644 src/modules/bmm/testarch/knowledge/overview.md create mode 100644 src/modules/bmm/testarch/knowledge/recurse.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e2350517..ccab5843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## [Unreleased] +### Added + +- **Playwright Utils Integration**: Test Architect now supports `@seontechnologies/playwright-utils` integration + - Installation prompt with `use_playwright_utils` configuration flag (mirrors tea_use_mcp_enhancements pattern) + - 11 comprehensive knowledge fragments covering ALL utilities: overview, api-request, network-recorder, auth-session, intercept-network-call, recurse, log, file-utils, burn-in, network-error-monitor, fixtures-composition + - Adaptive workflow recommendations in 6 workflows: automate (CRITICAL), framework, test-review, ci, atdd, test-design (light mention) + - 32 total knowledge fragments (21 core patterns + 11 playwright-utils) + - Context-aware fragment loading preserves existing behavior when flag is false + - Production-ready utilities from SEON Technologies now integrated with TEA's proven testing patterns + ## [6.0.0-alpha.12] **Release: November 19, 2025** diff --git a/README.md b/README.md index 071eaa56..0c64ce83 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,8 @@ Each phase has specialized workflows and agents working together to deliver exce | UX Designer | Test Architect | Analyst | BMad Master | | Tech Writer | Game Architect | Game Designer | Game Developer | +**Test Architect** integrates with `@seontechnologies/playwright-utils` for production-ready fixture-based utilities. + Each agent brings deep expertise and can be customized to match your team's style. ## 📦 What's Included @@ -162,7 +164,7 @@ For contributors working on the BMad codebase: npm test # Development commands -npm run lint # Check code style +npm run lint:fix # Fix code style npm run format:fix # Auto-format code npm run bundle # Build web bundles ``` diff --git a/src/modules/bmm/_module-installer/install-config.yaml b/src/modules/bmm/_module-installer/install-config.yaml index 901027e3..3a322e98 100644 --- a/src/modules/bmm/_module-installer/install-config.yaml +++ b/src/modules/bmm/_module-installer/install-config.yaml @@ -42,7 +42,15 @@ sprint_artifacts: # TEA Agent Configuration tea_use_mcp_enhancements: - prompt: "Enable Test Architect Playwright MCP capabilities (healing, exploratory, verification)?" + prompt: "Enable Test Architect Playwright MCP capabilities (healing, exploratory, verification)? You have to setup your MCPs yourself; refer to test-architecture.md for hints." + default: false + result: "{value}" + +tea_use_playwright_utils: + prompt: + - "Are you using playwright-utils (@seontechnologies/playwright-utils) in your project?" + - "This adds fixture-based utilities for auth, API requests, network recording, polling, intercept, recurse, logging, file download handling, and burn-in." + - "You must install packages yourself, or use test architect's *framework command." default: false result: "{value}" # desired_mcp_tools: diff --git a/src/modules/bmm/docs/test-architecture.md b/src/modules/bmm/docs/test-architecture.md index 1e985a05..b0563445 100644 --- a/src/modules/bmm/docs/test-architecture.md +++ b/src/modules/bmm/docs/test-architecture.md @@ -167,13 +167,35 @@ src/modules/bmm/ TEA uniquely requires: -- **Extensive domain knowledge**: 21 fragments, 12,821 lines covering test patterns, CI/CD, fixtures, quality practices, healing strategies +- **Extensive domain knowledge**: 32 fragments covering test patterns, CI/CD, fixtures, quality practices, healing strategies, and optional playwright-utils integration - **Centralized reference system**: `tea-index.csv` for on-demand fragment loading during workflow execution - **Cross-cutting concerns**: Domain-specific testing patterns (vs project-specific artifacts like PRDs/stories) -- **Optional MCP integration**: Healing, exploratory, and verification modes for enhanced testing capabilities +- **Optional integrations**: MCP capabilities (healing, exploratory, verification) and playwright-utils support This architecture enables TEA to maintain consistent, production-ready testing patterns across all BMad projects while operating across multiple development phases. +### Playwright Utils Integration + +TEA optionally integrates with `@seontechnologies/playwright-utils`, an open-source library providing fixture-based utilities for Playwright tests. + +**Installation:** + +```bash +npm install -D @seontechnologies/playwright-utils +``` + +**Enable during BMAD installation** by answering "Yes" when prompted. + +**Supported utilities (11 total):** + +- api-request, network-recorder, auth-session, intercept-network-call, recurse +- log, file-utils, burn-in, network-error-monitor +- fixtures-composition (integration patterns) + +**Workflows adapt:** automate, framework, test-review, ci, atdd (+ light mention in test-design). + +**Knowledge base:** 32 total fragments (21 core patterns + 11 playwright-utils) + ## High-Level Cheat Sheets diff --git a/src/modules/bmm/testarch/knowledge/api-request.md b/src/modules/bmm/testarch/knowledge/api-request.md new file mode 100644 index 00000000..b47bfc4f --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/api-request.md @@ -0,0 +1,303 @@ +# API Request Utility + +## Principle + +Use typed HTTP client with built-in schema validation and automatic retry for server errors. The utility handles URL resolution, header management, response parsing, and single-line response validation with proper TypeScript support. + +## Rationale + +Vanilla Playwright's request API requires boilerplate for common patterns: + +- Manual JSON parsing (`await response.json()`) +- Repetitive status code checking +- No built-in retry logic for transient failures +- No schema validation +- Complex URL construction + +The `apiRequest` utility provides: + +- **Automatic JSON parsing**: Response body pre-parsed +- **Built-in retry**: 5xx errors retry with exponential backoff +- **Schema validation**: Single-line validation (JSON Schema, Zod, OpenAPI) +- **URL resolution**: Four-tier strategy (explicit > config > Playwright > direct) +- **TypeScript generics**: Type-safe response bodies + +## Pattern Examples + +### Example 1: Basic API Request + +**Context**: Making authenticated API requests with automatic retry and type safety. + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/api-request/fixtures'; + +test('should fetch user data', async ({ apiRequest }) => { + const { status, body } = await apiRequest({ + method: 'GET', + path: '/api/users/123', + headers: { Authorization: 'Bearer token' }, + }); + + expect(status).toBe(200); + expect(body.name).toBe('John Doe'); // TypeScript knows body is User +}); +``` + +**Key Points**: + +- Generic type `` provides TypeScript autocomplete for `body` +- Status and body destructured from response +- Headers passed as object +- Automatic retry for 5xx errors (configurable) + +### Example 2: Schema Validation (Single Line) + +**Context**: Validate API responses match expected schema with single-line syntax. + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/api-request/fixtures'; + +test('should validate response schema', async ({ apiRequest }) => { + // JSON Schema validation + const response = await apiRequest({ + method: 'GET', + path: '/api/users/123', + validateSchema: { + type: 'object', + required: ['id', 'name', 'email'], + properties: { + id: { type: 'string' }, + name: { type: 'string' }, + email: { type: 'string', format: 'email' }, + }, + }, + }); + // Throws if schema validation fails + + // Zod schema validation + import { z } from 'zod'; + + const UserSchema = z.object({ + id: z.string(), + name: z.string(), + email: z.string().email(), + }); + + const response = await apiRequest({ + method: 'GET', + path: '/api/users/123', + validateSchema: UserSchema, + }); + // Response body is type-safe AND validated +}); +``` + +**Key Points**: + +- Single `validateSchema` parameter +- Supports JSON Schema, Zod, YAML files, OpenAPI specs +- Throws on validation failure with detailed errors +- Zero boilerplate validation code + +### Example 3: POST with Body and Retry Configuration + +**Context**: Creating resources with custom retry behavior for error testing. + +**Implementation**: + +```typescript +test('should create user', async ({ apiRequest }) => { + const newUser = { + name: 'Jane Doe', + email: 'jane@example.com', + }; + + const { status, body } = await apiRequest({ + method: 'POST', + path: '/api/users', + body: newUser, // Automatically sent as JSON + headers: { Authorization: 'Bearer token' }, + }); + + expect(status).toBe(201); + expect(body.id).toBeDefined(); +}); + +// Disable retry for error testing +test('should handle 500 errors', async ({ apiRequest }) => { + await expect( + apiRequest({ + method: 'GET', + path: '/api/error', + retryConfig: { maxRetries: 0 }, // Disable retry + }), + ).rejects.toThrow('Request failed with status 500'); +}); +``` + +**Key Points**: + +- `body` parameter auto-serializes to JSON +- Default retry: 5xx errors, 3 retries, exponential backoff +- Disable retry with `retryConfig: { maxRetries: 0 }` +- Only 5xx errors retry (4xx errors fail immediately) + +### Example 4: URL Resolution Strategy + +**Context**: Flexible URL handling for different environments and test contexts. + +**Implementation**: + +```typescript +// Strategy 1: Explicit baseUrl (highest priority) +await apiRequest({ + method: 'GET', + path: '/users', + baseUrl: 'https://api.example.com', // Uses https://api.example.com/users +}); + +// Strategy 2: Config baseURL (from fixture) +import { test } from '@seontechnologies/playwright-utils/api-request/fixtures'; + +test.use({ configBaseUrl: 'https://staging-api.example.com' }); + +test('uses config baseURL', async ({ apiRequest }) => { + await apiRequest({ + method: 'GET', + path: '/users', // Uses https://staging-api.example.com/users + }); +}); + +// Strategy 3: Playwright baseURL (from playwright.config.ts) +// playwright.config.ts +export default defineConfig({ + use: { + baseURL: 'https://api.example.com', + }, +}); + +test('uses Playwright baseURL', async ({ apiRequest }) => { + await apiRequest({ + method: 'GET', + path: '/users', // Uses https://api.example.com/users + }); +}); + +// Strategy 4: Direct path (full URL) +await apiRequest({ + method: 'GET', + path: 'https://api.example.com/users', // Full URL works too +}); +``` + +**Key Points**: + +- Four-tier resolution: explicit > config > Playwright > direct +- Trailing slashes normalized automatically +- Environment-specific baseUrl easy to configure + +### Example 5: Integration with Recurse (Polling) + +**Context**: Waiting for async operations to complete (background jobs, eventual consistency). + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/fixtures'; + +test('should poll until job completes', async ({ apiRequest, recurse }) => { + // Create job + const { body } = await apiRequest({ + method: 'POST', + path: '/api/jobs', + body: { type: 'export' }, + }); + + const jobId = body.id; + + // Poll until ready + const completedJob = await recurse( + () => apiRequest({ method: 'GET', path: `/api/jobs/${jobId}` }), + (response) => response.body.status === 'completed', + { timeout: 60000, interval: 2000 }, + ); + + expect(completedJob.body.result).toBeDefined(); +}); +``` + +**Key Points**: + +- `apiRequest` returns full response object +- `recurse` polls until predicate returns true +- Composable utilities work together seamlessly + +## Comparison with Vanilla Playwright + +| Vanilla Playwright | playwright-utils apiRequest | +| ---------------------------------------------- | ---------------------------------------------------------------------------------- | +| `const resp = await request.get('/api/users')` | `const { status, body } = await apiRequest({ method: 'GET', path: '/api/users' })` | +| `const body = await resp.json()` | Response already parsed | +| `expect(resp.ok()).toBeTruthy()` | Status code directly accessible | +| No retry logic | Auto-retry 5xx errors with backoff | +| No schema validation | Built-in multi-format validation | +| Manual error handling | Descriptive error messages | + +## When to Use + +**Use apiRequest for:** + +- ✅ API endpoint testing +- ✅ Background API calls in UI tests +- ✅ Schema validation needs +- ✅ Tests requiring retry logic +- ✅ Typed API responses + +**Stick with vanilla Playwright for:** + +- Simple one-off requests where utility overhead isn't worth it +- Testing Playwright's native features specifically +- Legacy tests where migration isn't justified + +## Related Fragments + +- `overview.md` - Installation and design principles +- `auth-session.md` - Authentication token management +- `recurse.md` - Polling for async operations +- `fixtures-composition.md` - Combining utilities with mergeTests +- `log.md` - Logging API requests + +## Anti-Patterns + +**❌ Ignoring retry failures:** + +```typescript +try { + await apiRequest({ method: 'GET', path: '/api/unstable' }); +} catch { + // Silent failure - loses retry information +} +``` + +**✅ Let retries happen, handle final failure:** + +```typescript +await expect(apiRequest({ method: 'GET', path: '/api/unstable' })).rejects.toThrow(); // Retries happen automatically, then final error caught +``` + +**❌ Disabling TypeScript benefits:** + +```typescript +const response: any = await apiRequest({ method: 'GET', path: '/users' }); +``` + +**✅ Use generic types:** + +```typescript +const { body } = await apiRequest({ method: 'GET', path: '/users' }); +// body is typed as User[] +``` diff --git a/src/modules/bmm/testarch/knowledge/auth-session.md b/src/modules/bmm/testarch/knowledge/auth-session.md new file mode 100644 index 00000000..3aa456af --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/auth-session.md @@ -0,0 +1,356 @@ +# Auth Session Utility + +## Principle + +Persist authentication tokens to disk and reuse across test runs. Support multiple user identifiers, ephemeral authentication, and worker-specific accounts for parallel execution. Fetch tokens once, use everywhere. + +## Rationale + +Playwright's built-in authentication works but has limitations: + +- Re-authenticates for every test run (slow) +- Single user per project setup +- No token expiration handling +- Manual session management +- Complex setup for multi-user scenarios + +The `auth-session` utility provides: + +- **Token persistence**: Authenticate once, reuse across runs +- **Multi-user support**: Different user identifiers in same test suite +- **Ephemeral auth**: On-the-fly user authentication without disk persistence +- **Worker-specific accounts**: Parallel execution with isolated user accounts +- **Automatic token management**: Checks validity, renews if expired +- **Flexible provider pattern**: Adapt to any auth system (OAuth2, JWT, custom) + +## Pattern Examples + +### Example 1: Basic Auth Session Setup + +**Context**: Configure global authentication that persists across test runs. + +**Implementation**: + +```typescript +// Step 1: Configure in global-setup.ts +import { authStorageInit, setAuthProvider, configureAuthSession, authGlobalInit } from '@seontechnologies/playwright-utils/auth-session'; +import myCustomProvider from './auth/custom-auth-provider'; + +async function globalSetup() { + // Ensure storage directories exist + authStorageInit(); + + // Configure storage path + configureAuthSession({ + authStoragePath: process.cwd() + '/playwright/auth-sessions', + debug: true, + }); + + // Set custom provider (HOW to authenticate) + setAuthProvider(myCustomProvider); + + // Optional: pre-fetch token for default user + await authGlobalInit(); +} + +export default globalSetup; + +// Step 2: Create auth fixture +import { test as base } from '@playwright/test'; +import { createAuthFixtures, setAuthProvider } from '@seontechnologies/playwright-utils/auth-session'; +import myCustomProvider from './custom-auth-provider'; + +// Register provider early +setAuthProvider(myCustomProvider); + +export const test = base.extend(createAuthFixtures()); + +// Step 3: Use in tests +test('authenticated request', async ({ authToken, request }) => { + const response = await request.get('/api/protected', { + headers: { Authorization: `Bearer ${authToken}` }, + }); + + expect(response.ok()).toBeTruthy(); +}); +``` + +**Key Points**: + +- Global setup runs once before all tests +- Token fetched once, reused across all tests +- Custom provider defines your auth mechanism +- Order matters: configure, then setProvider, then init + +### Example 2: Multi-User Authentication + +**Context**: Testing with different user roles (admin, regular user, guest) in same test suite. + +**Implementation**: + +```typescript +import { test } from '../support/auth/auth-fixture'; + +// Option 1: Per-test user override +test('admin actions', async ({ authToken, authOptions }) => { + // Override default user + authOptions.userIdentifier = 'admin'; + + const { authToken: adminToken } = await test.step('Get admin token', async () => { + return { authToken }; // Re-fetches with new identifier + }); + + // Use admin token + const response = await request.get('/api/admin/users', { + headers: { Authorization: `Bearer ${adminToken}` }, + }); +}); + +// Option 2: Parallel execution with different users +test.describe.parallel('multi-user tests', () => { + test('user 1 actions', async ({ authToken }) => { + // Uses default user (e.g., 'user1') + }); + + test('user 2 actions', async ({ authToken, authOptions }) => { + authOptions.userIdentifier = 'user2'; + // Uses different token for user2 + }); +}); +``` + +**Key Points**: + +- Override `authOptions.userIdentifier` per test +- Tokens cached separately per user identifier +- Parallel tests isolated with different users +- Worker-specific accounts possible + +### Example 3: Ephemeral User Authentication + +**Context**: Create temporary test users that don't persist to disk (e.g., testing user creation flow). + +**Implementation**: + +```typescript +import { applyUserCookiesToBrowserContext } from '@seontechnologies/playwright-utils/auth-session'; +import { createTestUser } from '../utils/user-factory'; + +test('ephemeral user test', async ({ context, page }) => { + // Create temporary user (not persisted) + const ephemeralUser = await createTestUser({ + role: 'admin', + permissions: ['delete-users'], + }); + + // Apply auth directly to browser context + await applyUserCookiesToBrowserContext(context, ephemeralUser); + + // Page now authenticated as ephemeral user + await page.goto('/admin/users'); + + await expect(page.getByTestId('delete-user-btn')).toBeVisible(); + + // User and token cleaned up after test +}); +``` + +**Key Points**: + +- No disk persistence (ephemeral) +- Apply cookies directly to context +- Useful for testing user lifecycle +- Clean up automatic when test ends + +### Example 4: Testing Multiple Users in Single Test + +**Context**: Testing interactions between users (messaging, sharing, collaboration features). + +**Implementation**: + +```typescript +test('user interaction', async ({ browser }) => { + // User 1 context + const user1Context = await browser.newContext({ + storageState: './auth-sessions/local/user1/storage-state.json', + }); + const user1Page = await user1Context.newPage(); + + // User 2 context + const user2Context = await browser.newContext({ + storageState: './auth-sessions/local/user2/storage-state.json', + }); + const user2Page = await user2Context.newPage(); + + // User 1 sends message + await user1Page.goto('/messages'); + await user1Page.fill('#message', 'Hello from user 1'); + await user1Page.click('#send'); + + // User 2 receives message + await user2Page.goto('/messages'); + await expect(user2Page.getByText('Hello from user 1')).toBeVisible(); + + // Cleanup + await user1Context.close(); + await user2Context.close(); +}); +``` + +**Key Points**: + +- Each user has separate browser context +- Reference storage state files directly +- Test real-time interactions +- Clean up contexts after test + +### Example 5: Worker-Specific Accounts (Parallel Testing) + +**Context**: Running tests in parallel with isolated user accounts per worker to avoid conflicts. + +**Implementation**: + +```typescript +// playwright.config.ts +export default defineConfig({ + workers: 4, // 4 parallel workers + use: { + // Each worker uses different user + storageState: async ({}, use, testInfo) => { + const workerIndex = testInfo.workerIndex; + const userIdentifier = `worker-${workerIndex}`; + + await use(`./auth-sessions/local/${userIdentifier}/storage-state.json`); + }, + }, +}); + +// Tests run in parallel, each worker with its own user +test('parallel test 1', async ({ page }) => { + // Worker 0 uses worker-0 account + await page.goto('/dashboard'); +}); + +test('parallel test 2', async ({ page }) => { + // Worker 1 uses worker-1 account + await page.goto('/dashboard'); +}); +``` + +**Key Points**: + +- Each worker has isolated user account +- No conflicts in parallel execution +- Token management automatic per worker +- Scales to any number of workers + +## Custom Auth Provider Pattern + +**Context**: Adapt auth-session to your authentication system (OAuth2, JWT, SAML, custom). + +**Minimal provider structure**: + +```typescript +import { type AuthProvider } from '@seontechnologies/playwright-utils/auth-session'; + +const myCustomProvider: AuthProvider = { + getEnvironment: (options) => options.environment || 'local', + + getUserIdentifier: (options) => options.userIdentifier || 'default-user', + + extractToken: (storageState) => { + // Extract token from your storage format + return storageState.cookies.find((c) => c.name === 'auth_token')?.value; + }, + + extractCookies: (tokenData) => { + // Convert token to cookies for browser context + return [ + { + name: 'auth_token', + value: tokenData, + domain: 'example.com', + path: '/', + httpOnly: true, + secure: true, + }, + ]; + }, + + isTokenExpired: (storageState) => { + // Check if token is expired + const expiresAt = storageState.cookies.find((c) => c.name === 'expires_at'); + return Date.now() > parseInt(expiresAt?.value || '0'); + }, + + manageAuthToken: async (request, options) => { + // Main token acquisition logic + // Return storage state with cookies/localStorage + }, +}; + +export default myCustomProvider; +``` + +## Integration with API Request + +```typescript +import { test } from '@seontechnologies/playwright-utils/fixtures'; + +test('authenticated API call', async ({ apiRequest, authToken }) => { + const { status, body } = await apiRequest({ + method: 'GET', + path: '/api/protected', + headers: { Authorization: `Bearer ${authToken}` }, + }); + + expect(status).toBe(200); +}); +``` + +## Related Fragments + +- `overview.md` - Installation and fixture composition +- `api-request.md` - Authenticated API requests +- `fixtures-composition.md` - Merging auth with other utilities + +## Anti-Patterns + +**❌ Calling setAuthProvider after globalSetup:** + +```typescript +async function globalSetup() { + configureAuthSession(...) + await authGlobalInit() // Provider not set yet! + setAuthProvider(provider) // Too late +} +``` + +**✅ Register provider before init:** + +```typescript +async function globalSetup() { + authStorageInit() + configureAuthSession(...) + setAuthProvider(provider) // First + await authGlobalInit() // Then init +} +``` + +**❌ Hardcoding storage paths:** + +```typescript +const storageState = './auth-sessions/local/user1/storage-state.json'; // Brittle +``` + +**✅ Use helper functions:** + +```typescript +import { getTokenFilePath } from '@seontechnologies/playwright-utils/auth-session'; + +const tokenPath = getTokenFilePath({ + environment: 'local', + userIdentifier: 'user1', + tokenFileName: 'storage-state.json', +}); +``` diff --git a/src/modules/bmm/testarch/knowledge/burn-in.md b/src/modules/bmm/testarch/knowledge/burn-in.md new file mode 100644 index 00000000..d8b9f9ec --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/burn-in.md @@ -0,0 +1,273 @@ +# Burn-in Test Runner + +## Principle + +Use smart test selection with git diff analysis to run only affected tests. Filter out irrelevant changes (configs, types, docs) and control test volume with percentage-based execution. Reduce unnecessary CI runs while maintaining reliability. + +## Rationale + +Playwright's `--only-changed` triggers all affected tests: + +- Config file changes trigger hundreds of tests +- Type definition changes cause full suite runs +- No volume control (all or nothing) +- Slow CI pipelines + +The `burn-in` utility provides: + +- **Smart filtering**: Skip patterns for irrelevant files (configs, types, docs) +- **Volume control**: Run percentage of affected tests after filtering +- **Custom dependency analysis**: More accurate than Playwright's built-in +- **CI optimization**: Faster pipelines without sacrificing confidence +- **Process of elimination**: Start with all → filter irrelevant → control volume + +## Pattern Examples + +### Example 1: Basic Burn-in Setup + +**Context**: Run burn-in on changed files compared to main branch. + +**Implementation**: + +```typescript +// Step 1: Create burn-in script +// playwright/scripts/burn-in-changed.ts +import { runBurnIn } from '@seontechnologies/playwright-utils/burn-in' + +async function main() { + await runBurnIn({ + configPath: 'playwright/config/.burn-in.config.ts', + baseBranch: 'main' + }) +} + +main().catch(console.error) + +// Step 2: Create config +// playwright/config/.burn-in.config.ts +import type { BurnInConfig } from '@seontechnologies/playwright-utils/burn-in' + +const config: BurnInConfig = { + // Files that never trigger tests (first filter) + skipBurnInPatterns: [ + '**/config/**', + '**/*constants*', + '**/*types*', + '**/*.md', + '**/README*' + ], + + // Run 30% of remaining tests after skip filter + burnInTestPercentage: 0.3, + + // Burn-in repetition + burnIn: { + repeatEach: 3, // Run each test 3 times + retries: 1 // Allow 1 retry + } +} + +export default config + +// Step 3: Add package.json script +{ + "scripts": { + "test:pw:burn-in-changed": "tsx playwright/scripts/burn-in-changed.ts" + } +} +``` + +**Key Points**: + +- Two-stage filtering: skip patterns, then volume control +- `skipBurnInPatterns` eliminates irrelevant files +- `burnInTestPercentage` controls test volume (0.3 = 30%) +- Custom dependency analysis finds actually affected tests + +### Example 2: CI Integration + +**Context**: Use burn-in in GitHub Actions for efficient CI runs. + +**Implementation**: + +```yaml +# .github/workflows/burn-in.yml +name: Burn-in Changed Tests + +on: + pull_request: + branches: [main] + +jobs: + burn-in: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Need git history + + - name: Setup Node + uses: actions/setup-node@v4 + + - name: Install dependencies + run: npm ci + + - name: Run burn-in on changed tests + run: npm run test:pw:burn-in-changed -- --base-branch=origin/main + + - name: Upload artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: burn-in-failures + path: test-results/ +``` + +**Key Points**: + +- `fetch-depth: 0` for full git history +- Pass `--base-branch=origin/main` for PR comparison +- Upload artifacts only on failure +- Significantly faster than full suite + +### Example 3: How It Works (Process of Elimination) + +**Context**: Understanding the filtering pipeline. + +**Scenario:** + +``` +Git diff finds: 21 changed files +├─ Step 1: Skip patterns filter +│ Removed: 6 files (*.md, config/*, *types*) +│ Remaining: 15 files +│ +├─ Step 2: Dependency analysis +│ Tests that import these 15 files: 45 tests +│ +└─ Step 3: Volume control (30%) + Final tests to run: 14 tests (30% of 45) + +Result: Run 14 targeted tests instead of 147 with --only-changed! +``` + +**Key Points**: + +- Three-stage pipeline: skip → analyze → control +- Custom dependency analysis (not just imports) +- Percentage applies AFTER filtering +- Dramatically reduces CI time + +### Example 4: Environment-Specific Configuration + +**Context**: Different settings for local vs CI environments. + +**Implementation**: + +```typescript +import type { BurnInConfig } from '@seontechnologies/playwright-utils/burn-in'; + +const config: BurnInConfig = { + skipBurnInPatterns: ['**/config/**', '**/*types*', '**/*.md'], + + // CI runs fewer iterations, local runs more + burnInTestPercentage: process.env.CI ? 0.2 : 0.3, + + burnIn: { + repeatEach: process.env.CI ? 2 : 3, + retries: process.env.CI ? 0 : 1, // No retries in CI + }, +}; + +export default config; +``` + +**Key Points**: + +- `process.env.CI` for environment detection +- Lower percentage in CI (20% vs 30%) +- Fewer iterations in CI (2 vs 3) +- No retries in CI (fail fast) + +### Example 5: Sharding Support + +**Context**: Distribute burn-in tests across multiple CI workers. + +**Implementation**: + +```typescript +// burn-in-changed.ts with sharding +import { runBurnIn } from '@seontechnologies/playwright-utils/burn-in'; + +async function main() { + const shardArg = process.argv.find((arg) => arg.startsWith('--shard=')); + + if (shardArg) { + process.env.PW_SHARD = shardArg.split('=')[1]; + } + + await runBurnIn({ + configPath: 'playwright/config/.burn-in.config.ts', + }); +} +``` + +```yaml +# GitHub Actions with sharding +jobs: + burn-in: + strategy: + matrix: + shard: [1/3, 2/3, 3/3] + steps: + - run: npm run test:pw:burn-in-changed -- --shard=${{ matrix.shard }} +``` + +**Key Points**: + +- Pass `--shard=1/3` for parallel execution +- Burn-in respects Playwright sharding +- Distribute across multiple workers +- Reduces total CI time further + +## Integration with CI Workflow + +When setting up CI with `*ci` workflow, recommend burn-in for: + +- Pull request validation +- Pre-merge checks +- Nightly builds (subset runs) + +## Related Fragments + +- `ci-burn-in.md` - Traditional burn-in patterns (10-iteration loops) +- `selective-testing.md` - Test selection strategies +- `overview.md` - Installation + +## Anti-Patterns + +**❌ Over-aggressive skip patterns:** + +```typescript +skipBurnInPatterns: [ + '**/*', // Skips everything! +]; +``` + +**✅ Targeted skip patterns:** + +```typescript +skipBurnInPatterns: ['**/config/**', '**/*types*', '**/*.md', '**/*constants*']; +``` + +**❌ Too low percentage (false confidence):** + +```typescript +burnInTestPercentage: 0.05; // Only 5% - might miss issues +``` + +**✅ Balanced percentage:** + +```typescript +burnInTestPercentage: 0.2; // 20% in CI, provides good coverage +``` diff --git a/src/modules/bmm/testarch/knowledge/file-utils.md b/src/modules/bmm/testarch/knowledge/file-utils.md new file mode 100644 index 00000000..1fa02397 --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/file-utils.md @@ -0,0 +1,260 @@ +# File Utilities + +## Principle + +Read and validate files (CSV, XLSX, PDF, ZIP) with automatic parsing, type-safe results, and download handling. Simplify file operations in Playwright tests with built-in format support and validation helpers. + +## Rationale + +Testing file operations in Playwright requires boilerplate: + +- Manual download handling +- External parsing libraries for each format +- No validation helpers +- Type-unsafe results +- Repetitive path handling + +The `file-utils` module provides: + +- **Auto-parsing**: CSV, XLSX, PDF, ZIP automatically parsed +- **Download handling**: Single function for UI or API-triggered downloads +- **Type-safe**: TypeScript interfaces for parsed results +- **Validation helpers**: Row count, header checks, content validation +- **Format support**: Multiple sheet support (XLSX), text extraction (PDF), archive extraction (ZIP) + +## Pattern Examples + +### Example 1: UI-Triggered CSV Download + +**Context**: User clicks button, CSV downloads, validate contents. + +**Implementation**: + +```typescript +import { handleDownload, readCSV } from '@seontechnologies/playwright-utils/file-utils'; +import path from 'node:path'; + +const DOWNLOAD_DIR = path.join(__dirname, '../downloads'); + +test('should download and validate CSV', async ({ page }) => { + const downloadPath = await handleDownload({ + page, + downloadDir: DOWNLOAD_DIR, + trigger: () => page.click('[data-testid="export-csv"]'), + }); + + const { content } = await readCSV({ filePath: downloadPath }); + + // Validate headers + expect(content.headers).toEqual(['ID', 'Name', 'Email', 'Role']); + + // Validate data + expect(content.data).toHaveLength(10); + expect(content.data[0]).toMatchObject({ + ID: expect.any(String), + Name: expect.any(String), + Email: expect.stringMatching(/@/), + }); +}); +``` + +**Key Points**: + +- `handleDownload` waits for download, returns file path +- `readCSV` auto-parses to `{ headers, data }` +- Type-safe access to parsed content +- Clean up downloads in `afterEach` + +### Example 2: XLSX with Multiple Sheets + +**Context**: Excel file with multiple sheets (e.g., Summary, Details, Errors). + +**Implementation**: + +```typescript +import { readXLSX } from '@seontechnologies/playwright-utils/file-utils'; + +test('should read multi-sheet XLSX', async () => { + const downloadPath = await handleDownload({ + page, + downloadDir: DOWNLOAD_DIR, + trigger: () => page.click('[data-testid="export-xlsx"]'), + }); + + const { content } = await readXLSX({ filePath: downloadPath }); + + // Access specific sheets + const summarySheet = content.sheets.find((s) => s.name === 'Summary'); + const detailsSheet = content.sheets.find((s) => s.name === 'Details'); + + // Validate summary + expect(summarySheet.data).toHaveLength(1); + expect(summarySheet.data[0].TotalRecords).toBe('150'); + + // Validate details + expect(detailsSheet.data).toHaveLength(150); + expect(detailsSheet.headers).toContain('TransactionID'); +}); +``` + +**Key Points**: + +- `sheets` array with `name` and `data` properties +- Access sheets by name +- Each sheet has its own headers and data +- Type-safe sheet iteration + +### Example 3: PDF Text Extraction + +**Context**: Validate PDF report contains expected content. + +**Implementation**: + +```typescript +import { readPDF } from '@seontechnologies/playwright-utils/file-utils'; + +test('should validate PDF report', async () => { + const downloadPath = await handleDownload({ + page, + downloadDir: DOWNLOAD_DIR, + trigger: () => page.click('[data-testid="download-report"]'), + }); + + const { content } = await readPDF({ filePath: downloadPath }); + + // content.text is extracted text from all pages + expect(content.text).toContain('Financial Report Q4 2024'); + expect(content.text).toContain('Total Revenue:'); + + // Validate page count + expect(content.numpages).toBeGreaterThan(10); +}); +``` + +**Key Points**: + +- `content.text` contains all extracted text +- `content.numpages` for page count +- PDF parsing handles multi-page documents +- Search for specific phrases + +### Example 4: ZIP Archive Validation + +**Context**: Validate ZIP contains expected files and extract specific file. + +**Implementation**: + +```typescript +import { readZIP } from '@seontechnologies/playwright-utils/file-utils'; + +test('should validate ZIP archive', async () => { + const downloadPath = await handleDownload({ + page, + downloadDir: DOWNLOAD_DIR, + trigger: () => page.click('[data-testid="download-backup"]'), + }); + + const { content } = await readZIP({ filePath: downloadPath }); + + // Check file list + expect(content.files).toContain('data.csv'); + expect(content.files).toContain('config.json'); + expect(content.files).toContain('readme.txt'); + + // Read specific file from archive + const configContent = content.zip.readAsText('config.json'); + const config = JSON.parse(configContent); + + expect(config.version).toBe('2.0'); +}); +``` + +**Key Points**: + +- `content.files` lists all files in archive +- `content.zip.readAsText()` extracts specific files +- Validate archive structure +- Read and parse individual files from ZIP + +### Example 5: API-Triggered Download + +**Context**: API endpoint returns file download (not UI click). + +**Implementation**: + +```typescript +test('should download via API', async ({ page, request }) => { + const downloadPath = await handleDownload({ + page, + downloadDir: DOWNLOAD_DIR, + trigger: async () => { + const response = await request.get('/api/export/csv', { + headers: { Authorization: 'Bearer token' }, + }); + + if (!response.ok()) { + throw new Error(`Export failed: ${response.status()}`); + } + }, + }); + + const { content } = await readCSV({ filePath: downloadPath }); + + expect(content.data).toHaveLength(100); +}); +``` + +**Key Points**: + +- `trigger` can be async API call +- API must return `Content-Disposition` header +- Still need `page` for download events +- Works with authenticated endpoints + +## Validation Helpers + +```typescript +// CSV validation +const { isValid, errors } = await validateCSV({ + filePath: downloadPath, + expectedRowCount: 10, + requiredHeaders: ['ID', 'Name', 'Email'], +}); + +expect(isValid).toBe(true); +expect(errors).toHaveLength(0); +``` + +## Download Cleanup Pattern + +```typescript +test.afterEach(async () => { + // Clean up downloaded files + await fs.remove(DOWNLOAD_DIR); +}); +``` + +## Related Fragments + +- `overview.md` - Installation and imports +- `api-request.md` - API-triggered downloads +- `recurse.md` - Poll for file generation completion + +## Anti-Patterns + +**❌ Not cleaning up downloads:** + +```typescript +test('creates file', async () => { + await handleDownload({ ... }) + // File left in downloads folder +}) +``` + +**✅ Clean up after tests:** + +```typescript +test.afterEach(async () => { + await fs.remove(DOWNLOAD_DIR); +}); +``` diff --git a/src/modules/bmm/testarch/knowledge/fixtures-composition.md b/src/modules/bmm/testarch/knowledge/fixtures-composition.md new file mode 100644 index 00000000..93d14d0e --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/fixtures-composition.md @@ -0,0 +1,382 @@ +# Fixtures Composition with mergeTests + +## Principle + +Combine multiple Playwright fixtures using `mergeTests` to create a unified test object with all capabilities. Build composable test infrastructure by merging playwright-utils fixtures with custom project fixtures. + +## Rationale + +Using fixtures from multiple sources requires combining them: + +- Importing from multiple fixture files is verbose +- Name conflicts between fixtures +- Duplicate fixture definitions +- No clear single test object + +Playwright's `mergeTests` provides: + +- **Single test object**: All fixtures in one import +- **Conflict resolution**: Handles name collisions automatically +- **Composition pattern**: Mix utilities, custom fixtures, third-party fixtures +- **Type safety**: Full TypeScript support for merged fixtures +- **Maintainability**: One place to manage all fixtures + +## Pattern Examples + +### Example 1: Basic Fixture Merging + +**Context**: Combine multiple playwright-utils fixtures into single test object. + +**Implementation**: + +```typescript +// 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'; + +// Merge all fixtures +export const test = mergeTests(apiRequestFixture, authFixture, recurseFixture); + +export { expect } from '@playwright/test'; +``` + +```typescript +// In your tests - import from merged fixtures +import { test, expect } from '../support/merged-fixtures'; + +test('all utilities available', async ({ + apiRequest, // From api-request fixture + authToken, // From auth fixture + recurse, // From recurse fixture +}) => { + // All fixtures available in single test signature + 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**: + +- Create one `merged-fixtures.ts` per project +- Import test object from merged fixtures in all test files +- All utilities available without multiple imports +- Type-safe access to all fixtures + +### Example 2: Combining with Custom Fixtures + +**Context**: Add project-specific fixtures alongside playwright-utils. + +**Implementation**: + +```typescript +// playwright/support/custom-fixtures.ts - Your project fixtures +import { test as base } from '@playwright/test'; +import { createUser } from './factories/user-factory'; +import { seedDatabase } from './helpers/db-seeder'; + +export const test = base.extend({ + // Custom fixture 1: Auto-seeded user + testUser: async ({ request }, use) => { + const user = await createUser({ role: 'admin' }); + await seedDatabase('users', [user]); + await use(user); + // Cleanup happens automatically + }, + + // Custom fixture 2: Database helpers + db: async ({}, use) => { + await use({ + seed: seedDatabase, + clear: () => seedDatabase.truncate(), + }); + }, +}); + +// playwright/support/merged-fixtures.ts - Combine everything +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 customFixtures } from './custom-fixtures'; + +export const test = mergeTests( + apiRequestFixture, + authFixture, + customFixtures, // Your project fixtures +); + +export { expect } from '@playwright/test'; +``` + +```typescript +// In tests - all fixtures available +import { test, expect } from '../support/merged-fixtures'; + +test('using mixed fixtures', async ({ + apiRequest, // playwright-utils + authToken, // playwright-utils + testUser, // custom + db, // custom +}) => { + // Use playwright-utils + const { body } = await apiRequest({ + method: 'GET', + path: `/api/users/${testUser.id}`, + headers: { Authorization: `Bearer ${authToken}` }, + }); + + // Use custom fixture + await db.clear(); +}); +``` + +**Key Points**: + +- Custom fixtures extend `base` test +- Merge custom with playwright-utils fixtures +- All available in one test signature +- Maintainable separation of concerns + +### Example 3: Full Utility Suite Integration + +**Context**: Production setup with all core playwright-utils and custom fixtures. + +**Implementation**: + +```typescript +// playwright/support/merged-fixtures.ts +import { mergeTests } from '@playwright/test'; + +// Playwright utils fixtures +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 interceptFixture } from '@seontechnologies/playwright-utils/intercept-network-call/fixtures'; +import { test as recurseFixture } from '@seontechnologies/playwright-utils/recurse/fixtures'; +import { test as networkRecorderFixture } from '@seontechnologies/playwright-utils/network-recorder/fixtures'; + +// Custom project fixtures +import { test as customFixtures } from './custom-fixtures'; + +// Merge everything +export const test = mergeTests(apiRequestFixture, authFixture, interceptFixture, recurseFixture, networkRecorderFixture, customFixtures); + +export { expect } from '@playwright/test'; +``` + +```typescript +// In tests +import { test, expect } from '../support/merged-fixtures'; + +test('full integration', async ({ + page, + context, + apiRequest, + authToken, + interceptNetworkCall, + recurse, + networkRecorder, + testUser, // custom +}) => { + // All utilities + custom fixtures available + await networkRecorder.setup(context); + + const usersCall = interceptNetworkCall({ url: '**/api/users' }); + + await page.goto('/users'); + const { responseJson } = await usersCall; + + expect(responseJson).toContainEqual(expect.objectContaining({ id: testUser.id })); +}); +``` + +**Key Points**: + +- One merged-fixtures.ts for entire project +- Combine all playwright-utils you use +- Add custom project fixtures +- Single import in all test files + +### Example 4: Fixture Override Pattern + +**Context**: Override default options for specific test files or describes. + +**Implementation**: + +```typescript +import { test, expect } from '../support/merged-fixtures'; + +// Override auth options for entire file +test.use({ + authOptions: { + userIdentifier: 'admin', + environment: 'staging', + }, +}); + +test('uses admin on staging', async ({ authToken }) => { + // Token is for admin user on staging environment +}); + +// Override for specific describe block +test.describe('manager tests', () => { + test.use({ + authOptions: { + userIdentifier: 'manager', + }, + }); + + test('manager can access reports', async ({ page }) => { + // Uses manager token + await page.goto('/reports'); + }); +}); +``` + +**Key Points**: + +- `test.use()` overrides fixture options +- Can override at file or describe level +- Options merge with defaults +- Type-safe overrides + +### Example 5: Avoiding Fixture Conflicts + +**Context**: Handle name collisions when merging fixtures with same names. + +**Implementation**: + +```typescript +// If two fixtures have same name, last one wins +import { test as fixture1 } from './fixture1'; // has 'user' fixture +import { test as fixture2 } from './fixture2'; // also has 'user' fixture + +const test = mergeTests(fixture1, fixture2); +// fixture2's 'user' overrides fixture1's 'user' + +// Better: Rename fixtures before merging +import { test as base } from '@playwright/test'; +import { test as fixture1 } from './fixture1'; + +const fixture1Renamed = base.extend({ + user1: fixture1._extend.user, // Rename to avoid conflict +}); + +const test = mergeTests(fixture1Renamed, fixture2); +// Now both 'user1' and 'user' available + +// Best: Design fixtures without conflicts +// - Prefix custom fixtures: 'myAppUser', 'myAppDb' +// - Playwright-utils uses descriptive names: 'apiRequest', 'authToken' +``` + +**Key Points**: + +- Last fixture wins in conflicts +- Rename fixtures to avoid collisions +- Design fixtures with unique names +- Playwright-utils uses descriptive names (no conflicts) + +## Recommended Project Structure + +``` +playwright/ +├── support/ +│ ├── merged-fixtures.ts # ⭐ Single test object for project +│ ├── custom-fixtures.ts # Your project-specific fixtures +│ ├── auth/ +│ │ ├── auth-fixture.ts # Auth wrapper (if needed) +│ │ └── custom-auth-provider.ts +│ ├── fixtures/ +│ │ ├── user-fixture.ts +│ │ ├── db-fixture.ts +│ │ └── api-fixture.ts +│ └── utils/ +│ └── factories/ +└── tests/ + ├── api/ + │ └── users.spec.ts # import { test } from '../../support/merged-fixtures' + ├── e2e/ + │ └── login.spec.ts # import { test } from '../../support/merged-fixtures' + └── component/ + └── button.spec.ts # import { test } from '../../support/merged-fixtures' +``` + +## Benefits of Fixture Composition + +**Compared to direct imports:** + +```typescript +// ❌ Without mergeTests (verbose) +import { test as base } from '@playwright/test'; +import { apiRequest } from '@seontechnologies/playwright-utils/api-request'; +import { getAuthToken } from './auth'; +import { createUser } from './factories'; + +test('verbose', async ({ request }) => { + const token = await getAuthToken(); + const user = await createUser(); + const response = await apiRequest({ request, method: 'GET', path: '/api/users' }); + // Manual wiring everywhere +}); + +// ✅ With mergeTests (clean) +import { test } from '../support/merged-fixtures'; + +test('clean', async ({ apiRequest, authToken, testUser }) => { + const { body } = await apiRequest({ method: 'GET', path: '/api/users' }); + // All fixtures auto-wired +}); +``` + +**Reduction:** ~10 lines per test → ~2 lines + +## Related Fragments + +- `overview.md` - Installation and design principles +- `api-request.md`, `auth-session.md`, `recurse.md` - Utilities to merge +- `network-recorder.md`, `intercept-network-call.md`, `log.md` - Additional utilities + +## Anti-Patterns + +**❌ Importing test from multiple fixture files:** + +```typescript +import { test } from '@seontechnologies/playwright-utils/api-request/fixtures'; +// Also need auth... +import { test as authTest } from '@seontechnologies/playwright-utils/auth-session/fixtures'; +// Name conflict! Which test to use? +``` + +**✅ Use merged fixtures:** + +```typescript +import { test } from '../support/merged-fixtures'; +// All utilities available, no conflicts +``` + +**❌ Merging too many fixtures (kitchen sink):** + +```typescript +// Merging 20+ fixtures makes test signature huge +const test = mergeTests(...20 different fixtures) + +test('my test', async ({ fixture1, fixture2, ..., fixture20 }) => { + // Cognitive overload +}) +``` + +**✅ Merge only what you actually use:** + +```typescript +// Merge the 4-6 fixtures your project actually needs +const test = mergeTests(apiRequestFixture, authFixture, recurseFixture, customFixtures); +``` diff --git a/src/modules/bmm/testarch/knowledge/intercept-network-call.md b/src/modules/bmm/testarch/knowledge/intercept-network-call.md new file mode 100644 index 00000000..a175d559 --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/intercept-network-call.md @@ -0,0 +1,280 @@ +# Intercept Network Call Utility + +## Principle + +Intercept network requests with a single declarative call that returns a Promise. Automatically parse JSON responses, support both spy (observe) and stub (mock) patterns, and use powerful glob pattern matching for URL filtering. + +## Rationale + +Vanilla Playwright's network interception requires multiple steps: + +- `page.route()` to setup, `page.waitForResponse()` to capture +- Manual JSON parsing +- Verbose syntax for conditional handling +- Complex filter predicates + +The `interceptNetworkCall` utility provides: + +- **Single declarative call**: Setup and wait in one statement +- **Automatic JSON parsing**: Response pre-parsed, strongly typed +- **Flexible URL patterns**: Glob matching with picomatch +- **Spy or stub modes**: Observe real traffic or mock responses +- **Concise API**: Reduces boilerplate by 60-70% + +## Pattern Examples + +### Example 1: Spy on Network (Observe Real Traffic) + +**Context**: Capture and inspect real API responses for validation. + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/intercept-network-call/fixtures'; + +test('should spy on users API', async ({ page, interceptNetworkCall }) => { + // Setup interception BEFORE navigation + const usersCall = interceptNetworkCall({ + url: '**/api/users', // Glob pattern + }); + + await page.goto('/dashboard'); + + // Wait for response and access parsed data + const { responseJson, status } = await usersCall; + + expect(status).toBe(200); + expect(responseJson).toHaveLength(10); + expect(responseJson[0]).toHaveProperty('name'); +}); +``` + +**Key Points**: + +- Intercept before navigation (critical for race-free tests) +- Returns Promise with `{ responseJson, status, requestBody }` +- Glob patterns (`**` matches any path segment) +- JSON automatically parsed + +### Example 2: Stub Network (Mock Response) + +**Context**: Mock API responses for testing UI behavior without backend. + +**Implementation**: + +```typescript +test('should stub users API', async ({ page, interceptNetworkCall }) => { + const mockUsers = [ + { id: 1, name: 'Test User 1' }, + { id: 2, name: 'Test User 2' }, + ]; + + const usersCall = interceptNetworkCall({ + url: '**/api/users', + fulfillResponse: { + status: 200, + body: mockUsers, + }, + }); + + await page.goto('/dashboard'); + await usersCall; + + // UI shows mocked data + await expect(page.getByText('Test User 1')).toBeVisible(); + await expect(page.getByText('Test User 2')).toBeVisible(); +}); +``` + +**Key Points**: + +- `fulfillResponse` mocks the API +- No backend needed +- Test UI logic in isolation +- Status code and body fully controllable + +### Example 3: Conditional Response Handling + +**Context**: Different responses based on request method or parameters. + +**Implementation**: + +```typescript +test('conditional mocking', async ({ page, interceptNetworkCall }) => { + await interceptNetworkCall({ + url: '**/api/data', + handler: async (route, request) => { + if (request.method() === 'POST') { + // Mock POST success + await route.fulfill({ + status: 201, + body: JSON.stringify({ id: 'new-id', success: true }), + }); + } else if (request.method() === 'GET') { + // Mock GET with data + await route.fulfill({ + status: 200, + body: JSON.stringify([{ id: 1, name: 'Item' }]), + }); + } else { + // Let other methods through + await route.continue(); + } + }, + }); + + await page.goto('/data-page'); +}); +``` + +**Key Points**: + +- `handler` function for complex logic +- Access full `route` and `request` objects +- Can mock, continue, or abort +- Flexible for advanced scenarios + +### Example 4: Error Simulation + +**Context**: Testing error handling in UI when API fails. + +**Implementation**: + +```typescript +test('should handle API errors gracefully', async ({ page, interceptNetworkCall }) => { + // Simulate 500 error + const errorCall = interceptNetworkCall({ + url: '**/api/users', + fulfillResponse: { + status: 500, + body: { error: 'Internal Server Error' }, + }, + }); + + await page.goto('/dashboard'); + await errorCall; + + // Verify UI shows error state + await expect(page.getByText('Failed to load users')).toBeVisible(); + await expect(page.getByTestId('retry-button')).toBeVisible(); +}); + +// Simulate network timeout +test('should handle timeout', async ({ page, interceptNetworkCall }) => { + await interceptNetworkCall({ + url: '**/api/slow', + handler: async (route) => { + // Never respond - simulates timeout + await new Promise(() => {}); + }, + }); + + await page.goto('/slow-page'); + + // UI should show timeout error + await expect(page.getByText('Request timed out')).toBeVisible({ timeout: 10000 }); +}); +``` + +**Key Points**: + +- Mock error statuses (4xx, 5xx) +- Test timeout scenarios +- Validate error UI states +- No real failures needed + +### Example 5: Multiple Intercepts (Order Matters!) + +**Context**: Intercepting different endpoints in same test - setup order is critical. + +**Implementation**: + +```typescript +test('multiple intercepts', async ({ page, interceptNetworkCall }) => { + // ✅ CORRECT: Setup all intercepts BEFORE navigation + const usersCall = interceptNetworkCall({ url: '**/api/users' }); + const productsCall = interceptNetworkCall({ url: '**/api/products' }); + const ordersCall = interceptNetworkCall({ url: '**/api/orders' }); + + // THEN navigate + await page.goto('/dashboard'); + + // Wait for all (or specific ones) + const [users, products] = await Promise.all([usersCall, productsCall]); + + expect(users.responseJson).toHaveLength(10); + expect(products.responseJson).toHaveLength(50); +}); +``` + +**Key Points**: + +- Setup all intercepts before triggering actions +- Use `Promise.all()` to wait for multiple calls +- Order: intercept → navigate → await +- Prevents race conditions + +## URL Pattern Matching + +**Supported glob patterns:** + +```typescript +'**/api/users'; // Any path ending with /api/users +'/api/users'; // Exact match +'**/users/*'; // Any users sub-path +'**/api/{users,products}'; // Either users or products +'**/api/users?id=*'; // With query params +``` + +**Uses picomatch library** - same pattern syntax as Playwright's `page.route()` but cleaner API. + +## Comparison with Vanilla Playwright + +| Vanilla Playwright | intercept-network-call | +| ----------------------------------------------------------- | ------------------------------------------------------------ | +| `await page.route('/api/users', route => route.continue())` | `const call = interceptNetworkCall({ url: '**/api/users' })` | +| `const resp = await page.waitForResponse('/api/users')` | (Combined in single statement) | +| `const json = await resp.json()` | `const { responseJson } = await call` | +| `const status = resp.status()` | `const { status } = await call` | +| Complex filter predicates | Simple glob patterns | + +**Reduction:** ~5-7 lines → ~2-3 lines per interception + +## Related Fragments + +- `network-first.md` - Core pattern: intercept before navigate +- `network-recorder.md` - HAR-based offline testing +- `overview.md` - Fixture composition basics + +## Anti-Patterns + +**❌ Intercepting after navigation:** + +```typescript +await page.goto('/dashboard'); // Navigation starts +const usersCall = interceptNetworkCall({ url: '**/api/users' }); // Too late! +``` + +**✅ Intercept before navigate:** + +```typescript +const usersCall = interceptNetworkCall({ url: '**/api/users' }); // First +await page.goto('/dashboard'); // Then navigate +const { responseJson } = await usersCall; // Then await +``` + +**❌ Ignoring the returned Promise:** + +```typescript +interceptNetworkCall({ url: '**/api/users' }); // Not awaited! +await page.goto('/dashboard'); +// No deterministic wait - race condition +``` + +**✅ Always await the intercept:** + +```typescript +const usersCall = interceptNetworkCall({ url: '**/api/users' }); +await page.goto('/dashboard'); +await usersCall; // Deterministic wait +``` diff --git a/src/modules/bmm/testarch/knowledge/log.md b/src/modules/bmm/testarch/knowledge/log.md new file mode 100644 index 00000000..42ddc228 --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/log.md @@ -0,0 +1,294 @@ +# Log Utility + +## Principle + +Use structured logging that integrates with Playwright's test reports. Support object logging, test step decoration, and multiple log levels (info, step, success, warning, error, debug). + +## Rationale + +Console.log in Playwright tests has limitations: + +- Not visible in HTML reports +- No test step integration +- No structured output +- Lost in terminal noise during CI + +The `log` utility provides: + +- **Report integration**: Logs appear in Playwright HTML reports +- **Test step decoration**: `log.step()` creates collapsible steps in UI +- **Object logging**: Automatically formats objects/arrays +- **Multiple levels**: info, step, success, warning, error, debug +- **Optional console**: Can disable console output but keep report logs + +## Pattern Examples + +### Example 1: Basic Logging Levels + +**Context**: Log different types of messages throughout test execution. + +**Implementation**: + +```typescript +import { log } from '@seontechnologies/playwright-utils'; + +test('logging demo', async ({ page }) => { + await log.step('Navigate to login page'); + await page.goto('/login'); + + await log.info('Entering credentials'); + await page.fill('#username', 'testuser'); + + await log.success('Login successful'); + + await log.warning('Rate limit approaching'); + + await log.debug({ userId: '123', sessionId: 'abc' }); + + // Errors still throw but get logged first + try { + await page.click('#nonexistent'); + } catch (error) { + await log.error('Click failed', false); // false = no console output + throw error; + } +}); +``` + +**Key Points**: + +- `step()` creates collapsible steps in Playwright UI +- `info()`, `success()`, `warning()` for different message types +- `debug()` for detailed data (objects/arrays) +- `error()` with optional console suppression +- All logs appear in test reports + +### Example 2: Object and Array Logging + +**Context**: Log structured data for debugging without cluttering console. + +**Implementation**: + +```typescript +test('object logging', async ({ apiRequest }) => { + const { body } = await apiRequest({ + method: 'GET', + path: '/api/users', + }); + + // Log array of objects + await log.debug(body); // Formatted as JSON in report + + // Log specific object + await log.info({ + totalUsers: body.length, + firstUser: body[0]?.name, + timestamp: new Date().toISOString(), + }); + + // Complex nested structures + await log.debug({ + request: { + method: 'GET', + path: '/api/users', + timestamp: Date.now(), + }, + response: { + status: 200, + body: body.slice(0, 3), // First 3 items + }, + }); +}); +``` + +**Key Points**: + +- Objects auto-formatted as pretty JSON +- Arrays handled gracefully +- Nested structures supported +- All visible in Playwright report attachments + +### Example 3: Test Step Organization + +**Context**: Organize test execution into collapsible steps for better readability in reports. + +**Implementation**: + +```typescript +test('organized with steps', async ({ page, apiRequest }) => { + await log.step('ARRANGE: Setup test data'); + const { body: user } = await apiRequest({ + method: 'POST', + path: '/api/users', + body: { name: 'Test User' }, + }); + + await log.step('ACT: Perform user action'); + await page.goto(`/users/${user.id}`); + await page.click('#edit'); + await page.fill('#name', 'Updated Name'); + await page.click('#save'); + + await log.step('ASSERT: Verify changes'); + await expect(page.getByText('Updated Name')).toBeVisible(); + + // In Playwright UI, each step is collapsible +}); +``` + +**Key Points**: + +- `log.step()` creates collapsible sections +- Organize by Arrange-Act-Assert +- Steps visible in Playwright trace viewer +- Better debugging when tests fail + +### Example 4: Conditional Logging + +**Context**: Log different messages based on environment or test conditions. + +**Implementation**: + +```typescript +test('conditional logging', async ({ page }) => { + const isCI = process.env.CI === 'true'; + + if (isCI) { + await log.info('Running in CI environment'); + } else { + await log.debug('Running locally'); + } + + const isKafkaWorking = await checkKafkaHealth(); + + if (!isKafkaWorking) { + await log.warning('Kafka unavailable - skipping event checks'); + } else { + await log.step('Verifying Kafka events'); + // ... event verification + } +}); +``` + +**Key Points**: + +- Log based on environment +- Skip logging with conditionals +- Use appropriate log levels +- Debug info for local, minimal for CI + +### Example 5: Integration with Auth and API + +**Context**: Log authenticated API requests with tokens (safely). + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/fixtures'; + +// Helper to create safe token preview +function createTokenPreview(token: string): string { + if (!token || token.length < 10) return '[invalid]'; + return `${token.slice(0, 6)}...${token.slice(-4)}`; +} + +test('should log auth flow', async ({ authToken, apiRequest }) => { + await log.info(`Using token: ${createTokenPreview(authToken)}`); + + await log.step('Fetch protected resource'); + const { status, body } = await apiRequest({ + method: 'GET', + path: '/api/protected', + headers: { Authorization: `Bearer ${authToken}` }, + }); + + await log.debug({ + status, + bodyPreview: { + id: body.id, + recordCount: body.data?.length, + }, + }); + + await log.success('Protected resource accessed successfully'); +}); +``` + +**Key Points**: + +- Never log full tokens (security risk) +- Use preview functions for sensitive data +- Combine with auth and API utilities +- Log at appropriate detail level + +## Log Levels Guide + +| Level | When to Use | Shows in Report | Shows in Console | +| --------- | ----------------------------------- | -------------------- | ---------------- | +| `step` | Test organization, major actions | ✅ Collapsible steps | ✅ Yes | +| `info` | General information, state changes | ✅ Yes | ✅ Yes | +| `success` | Successful operations | ✅ Yes | ✅ Yes | +| `warning` | Non-critical issues, skipped checks | ✅ Yes | ✅ Yes | +| `error` | Failures, exceptions | ✅ Yes | ✅ Configurable | +| `debug` | Detailed data, objects | ✅ Yes (attached) | ✅ Configurable | + +## Comparison with console.log + +| console.log | log Utility | +| ----------------------- | ------------------------- | +| Not in reports | Appears in reports | +| No test steps | Creates collapsible steps | +| Manual JSON.stringify() | Auto-formats objects | +| No log levels | 6 log levels | +| Lost in CI output | Preserved in artifacts | + +## Related Fragments + +- `overview.md` - Basic usage and imports +- `api-request.md` - Log API requests +- `auth-session.md` - Log auth flow (safely) +- `recurse.md` - Log polling progress + +## Anti-Patterns + +**❌ Logging objects in steps:** + +```typescript +await log.step({ user: 'test', action: 'create' }); // Shows empty in UI +``` + +**✅ Use strings for steps, objects for debug:** + +```typescript +await log.step('Creating user: test'); // Readable in UI +await log.debug({ user: 'test', action: 'create' }); // Detailed data +``` + +**❌ Logging sensitive data:** + +```typescript +await log.info(`Password: ${password}`); // Security risk! +await log.info(`Token: ${authToken}`); // Full token exposed! +``` + +**✅ Use previews or omit sensitive data:** + +```typescript +await log.info('User authenticated successfully'); // No sensitive data +await log.debug({ tokenPreview: token.slice(0, 6) + '...' }); +``` + +**❌ Excessive logging in loops:** + +```typescript +for (const item of items) { + await log.info(`Processing ${item.id}`); // 100 log entries! +} +``` + +**✅ Log summary or use debug level:** + +```typescript +await log.step(`Processing ${items.length} items`); +await log.debug({ itemIds: items.map((i) => i.id) }); // One log entry +``` diff --git a/src/modules/bmm/testarch/knowledge/network-error-monitor.md b/src/modules/bmm/testarch/knowledge/network-error-monitor.md new file mode 100644 index 00000000..0a2321bd --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/network-error-monitor.md @@ -0,0 +1,272 @@ +# Network Error Monitor + +## Principle + +Automatically detect and fail tests when HTTP 4xx/5xx errors occur during execution. Act like Sentry for tests - catch silent backend failures even when UI passes assertions. + +## Rationale + +Traditional Playwright tests focus on UI: + +- Backend 500 errors ignored if UI looks correct +- Silent failures slip through +- No visibility into background API health +- Tests pass while features are broken + +The `network-error-monitor` provides: + +- **Automatic detection**: All HTTP 4xx/5xx responses tracked +- **Test failures**: Fail tests with backend errors (even if UI passes) +- **Structured artifacts**: JSON reports with error details +- **Smart opt-out**: Disable for validation tests expecting errors +- **Deduplication**: Group repeated errors by pattern +- **Domino effect prevention**: Limit test failures per error pattern + +## Pattern Examples + +### Example 1: Basic Auto-Monitoring + +**Context**: Automatically fail tests when backend errors occur. + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures'; + +// Monitoring automatically enabled +test('should load dashboard', async ({ page }) => { + await page.goto('/dashboard'); + await expect(page.locator('h1')).toContainText('Dashboard'); + + // ✅ Passes if no HTTP errors + // ❌ Fails if any 4xx/5xx errors detected with clear message: + // "Network errors detected: 2 request(s) failed" + // Failed requests: + // GET 500 https://api.example.com/users + // POST 503 https://api.example.com/metrics +}); +``` + +**Key Points**: + +- Zero setup - auto-enabled for all tests +- Fails on any 4xx/5xx response +- Structured error message with URLs and status codes +- JSON artifact attached to test report + +### Example 2: Opt-Out for Validation Tests + +**Context**: Some tests expect errors (validation, error handling, edge cases). + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures'; + +// Opt-out with annotation +test('should show error on invalid input', { annotation: [{ type: 'skipNetworkMonitoring' }] }, async ({ page }) => { + await page.goto('/form'); + await page.click('#submit'); // Triggers 400 error + + // Monitoring disabled - test won't fail on 400 + await expect(page.getByText('Invalid input')).toBeVisible(); +}); + +// Or opt-out entire describe block +test.describe('error handling', { annotation: [{ type: 'skipNetworkMonitoring' }] }, () => { + test('handles 404', async ({ page }) => { + // All tests in this block skip monitoring + }); + + test('handles 500', async ({ page }) => { + // Monitoring disabled + }); +}); +``` + +**Key Points**: + +- Use annotation `{ type: 'skipNetworkMonitoring' }` +- Can opt-out single test or entire describe block +- Monitoring still active for other tests +- Perfect for intentional error scenarios + +### Example 3: Integration with Merged Fixtures + +**Context**: Combine network-error-monitor with other utilities. + +**Implementation**: + +```typescript +// playwright/support/merged-fixtures.ts +import { mergeTests } from '@playwright/test'; +import { test as authFixture } from '@seontechnologies/playwright-utils/auth-session/fixtures'; +import { test as networkErrorMonitorFixture } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures'; + +export const test = mergeTests( + authFixture, + networkErrorMonitorFixture, + // Add other fixtures +); + +// In tests +import { test, expect } from '../support/merged-fixtures'; + +test('authenticated with monitoring', async ({ page, authToken }) => { + // Both auth and network monitoring active + await page.goto('/protected'); + + // Fails if backend returns errors during auth flow +}); +``` + +**Key Points**: + +- Combine with `mergeTests` +- Works alongside all other utilities +- Monitoring active automatically +- No extra setup needed + +### Example 4: Domino Effect Prevention + +**Context**: One failing endpoint shouldn't fail all tests. + +**Implementation**: + +```typescript +// Configuration (internal to utility) +const config = { + maxTestsPerError: 3, // Max 3 tests fail per unique error pattern +}; + +// Scenario: +// Test 1: GET /api/broken → 500 error → Test fails ❌ +// Test 2: GET /api/broken → 500 error → Test fails ❌ +// Test 3: GET /api/broken → 500 error → Test fails ❌ +// Test 4: GET /api/broken → 500 error → Test passes ⚠️ (limit reached, warning logged) +// Test 5: Different error pattern → Test fails ❌ (new pattern, counter resets) +``` + +**Key Points**: + +- Limits cascading failures +- Groups errors by URL + status code pattern +- Warns when limit reached +- Prevents flaky backend from failing entire suite + +### Example 5: Artifact Structure + +**Context**: Debugging failed tests with network error artifacts. + +**Implementation**: + +When test fails due to network errors, artifact attached: + +```json +// test-results/my-test/network-errors.json +{ + "errors": [ + { + "url": "https://api.example.com/users", + "method": "GET", + "status": 500, + "statusText": "Internal Server Error", + "timestamp": "2024-08-13T10:30:45.123Z" + }, + { + "url": "https://api.example.com/metrics", + "method": "POST", + "status": 503, + "statusText": "Service Unavailable", + "timestamp": "2024-08-13T10:30:46.456Z" + } + ], + "summary": { + "totalErrors": 2, + "uniquePatterns": 2 + } +} +``` + +**Key Points**: + +- JSON artifact per failed test +- Full error details (URL, method, status, timestamp) +- Summary statistics +- Easy debugging with structured data + +## Comparison with Manual Error Checks + +| Manual Approach | network-error-monitor | +| ------------------------------------------------------ | -------------------------- | +| `page.on('response', resp => { if (!resp.ok()) ... })` | Auto-enabled, zero setup | +| Check each response manually | Automatic for all requests | +| Custom error tracking logic | Built-in deduplication | +| No structured artifacts | JSON artifacts attached | +| Easy to forget | Never miss a backend error | + +## When to Use + +**Auto-enabled for:** + +- ✅ All E2E tests +- ✅ Integration tests +- ✅ Any test hitting real APIs + +**Opt-out for:** + +- ❌ Validation tests (expecting 4xx) +- ❌ Error handling tests (expecting 5xx) +- ❌ Offline tests (network-recorder playback) + +## Integration with Framework Setup + +In `*framework` workflow, mention network-error-monitor: + +```typescript +// Add to merged-fixtures.ts +import { test as networkErrorMonitorFixture } from '@seontechnologies/playwright-utils/network-error-monitor/fixtures'; + +export const test = mergeTests( + // ... other fixtures + networkErrorMonitorFixture, +); +``` + +## Related Fragments + +- `overview.md` - Installation and fixtures +- `fixtures-composition.md` - Merging with other utilities +- `error-handling.md` - Traditional error handling patterns + +## Anti-Patterns + +**❌ Opting out of monitoring globally:** + +```typescript +// Every test skips monitoring +test.use({ annotation: [{ type: 'skipNetworkMonitoring' }] }); +``` + +**✅ Opt-out only for specific error tests:** + +```typescript +test.describe('error scenarios', { annotation: [{ type: 'skipNetworkMonitoring' }] }, () => { + // Only these tests skip monitoring +}); +``` + +**❌ Ignoring network error artifacts:** + +```typescript +// Test fails, artifact shows 500 errors +// Developer: "Works on my machine" ¯\_(ツ)_/¯ +``` + +**✅ Check artifacts for root cause:** + +```typescript +// Read network-errors.json artifact +// Identify failing endpoint: GET /api/users → 500 +// Fix backend issue before merging +``` diff --git a/src/modules/bmm/testarch/knowledge/network-recorder.md b/src/modules/bmm/testarch/knowledge/network-recorder.md new file mode 100644 index 00000000..ff24cb4e --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/network-recorder.md @@ -0,0 +1,265 @@ +# Network Recorder Utility + +## Principle + +Record network traffic to HAR files during test execution, then play back from disk for offline testing. Enables frontend tests to run in complete isolation from backend services with intelligent stateful CRUD detection for realistic API behavior. + +## Rationale + +Traditional E2E tests require live backend services: + +- Slow (real network latency) +- Flaky (backend instability affects tests) +- Expensive (full stack running for UI tests) +- Coupled (UI tests break when API changes) + +HAR-based recording/playback provides: + +- **True offline testing**: UI tests run without backend +- **Deterministic behavior**: Same responses every time +- **Fast execution**: No network latency +- **Stateful mocking**: CRUD operations work naturally (not just read-only) +- **Environment flexibility**: Map URLs for any environment + +## Pattern Examples + +### Example 1: Basic Record and Playback + +**Context**: The fundamental pattern - record traffic once, play back for all subsequent runs. + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/network-recorder/fixtures'; + +// Set mode in test file (recommended) +process.env.PW_NET_MODE = 'playback'; // or 'record' + +test('CRUD operations work offline', async ({ page, context, networkRecorder }) => { + // Setup recorder (records or plays back based on PW_NET_MODE) + await networkRecorder.setup(context); + + await page.goto('/'); + + // First time (record mode): Records all network traffic to HAR + // Subsequent runs (playback mode): Plays back from HAR (no backend!) + await page.fill('#movie-name', 'Inception'); + await page.click('#add-movie'); + + // Intelligent CRUD detection makes this work offline! + await expect(page.getByText('Inception')).toBeVisible(); +}); +``` + +**Key Points**: + +- `PW_NET_MODE=record` captures traffic to HAR files +- `PW_NET_MODE=playback` replays from HAR files +- Set mode in test file or via environment variable +- HAR files auto-organized by test name +- Stateful mocking detects CRUD operations + +### Example 2: Complete CRUD Flow with HAR + +**Context**: Full create-read-update-delete flow that works completely offline. + +**Implementation**: + +```typescript +process.env.PW_NET_MODE = 'playback'; + +test.describe('Movie CRUD - offline with network recorder', () => { + test.beforeEach(async ({ page, networkRecorder, context }) => { + await networkRecorder.setup(context); + await page.goto('/'); + }); + + test('should add, edit, delete movie browser-only', async ({ page, interceptNetworkCall }) => { + // Create + await page.fill('#movie-name', 'Inception'); + await page.fill('#year', '2010'); + await page.click('#add-movie'); + + // Verify create (reads from stateful HAR) + await expect(page.getByText('Inception')).toBeVisible(); + + // Update + await page.getByText('Inception').click(); + await page.fill('#movie-name', "Inception Director's Cut"); + + const updateCall = interceptNetworkCall({ + method: 'PUT', + url: '/movies/*', + }); + + await page.click('#save'); + await updateCall; // Wait for update + + // Verify update (HAR reflects state change!) + await page.click('#back'); + await expect(page.getByText("Inception Director's Cut")).toBeVisible(); + + // Delete + await page.click(`[data-testid="delete-Inception Director's Cut"]`); + + // Verify delete (HAR reflects removal!) + await expect(page.getByText("Inception Director's Cut")).not.toBeVisible(); + }); +}); +``` + +**Key Points**: + +- Full CRUD operations work offline +- Stateful HAR mocking tracks creates/updates/deletes +- Combine with `interceptNetworkCall` for deterministic waits +- First run records, subsequent runs replay + +### Example 3: Environment Switching + +**Context**: Record in dev environment, play back in CI with different base URLs. + +**Implementation**: + +```typescript +// playwright.config.ts - Map URLs for different environments +export default defineConfig({ + use: { + baseURL: process.env.CI ? 'https://app.ci.example.com' : 'http://localhost:3000', + }, +}); + +// Test works in both environments +test('cross-environment playback', async ({ page, context, networkRecorder }) => { + await networkRecorder.setup(context); + + // In dev: hits http://localhost:3000/api/movies + // In CI: HAR replays with https://app.ci.example.com/api/movies + await page.goto('/movies'); + + // Network recorder auto-maps URLs + await expect(page.getByTestId('movie-list')).toBeVisible(); +}); +``` + +**Key Points**: + +- HAR files record absolute URLs +- Playback maps to current baseURL +- Same HAR works across environments +- No manual URL rewriting needed + +### Example 4: Automatic vs Manual Mode Control + +**Context**: Choose between environment-based switching or in-test mode control. + +**Implementation**: + +```typescript +// Option 1: Environment variable (recommended for CI) +PW_NET_MODE=record npm run test:pw # Record traffic +PW_NET_MODE=playback npm run test:pw # Playback traffic + +// Option 2: In-test control (recommended for development) +process.env.PW_NET_MODE = 'record' // Set at top of test file + +test('my test', async ({ page, context, networkRecorder }) => { + await networkRecorder.setup(context) + // ... +}) + +// Option 3: Auto-fallback (record if HAR missing, else playback) +// This is the default behavior when PW_NET_MODE not set +test('auto mode', async ({ page, context, networkRecorder }) => { + await networkRecorder.setup(context) + // First run: auto-records + // Subsequent runs: auto-plays back +}) +``` + +**Key Points**: + +- Three mode options: record, playback, auto +- `PW_NET_MODE` environment variable +- In-test `process.env.PW_NET_MODE` assignment +- Auto-fallback when no mode specified + +## Why Use This Instead of Native Playwright? + +| Native Playwright (`routeFromHAR`) | network-recorder Utility | +| ---------------------------------- | ------------------------------ | +| ~80 lines setup boilerplate | ~5 lines total | +| Manual HAR file management | Automatic file organization | +| Complex setup/teardown | Automatic cleanup via fixtures | +| **Read-only tests** | **Full CRUD support** | +| **Stateless** | **Stateful mocking** | +| Manual URL mapping | Automatic environment mapping | + +**The game-changer: Stateful CRUD detection** + +Native Playwright HAR playback is stateless - a POST create followed by GET list won't show the created item. This utility intelligently tracks CRUD operations in memory to reflect state changes, making offline tests behave like real APIs. + +## Integration with Other Utilities + +**With interceptNetworkCall** (deterministic waits): + +```typescript +test('use both utilities', async ({ page, context, networkRecorder, interceptNetworkCall }) => { + await networkRecorder.setup(context); + + const createCall = interceptNetworkCall({ + method: 'POST', + url: '/api/movies', + }); + + await page.click('#add-movie'); + await createCall; // Wait for create (works with HAR!) + + // Network recorder provides playback, intercept provides determinism +}); +``` + +## Related Fragments + +- `overview.md` - Installation and fixture patterns +- `intercept-network-call.md` - Combine for deterministic offline tests +- `auth-session.md` - Record authenticated traffic +- `network-first.md` - Core pattern for intercept-before-navigate + +## Anti-Patterns + +**❌ Mixing record and playback in same test:** + +```typescript +process.env.PW_NET_MODE = 'record'; +// ... some test code ... +process.env.PW_NET_MODE = 'playback'; // Don't switch mid-test +``` + +**✅ One mode per test:** + +```typescript +process.env.PW_NET_MODE = 'playback'; // Set once at top + +test('my test', async ({ page, context, networkRecorder }) => { + await networkRecorder.setup(context); + // Entire test uses playback mode +}); +``` + +**❌ Forgetting to call setup:** + +```typescript +test('broken', async ({ page, networkRecorder }) => { + await page.goto('/'); // HAR not active! +}); +``` + +**✅ Always call setup before navigation:** + +```typescript +test('correct', async ({ page, context, networkRecorder }) => { + await networkRecorder.setup(context); // Must setup first + await page.goto('/'); // Now HAR is active +}); +``` diff --git a/src/modules/bmm/testarch/knowledge/overview.md b/src/modules/bmm/testarch/knowledge/overview.md new file mode 100644 index 00000000..3a60e349 --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/overview.md @@ -0,0 +1,284 @@ +# Playwright Utils Overview + +## Principle + +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. + +## Rationale + +Writing Playwright utilities from scratch for every project leads to: + +- Duplicated code across test suites +- Inconsistent patterns and quality +- Maintenance burden when Playwright APIs change +- Missing advanced features (schema validation, HAR recording, auth persistence) + +`@seontechnologies/playwright-utils` provides: + +- **Production-tested utilities**: Used at SEON Technologies in production +- **Functional-first design**: Core logic as pure functions, fixtures for convenience +- **Composable fixtures**: Use `mergeTests` to combine utilities +- **TypeScript support**: Full type safety with generic types +- **Comprehensive coverage**: API requests, auth, network, logging, file handling, burn-in + +## Installation + +```bash +npm install -D @seontechnologies/playwright-utils +``` + +**Peer Dependencies:** + +- `@playwright/test` >= 1.54.1 (required) +- `ajv` >= 8.0.0 (optional - for JSON Schema validation) +- `js-yaml` >= 4.0.0 (optional - for YAML schema support) +- `zod` >= 3.0.0 (optional - for Zod schema validation) + +## Available Utilities + +### Core Testing Utilities + +| Utility | Purpose | Test Context | +| -------------------------- | ------------------------------------------ | ------------- | +| **api-request** | Typed HTTP client with schema validation | API tests | +| **network-recorder** | HAR record/playback for offline testing | UI tests | +| **auth-session** | Token persistence, multi-user auth | Both UI & API | +| **recurse** | Cypress-style polling for async conditions | Both UI & API | +| **intercept-network-call** | Network spy/stub with auto JSON parsing | UI tests | +| **log** | Playwright report-integrated logging | Both UI & API | +| **file-utils** | CSV/XLSX/PDF/ZIP reading & validation | Both UI & API | +| **burn-in** | Smart test selection with git diff | CI/CD | +| **network-error-monitor** | Automatic HTTP 4xx/5xx detection | UI tests | + +## Design Patterns + +### Pattern 1: Functional Core, Fixture Shell + +**Context**: All utilities follow the same architectural pattern - pure function as core, fixture as wrapper. + +**Implementation**: + +```typescript +// 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**: + +- Pure functions testable without Playwright running +- Fixtures inject framework dependencies automatically +- Choose direct import (more control) or fixture (convenience) + +### Pattern 2: Subpath Imports for Tree-Shaking + +**Context**: Import only what you need to keep bundle sizes small. + +**Implementation**: + +```typescript +// 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**: + +- Subpath imports enable tree-shaking +- Keep bundle sizes minimal +- Import from specific paths for production builds + +### Pattern 3: Fixture Composition with mergeTests + +**Context**: Combine multiple playwright-utils fixtures with your own custom fixtures. + +**Implementation**: + +```typescript +// 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'; +``` + +```typescript +// 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 conflicts +- Create one merged-fixtures.ts file per project +- Import test object from your merged fixtures in all tests +- All utilities available in single test signature + +## Integration with Existing Tests + +### Gradual Adoption Strategy + +**1. Start with logging** (zero breaking changes): + +```typescript +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): + +```typescript +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): + +```typescript +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. + +## Related Fragments + +- `api-request.md` - HTTP client with schema validation +- `network-recorder.md` - HAR-based offline testing +- `auth-session.md` - Token management +- `intercept-network-call.md` - Network interception +- `recurse.md` - Polling patterns +- `log.md` - Logging utility +- `file-utils.md` - File operations +- `fixtures-composition.md` - Advanced mergeTests patterns + +## Anti-Patterns + +**❌ Don't mix direct and fixture imports in same test:** + +```typescript +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:** + +```typescript +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:** + +```typescript +import * as utils from '@seontechnologies/playwright-utils'; // Large bundle +``` + +**✅ Use subpath imports:** + +```typescript +import { apiRequest } from '@seontechnologies/playwright-utils/api-request'; // Small bundle +``` + +## Reference Implementation + +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:** + +- **Test examples:** `playwright/tests` - All utilities in action +- **Framework setup:** `playwright.config.ts`, `playwright/support/merged-fixtures.ts` +- **CI patterns:** `.github/workflows/` - GitHub Actions with sharding, parallelization + +**Quick start:** + +```bash +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. diff --git a/src/modules/bmm/testarch/knowledge/recurse.md b/src/modules/bmm/testarch/knowledge/recurse.md new file mode 100644 index 00000000..aec553a1 --- /dev/null +++ b/src/modules/bmm/testarch/knowledge/recurse.md @@ -0,0 +1,296 @@ +# Recurse (Polling) Utility + +## Principle + +Use Cypress-style polling with Playwright's `expect.poll` to wait for asynchronous conditions. Provides configurable timeout, interval, logging, and post-polling callbacks with enhanced error categorization. + +## Rationale + +Testing async operations (background jobs, eventual consistency, webhook processing) requires polling: + +- Vanilla `expect.poll` is verbose +- No built-in logging for debugging +- Generic timeout errors +- No post-poll hooks + +The `recurse` utility provides: + +- **Clean syntax**: Inspired by cypress-recurse +- **Enhanced errors**: Timeout vs command failure vs predicate errors +- **Built-in logging**: Track polling progress +- **Post-poll callbacks**: Process results after success +- **Type-safe**: Full TypeScript generic support + +## Pattern Examples + +### Example 1: Basic Polling + +**Context**: Wait for async operation to complete with custom timeout and interval. + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/recurse/fixtures'; + +test('should wait for job completion', async ({ recurse, apiRequest }) => { + // Start job + const { body } = await apiRequest({ + method: 'POST', + path: '/api/jobs', + body: { type: 'export' }, + }); + + // Poll until ready + const result = await recurse( + () => apiRequest({ method: 'GET', path: `/api/jobs/${body.id}` }), + (response) => response.body.status === 'completed', + { + timeout: 60000, // 60 seconds max + interval: 2000, // Check every 2 seconds + log: 'Waiting for export job to complete', + }, + ); + + expect(result.body.downloadUrl).toBeDefined(); +}); +``` + +**Key Points**: + +- First arg: command function (what to execute) +- Second arg: predicate function (when to stop) +- Options: timeout, interval, log message +- Returns the value when predicate returns true + +### Example 2: Polling with Assertions + +**Context**: Use assertions directly in predicate for more expressive tests. + +**Implementation**: + +```typescript +test('should poll with assertions', async ({ recurse, apiRequest }) => { + await apiRequest({ + method: 'POST', + path: '/api/events', + body: { type: 'user-created', userId: '123' }, + }); + + // Poll with assertions in predicate + await recurse( + async () => { + const { body } = await apiRequest({ method: 'GET', path: '/api/events/123' }); + return body; + }, + (event) => { + // Use assertions instead of boolean returns + expect(event.processed).toBe(true); + expect(event.timestamp).toBeDefined(); + // If assertions pass, predicate succeeds + }, + { timeout: 30000 }, + ); +}); +``` + +**Key Points**: + +- Predicate can use `expect()` assertions +- If assertions throw, polling continues +- If assertions pass, polling succeeds +- More expressive than boolean returns + +### Example 3: Custom Error Messages + +**Context**: Provide context-specific error messages for timeout failures. + +**Implementation**: + +```typescript +test('custom error on timeout', async ({ recurse, apiRequest }) => { + try { + await recurse( + () => apiRequest({ method: 'GET', path: '/api/status' }), + (res) => res.body.ready === true, + { + timeout: 10000, + error: 'System failed to become ready within 10 seconds - check background workers', + }, + ); + } catch (error) { + // Error message includes custom context + expect(error.message).toContain('check background workers'); + throw error; + } +}); +``` + +**Key Points**: + +- `error` option provides custom message +- Replaces default "Timed out after X ms" +- Include debugging hints in error message +- Helps diagnose failures faster + +### Example 4: Post-Polling Callback + +**Context**: Process or log results after successful polling. + +**Implementation**: + +```typescript +test('post-poll processing', async ({ recurse, apiRequest }) => { + const finalResult = await recurse( + () => apiRequest({ method: 'GET', path: '/api/batch-job/123' }), + (res) => res.body.status === 'completed', + { + timeout: 60000, + post: (result) => { + // Runs after successful polling + console.log(`Job completed in ${result.body.duration}ms`); + console.log(`Processed ${result.body.itemsProcessed} items`); + return result.body; + }, + }, + ); + + expect(finalResult.itemsProcessed).toBeGreaterThan(0); +}); +``` + +**Key Points**: + +- `post` callback runs after predicate succeeds +- Receives the final result +- Can transform or log results +- Return value becomes final `recurse` result + +### Example 5: Integration with API Request (Common Pattern) + +**Context**: Most common use case - polling API endpoints for state changes. + +**Implementation**: + +```typescript +import { test } from '@seontechnologies/playwright-utils/fixtures'; + +test('end-to-end polling', async ({ apiRequest, recurse }) => { + // Trigger async operation + const { body: createResp } = await apiRequest({ + method: 'POST', + path: '/api/data-import', + body: { source: 's3://bucket/data.csv' }, + }); + + // Poll until import completes + const importResult = await recurse( + () => apiRequest({ method: 'GET', path: `/api/data-import/${createResp.importId}` }), + (response) => { + const { status, rowsImported } = response.body; + return status === 'completed' && rowsImported > 0; + }, + { + timeout: 120000, // 2 minutes for large imports + interval: 5000, // Check every 5 seconds + log: `Polling import ${createResp.importId}`, + }, + ); + + expect(importResult.body.rowsImported).toBeGreaterThan(1000); + expect(importResult.body.errors).toHaveLength(0); +}); +``` + +**Key Points**: + +- Combine `apiRequest` + `recurse` for API polling +- Both from `@seontechnologies/playwright-utils/fixtures` +- Complex predicates with multiple conditions +- Logging shows polling progress in test reports + +## Enhanced Error Types + +The utility categorizes errors for easier debugging: + +```typescript +// TimeoutError - Predicate never returned true +Error: Polling timed out after 30000ms: Job never completed + +// CommandError - Command function threw +Error: Command failed: Request failed with status 500 + +// PredicateError - Predicate function threw (not from assertions) +Error: Predicate failed: Cannot read property 'status' of undefined +``` + +## Comparison with Vanilla Playwright + +| Vanilla Playwright | recurse Utility | +| ----------------------------------------------------------------- | ------------------------------------------------------------------------- | +| `await expect.poll(() => { ... }, { timeout: 30000 }).toBe(true)` | `await recurse(() => { ... }, (val) => val === true, { timeout: 30000 })` | +| No logging | Built-in log option | +| Generic timeout errors | Categorized errors (timeout/command/predicate) | +| No post-poll hooks | `post` callback support | + +## When to Use + +**Use recurse for:** + +- ✅ Background job completion +- ✅ Webhook/event processing +- ✅ Database eventual consistency +- ✅ Cache propagation +- ✅ State machine transitions + +**Stick with vanilla expect.poll for:** + +- Simple UI element visibility (use `expect(locator).toBeVisible()`) +- Single-property checks +- Cases where logging isn't needed + +## Related Fragments + +- `api-request.md` - Combine for API endpoint polling +- `overview.md` - Fixture composition patterns +- `fixtures-composition.md` - Using with mergeTests + +## Anti-Patterns + +**❌ Using hard waits instead of polling:** + +```typescript +await page.click('#export'); +await page.waitForTimeout(5000); // Arbitrary wait +expect(await page.textContent('#status')).toBe('Ready'); +``` + +**✅ Poll for actual condition:** + +```typescript +await page.click('#export'); +await recurse( + () => page.textContent('#status'), + (status) => status === 'Ready', + { timeout: 10000 }, +); +``` + +**❌ Polling too frequently:** + +```typescript +await recurse( + () => apiRequest({ method: 'GET', path: '/status' }), + (res) => res.body.ready, + { interval: 100 }, // Hammers API every 100ms! +); +``` + +**✅ Reasonable interval for API calls:** + +```typescript +await recurse( + () => apiRequest({ method: 'GET', path: '/status' }), + (res) => res.body.ready, + { interval: 2000 }, // Check every 2 seconds (reasonable) +); +``` diff --git a/src/modules/bmm/testarch/tea-index.csv b/src/modules/bmm/testarch/tea-index.csv index f3a2e68d..cf1efd67 100644 --- a/src/modules/bmm/testarch/tea-index.csv +++ b/src/modules/bmm/testarch/tea-index.csv @@ -20,3 +20,14 @@ test-priorities,Test Priorities Matrix,"P0–P3 criteria, coverage targets, exec test-healing-patterns,Test Healing Patterns,"Common failure patterns and automated fixes","healing,debugging,patterns",knowledge/test-healing-patterns.md selector-resilience,Selector Resilience,"Robust selector strategies and debugging techniques","selectors,locators,debugging",knowledge/selector-resilience.md timing-debugging,Timing Debugging,"Race condition identification and deterministic wait fixes","timing,async,debugging",knowledge/timing-debugging.md +overview,Playwright Utils Overview,"Installation, design principles, fixture patterns","playwright-utils,fixtures",knowledge/overview.md +api-request,API Request,"Typed HTTP client, schema validation","api,playwright-utils",knowledge/api-request.md +network-recorder,Network Recorder,"HAR record/playback, CRUD detection","network,playwright-utils",knowledge/network-recorder.md +auth-session,Auth Session,"Token persistence, multi-user","auth,playwright-utils",knowledge/auth-session.md +intercept-network-call,Intercept Network Call,"Network spy/stub, JSON parsing","network,playwright-utils",knowledge/intercept-network-call.md +recurse,Recurse Polling,"Async polling, condition waiting","polling,playwright-utils",knowledge/recurse.md +log,Log Utility,"Report logging, structured output","logging,playwright-utils",knowledge/log.md +file-utils,File Utilities,"CSV/XLSX/PDF/ZIP validation","files,playwright-utils",knowledge/file-utils.md +burn-in,Burn-in Runner,"Smart test selection, git diff","ci,playwright-utils",knowledge/burn-in.md +network-error-monitor,Network Error Monitor,"HTTP 4xx/5xx detection","monitoring,playwright-utils",knowledge/network-error-monitor.md +fixtures-composition,Fixtures Composition,"mergeTests composition patterns","fixtures,playwright-utils",knowledge/fixtures-composition.md diff --git a/src/modules/bmm/workflows/testarch/atdd/instructions.md b/src/modules/bmm/workflows/testarch/atdd/instructions.md index 0372e4bd..82068208 100644 --- a/src/modules/bmm/workflows/testarch/atdd/instructions.md +++ b/src/modules/bmm/workflows/testarch/atdd/instructions.md @@ -48,18 +48,38 @@ Generates failing acceptance tests BEFORE implementation following TDD's red-gre - Check data factory patterns - Note naming conventions -4. **Load Knowledge Base Fragments** +4. **Check Playwright Utils Flag** + + Read `{config_source}` and check `config.tea_use_playwright_utils`. + +5. **Load Knowledge Base Fragments** **Critical:** Consult `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv` to load: - - `fixture-architecture.md` - Test fixture patterns with auto-cleanup (pure function → fixture → mergeTests composition, 406 lines, 5 examples) + + **Core Patterns (Always load):** - `data-factories.md` - Factory patterns using faker (override patterns, nested factories, API seeding, 498 lines, 5 examples) - `component-tdd.md` - Component test strategies (red-green-refactor, provider isolation, accessibility, visual regression, 480 lines, 4 examples) - - `network-first.md` - Route interception patterns (intercept before navigate, HAR capture, deterministic waiting, 489 lines, 5 examples) - `test-quality.md` - Test design principles (deterministic tests, isolated with cleanup, explicit assertions, length limits, execution time optimization, 658 lines, 5 examples) - `test-healing-patterns.md` - Common failure patterns and healing strategies (stale selectors, race conditions, dynamic data, network errors, hard waits, 648 lines, 5 examples) - `selector-resilience.md` - Selector best practices (data-testid > ARIA > text > CSS hierarchy, dynamic patterns, anti-patterns, 541 lines, 4 examples) - `timing-debugging.md` - Race condition prevention and async debugging (network-first, deterministic waiting, anti-patterns, 370 lines, 3 examples) + **If `config.tea_use_playwright_utils: true` (All Utilities):** + - `overview.md` - Playwright utils for ATDD patterns + - `api-request.md` - API test examples with schema validation + - `network-recorder.md` - HAR record/playback for UI acceptance tests + - `auth-session.md` - Auth setup for acceptance tests + - `intercept-network-call.md` - Network interception in ATDD scenarios + - `recurse.md` - Polling for async acceptance criteria + - `log.md` - Logging in ATDD tests + - `file-utils.md` - File download validation in acceptance tests + - `network-error-monitor.md` - Catch silent failures in ATDD + - `fixtures-composition.md` - Composing utilities for ATDD + + **If `config.tea_use_playwright_utils: false`:** + - `fixture-architecture.md` - Test fixture patterns with auto-cleanup (pure function → fixture → mergeTests composition, 406 lines, 5 examples) + - `network-first.md` - Route interception patterns (intercept before navigate, HAR capture, deterministic waiting, 489 lines, 5 examples) + **Halt Condition:** If story has no acceptance criteria or framework is missing, HALT with message: "ATDD requires clear acceptance criteria and test framework setup" --- diff --git a/src/modules/bmm/workflows/testarch/automate/instructions.md b/src/modules/bmm/workflows/testarch/automate/instructions.md index 2dd656b7..55dcebe7 100644 --- a/src/modules/bmm/workflows/testarch/automate/instructions.md +++ b/src/modules/bmm/workflows/testarch/automate/instructions.md @@ -81,16 +81,37 @@ Expands test automation coverage by generating comprehensive test suites at appr - Map tests to source files (coverage gaps) - Check existing fixture and factory patterns -5. **Load Knowledge Base Fragments** +5. **Check Playwright Utils Flag** + + Read `{config_source}` and check `config.tea_use_playwright_utils`. + +6. **Load Knowledge Base Fragments** **Critical:** Consult `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv` to load: + + **Core Testing Patterns (Always load):** - `test-levels-framework.md` - Test level selection (E2E vs API vs Component vs Unit with decision matrix, 467 lines, 4 examples) - `test-priorities-matrix.md` - Priority classification (P0-P3 with automated scoring, risk mapping, 389 lines, 2 examples) - - `fixture-architecture.md` - Test fixture patterns (pure function → fixture → mergeTests, auto-cleanup, 406 lines, 5 examples) - `data-factories.md` - Factory patterns with faker (overrides, nested factories, API seeding, 498 lines, 5 examples) - `selective-testing.md` - Targeted test execution strategies (tag-based, spec filters, diff-based, promotion rules, 727 lines, 4 examples) - `ci-burn-in.md` - Flaky test detection patterns (10-iteration burn-in, sharding, selective execution, 678 lines, 4 examples) - `test-quality.md` - Test design principles (deterministic, isolated, explicit assertions, length/time limits, 658 lines, 5 examples) + + **If `config.tea_use_playwright_utils: true` (Playwright Utils Integration - All Utilities):** + - `overview.md` - Playwright utils installation, design principles, fixture patterns + - `api-request.md` - Typed HTTP client with schema validation + - `network-recorder.md` - HAR record/playback for offline testing + - `auth-session.md` - Token persistence and multi-user support + - `intercept-network-call.md` - Network spy/stub with automatic JSON parsing + - `recurse.md` - Cypress-style polling for async conditions + - `log.md` - Playwright report-integrated logging + - `file-utils.md` - CSV/XLSX/PDF/ZIP reading and validation + - `burn-in.md` - Smart test selection (relevant for CI test generation) + - `network-error-monitor.md` - Automatic HTTP error detection + - `fixtures-composition.md` - mergeTests composition patterns + + **If `config.tea_use_playwright_utils: false` (Traditional Patterns):** + - `fixture-architecture.md` - Test fixture patterns (pure function → fixture → mergeTests, auto-cleanup, 406 lines, 5 examples) - `network-first.md` - Route interception patterns (intercept before navigate, HAR capture, deterministic waiting, 489 lines, 5 examples) **Healing Knowledge (If `{auto_heal_failures}` is true):** diff --git a/src/modules/bmm/workflows/testarch/ci/instructions.md b/src/modules/bmm/workflows/testarch/ci/instructions.md index a96600ae..9bd87940 100644 --- a/src/modules/bmm/workflows/testarch/ci/instructions.md +++ b/src/modules/bmm/workflows/testarch/ci/instructions.md @@ -353,7 +353,11 @@ Scaffolds a production-ready CI/CD quality pipeline with test execution, burn-in ### Knowledge Base Integration -**Critical:** Consult `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv` to identify and load relevant knowledge fragments: +**Critical:** Check configuration and load appropriate fragments. + +Read `{config_source}` and check `config.tea_use_playwright_utils`. + +**Core CI Patterns (Always load):** - `ci-burn-in.md` - Burn-in loop patterns: 10-iteration detection, GitHub Actions workflow, shard orchestration, selective execution (678 lines, 4 examples) - `selective-testing.md` - Changed test detection strategies: tag-based, spec filters, diff-based selection, promotion rules (727 lines, 4 examples) @@ -361,6 +365,19 @@ Scaffolds a production-ready CI/CD quality pipeline with test execution, burn-in - `test-quality.md` - CI-specific test quality criteria: deterministic tests, isolated with cleanup, explicit assertions, length/time optimization (658 lines, 5 examples) - `playwright-config.md` - CI-optimized configuration: parallelization, artifact output, project dependencies, sharding (722 lines, 5 examples) +**If `config.tea_use_playwright_utils: true`:** + +Load playwright-utils CI-relevant fragments: + +- `burn-in.md` - Smart test selection with git diff analysis (very important for CI optimization) +- `network-error-monitor.md` - Automatic HTTP 4xx/5xx detection (recommend in CI pipelines) + +Recommend: + +- Add burn-in script for pull request validation +- Enable network-error-monitor in merged fixtures for catching silent failures +- Reference full docs in `*framework` and `*automate` workflows + ### CI Platform-Specific Guidance **GitHub Actions:** diff --git a/src/modules/bmm/workflows/testarch/framework/instructions.md b/src/modules/bmm/workflows/testarch/framework/instructions.md index ec333056..9f02d69c 100644 --- a/src/modules/bmm/workflows/testarch/framework/instructions.md +++ b/src/modules/bmm/workflows/testarch/framework/instructions.md @@ -349,7 +349,33 @@ The generated `tests/README.md` should include: ### Knowledge Base Integration -**Critical:** Consult `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv` to identify and load relevant knowledge fragments: +**Critical:** Check configuration and load appropriate fragments. + +Read `{config_source}` and check `config.tea_use_playwright_utils`. + +**If `config.tea_use_playwright_utils: true` (Playwright Utils Integration):** + +Consult `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv` and load: + +- `overview.md` - Playwright utils installation and design principles +- `fixtures-composition.md` - mergeTests composition with playwright-utils +- `auth-session.md` - Token persistence setup (if auth needed) +- `api-request.md` - API testing utilities (if API tests planned) +- `burn-in.md` - Smart test selection for CI (recommend during framework setup) +- `network-error-monitor.md` - Automatic HTTP error detection (recommend in merged fixtures) +- `data-factories.md` - Factory patterns with faker (498 lines, 5 examples) + +Recommend installing playwright-utils: + +```bash +npm install -D @seontechnologies/playwright-utils +``` + +Recommend adding burn-in and network-error-monitor to merged fixtures for enhanced reliability. + +**If `config.tea_use_playwright_utils: false` (Traditional Patterns):** + +Consult `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv` and load: - `fixture-architecture.md` - Pure function → fixture → `mergeTests` composition with auto-cleanup (406 lines, 5 examples) - `data-factories.md` - Faker-based factories with overrides, nested factories, API seeding, auto-cleanup (498 lines, 5 examples) diff --git a/src/modules/bmm/workflows/testarch/test-design/instructions.md b/src/modules/bmm/workflows/testarch/test-design/instructions.md index f572af53..c96bf876 100644 --- a/src/modules/bmm/workflows/testarch/test-design/instructions.md +++ b/src/modules/bmm/workflows/testarch/test-design/instructions.md @@ -66,7 +66,13 @@ The workflow auto-detects which mode to use based on project phase. - Note integration points and external system dependencies - Extract NFR requirements (performance SLOs, security requirements, etc.) -2. **Load Knowledge Base Fragments (System-Level)** +2. **Check Playwright Utils Flag** + + Read `{config_source}` and check `config.tea_use_playwright_utils`. + + If true, note that `@seontechnologies/playwright-utils` provides utilities for test implementation. Reference in test design where relevant. + +3. **Load Knowledge Base Fragments (System-Level)** **Critical:** Consult `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv` to load: - `nfr-criteria.md` - NFR validation approach (security, performance, reliability, maintainability) @@ -74,7 +80,7 @@ The workflow auto-detects which mode to use based on project phase. - `risk-governance.md` - Testability risk identification - `test-quality.md` - Quality standards and Definition of Done -3. **Analyze Existing Test Setup (if brownfield)** +4. **Analyze Existing Test Setup (if brownfield)** - Search for existing test directories - Identify current test framework (if any) - Note testability concerns in existing codebase diff --git a/src/modules/bmm/workflows/testarch/test-review/instructions.md b/src/modules/bmm/workflows/testarch/test-review/instructions.md index 91a09ade..0bf378cc 100644 --- a/src/modules/bmm/workflows/testarch/test-review/instructions.md +++ b/src/modules/bmm/workflows/testarch/test-review/instructions.md @@ -49,31 +49,51 @@ This workflow performs comprehensive test quality reviews using TEA's knowledge **Actions:** -1. Load relevant knowledge fragments from `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv`: +1. Check playwright-utils flag: + - Read `{config_source}` and check `config.tea_use_playwright_utils` + +2. Load relevant knowledge fragments from `{project-root}/{bmad_folder}/bmm/testarch/tea-index.csv`: + + **Core Patterns (Always load):** - `test-quality.md` - Definition of Done (deterministic tests, isolated with cleanup, explicit assertions, <300 lines, <1.5 min, 658 lines, 5 examples) - - `fixture-architecture.md` - Pure function → Fixture → mergeTests composition with auto-cleanup (406 lines, 5 examples) - - `network-first.md` - Route intercept before navigate to prevent race conditions (intercept before navigate, HAR capture, deterministic waiting, 489 lines, 5 examples) - `data-factories.md` - Factory functions with faker: overrides, nested factories, API-first setup (498 lines, 5 examples) - `test-levels-framework.md` - E2E vs API vs Component vs Unit appropriateness with decision matrix (467 lines, 4 examples) - - `playwright-config.md` - Environment-based configuration with fail-fast validation (722 lines, 5 examples) - - `component-tdd.md` - Red-Green-Refactor patterns with provider isolation, accessibility, visual regression (480 lines, 4 examples) - `selective-testing.md` - Duplicate coverage detection with tag-based, spec filter, diff-based selection (727 lines, 4 examples) - `test-healing-patterns.md` - Common failure patterns: stale selectors, race conditions, dynamic data, network errors, hard waits (648 lines, 5 examples) - `selector-resilience.md` - Selector best practices (data-testid > ARIA > text > CSS hierarchy, anti-patterns, 541 lines, 4 examples) - `timing-debugging.md` - Race condition prevention and async debugging techniques (370 lines, 3 examples) + + **If `config.tea_use_playwright_utils: true` (All Utilities):** + - `overview.md` - Playwright utils best practices + - `api-request.md` - Validate apiRequest usage patterns + - `network-recorder.md` - Review HAR record/playback implementation + - `auth-session.md` - Check auth token management + - `intercept-network-call.md` - Validate network interception + - `recurse.md` - Review polling patterns + - `log.md` - Check logging best practices + - `file-utils.md` - Validate file operation patterns + - `burn-in.md` - Review burn-in configuration + - `network-error-monitor.md` - Check error monitoring setup + - `fixtures-composition.md` - Validate mergeTests usage + + **If `config.tea_use_playwright_utils: false`:** + - `fixture-architecture.md` - Pure function → Fixture → mergeTests composition with auto-cleanup (406 lines, 5 examples) + - `network-first.md` - Route intercept before navigate to prevent race conditions (489 lines, 5 examples) + - `playwright-config.md` - Environment-based configuration with fail-fast validation (722 lines, 5 examples) + - `component-tdd.md` - Red-Green-Refactor patterns with provider isolation (480 lines, 4 examples) - `ci-burn-in.md` - Flaky test detection with 10-iteration burn-in loop (678 lines, 4 examples) -2. Determine review scope: +3. Determine review scope: - **single**: Review one test file (`test_file_path` provided) - **directory**: Review all tests in directory (`test_dir` provided) - **suite**: Review entire test suite (discover all test files) -3. Auto-discover related artifacts (if `auto_discover_story: true`): +4. Auto-discover related artifacts (if `auto_discover_story: true`): - Extract test ID from filename (e.g., `1.3-E2E-001.spec.ts` → story 1.3) - Search for story file (`story-1.3.md`) - Search for test design (`test-design-story-1.3.md` or `test-design-epic-1.md`) -4. Read story file for context (if available): +5. Read story file for context (if available): - Extract acceptance criteria - Extract priority classification - Extract expected test IDs