e2e-testing

Playwright E2E testing patterns with Page Object Model, configuration, CI/CD setup, and flaky test strategies. Provides Page Object Model structure for maintainable test organization, with reusable page classes that encapsulate locators and navigation logic Covers multi-browser testing across Chromium, Firefox, WebKit, and mobile devices with configurable retries, parallelization, and artifact capture (screenshots, videos, traces) Includes flaky test identification and remediation patterns: quarantine techniques, race condition fixes, network and animation timing strategies, and repeat-test commands for diagnosis Demonstrates CI/CD integration via GitHub Actions with artifact upload, plus specialized patterns for wallet/Web3 mocking and financial transaction testing with production safeguards

INSTALLATION
npx skills add https://github.com/affaan-m/everything-claude-code --skill e2e-testing
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.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()

})
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card