> e2e-testing
Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies.
curl "https://skillshub.wtf/affaan-m/everything-claude-code/e2e-testing?format=md"E2E Testing Patterns
Comprehensive Playwright patterns for building stable, fast, and maintainable E2E test suites.
Test File Organization
tests/
├── e2e/
│ ├── auth/
│ │ ├── login.spec.ts
│ │ ├── logout.spec.ts
│ │ └── register.spec.ts
│ ├── features/
│ │ ├── browse.spec.ts
│ │ ├── search.spec.ts
│ │ └── create.spec.ts
│ └── api/
│ └── endpoints.spec.ts
├── fixtures/
│ ├── auth.ts
│ └── data.ts
└── playwright.config.ts
Page Object Model (POM)
import { Page, Locator } from '@playwright/test'
export class ItemsPage {
readonly page: Page
readonly searchInput: Locator
readonly itemCards: Locator
readonly createButton: Locator
constructor(page: Page) {
this.page = page
this.searchInput = page.locator('[data-testid="search-input"]')
this.itemCards = page.locator('[data-testid="item-card"]')
this.createButton = page.locator('[data-testid="create-btn"]')
}
async goto() {
await this.page.goto('/items')
await this.page.waitForLoadState('networkidle')
}
async search(query: string) {
await this.searchInput.fill(query)
await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
await this.page.waitForLoadState('networkidle')
}
async getItemCount() {
return await this.itemCards.count()
}
}
Test Structure
import { test, expect } from '@playwright/test'
import { ItemsPage } from '../../pages/ItemsPage'
test.describe('Item Search', () => {
let itemsPage: ItemsPage
test.beforeEach(async ({ page }) => {
itemsPage = new ItemsPage(page)
await itemsPage.goto()
})
test('should search by keyword', async ({ page }) => {
await itemsPage.search('test')
const count = await itemsPage.getItemCount()
expect(count).toBeGreaterThan(0)
await expect(itemsPage.itemCards.first()).toContainText(/test/i)
await page.screenshot({ path: 'artifacts/search-results.png' })
})
test('should handle no results', async ({ page }) => {
await itemsPage.search('xyznonexistent123')
await expect(page.locator('[data-testid="no-results"]')).toBeVisible()
expect(await itemsPage.getItemCount()).toBe(0)
})
})
Playwright Configuration
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'playwright-results.xml' }],
['json', { outputFile: 'playwright-results.json' }]
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
})
Flaky Test Patterns
Quarantine
test('flaky: complex search', async ({ page }) => {
test.fixme(true, 'Flaky - Issue #123')
// test code...
})
test('conditional skip', async ({ page }) => {
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
// test code...
})
Identify Flakiness
npx playwright test tests/search.spec.ts --repeat-each=10
npx playwright test tests/search.spec.ts --retries=3
Common Causes & Fixes
Race conditions:
// Bad: assumes element is ready
await page.click('[data-testid="button"]')
// Good: auto-wait locator
await page.locator('[data-testid="button"]').click()
Network timing:
// Bad: arbitrary timeout
await page.waitForTimeout(5000)
// Good: wait for specific condition
await page.waitForResponse(resp => resp.url().includes('/api/data'))
Animation timing:
// Bad: click during animation
await page.click('[data-testid="menu-item"]')
// Good: wait for stability
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
await page.waitForLoadState('networkidle')
await page.locator('[data-testid="menu-item"]').click()
Artifact Management
Screenshots
await page.screenshot({ path: 'artifacts/after-login.png' })
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
await page.locator('[data-testid="chart"]').screenshot({ path: 'artifacts/chart.png' })
Traces
await browser.startTracing(page, {
path: 'artifacts/trace.json',
screenshots: true,
snapshots: true,
})
// ... test actions ...
await browser.stopTracing()
Video
// In playwright.config.ts
use: {
video: 'retain-on-failure',
videosPath: 'artifacts/videos/'
}
CI/CD Integration
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
BASE_URL: ${{ vars.STAGING_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Test Report Template
# E2E Test Report
**Date:** YYYY-MM-DD HH:MM
**Duration:** Xm Ys
**Status:** PASSING / FAILING
## Summary
- Total: X | Passed: Y (Z%) | Failed: A | Flaky: B | Skipped: C
## Failed Tests
### test-name
**File:** `tests/e2e/feature.spec.ts:45`
**Error:** Expected element to be visible
**Screenshot:** artifacts/failed.png
**Recommended Fix:** [description]
## Artifacts
- HTML Report: playwright-report/index.html
- Screenshots: artifacts/*.png
- Videos: artifacts/videos/*.webm
- Traces: artifacts/*.zip
Wallet / Web3 Testing
test('wallet connection', async ({ page, context }) => {
// Mock wallet provider
await context.addInitScript(() => {
window.ethereum = {
isMetaMask: true,
request: async ({ method }) => {
if (method === 'eth_requestAccounts')
return ['0x1234567890123456789012345678901234567890']
if (method === 'eth_chainId') return '0x1'
}
}
})
await page.goto('/')
await page.locator('[data-testid="connect-wallet"]').click()
await expect(page.locator('[data-testid="wallet-address"]')).toContainText('0x1234')
})
Financial / Critical Flow Testing
test('trade execution', async ({ page }) => {
// Skip on production — real money
test.skip(process.env.NODE_ENV === 'production', 'Skip on production')
await page.goto('/markets/test-market')
await page.locator('[data-testid="position-yes"]').click()
await page.locator('[data-testid="trade-amount"]').fill('1.0')
// Verify preview
const preview = page.locator('[data-testid="trade-preview"]')
await expect(preview).toContainText('1.0')
// Confirm and wait for blockchain
await page.locator('[data-testid="confirm-trade"]').click()
await page.waitForResponse(
resp => resp.url().includes('/api/trade') && resp.status() === 200,
{ timeout: 30000 }
)
await expect(page.locator('[data-testid="trade-success"]')).toBeVisible()
})
> related_skills --same-repo
> enterprise-agent-ops
Operate long-lived agent workloads with observability, security boundaries, and lifecycle management.
> energy-procurement
energy-procurement skill from affaan-m/everything-claude-code
> docker-patterns
Docker and Docker Compose patterns for local development, container security, networking, volume strategies, and multi-service orchestration.
> dmux-workflows
Multi-agent orchestration using dmux (tmux pane manager for AI agents). Patterns for parallel agent workflows across Claude Code, Codex, OpenCode, and other harnesses. Use when running multiple agent sessions in parallel or coordinating multi-agent development workflows.