e2e-testing-patterns

Build reliable, fast E2E test suites with Playwright and Cypress. Critical user journey coverage, flaky test elimination, CI/CD integration.

7 stars

Best use case

e2e-testing-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Build reliable, fast E2E test suites with Playwright and Cypress. Critical user journey coverage, flaky test elimination, CI/CD integration.

Teams using e2e-testing-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/e2e-testing-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/wpank/ai/main/skills/testing/e2e-testing-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/e2e-testing-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How e2e-testing-patterns Compares

Feature / Agente2e-testing-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Build reliable, fast E2E test suites with Playwright and Cypress. Critical user journey coverage, flaky test elimination, CI/CD integration.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# E2E Testing Patterns

> Test what users do, not how code works. E2E tests prove the system works as a whole — they're your confidence to ship.


## Installation

### OpenClaw / Moltbot / Clawbot

```bash
npx clawhub@latest install e2e-testing-patterns
```


---

## WHAT This Skill Does

Provides patterns for building end-to-end test suites that:
- Catch regressions before users do
- Run fast enough for CI/CD
- Remain stable (no flaky failures)
- Cover critical user journeys without over-testing

## WHEN To Use

- **Implementing E2E test automation** for a web application
- **Debugging flaky tests** that fail intermittently
- **Setting up CI/CD test pipelines** with browser tests
- **Testing critical user workflows** (auth, checkout, signup)
- **Choosing what to test with E2E** vs unit/integration tests

---

## Test Pyramid — Know Your Layer

```
        /\
       /E2E\         ← FEW: Critical paths only (this skill)
      /─────\
     /Integr\        ← MORE: Component interactions, API contracts
    /────────\
   /Unit Tests\      ← MANY: Fast, isolated, cover edge cases
  /────────────\
```

### What E2E Tests Are For

| E2E Tests ✓ | NOT E2E Tests ✗ |
|-------------|-----------------|
| Critical user journeys (login → dashboard → action → logout) | Unit-level logic (use unit tests) |
| Multi-step flows (checkout, onboarding wizard) | API contracts (use integration tests) |
| Cross-browser compatibility | Edge cases (too slow, use unit tests) |
| Real API integration | Internal implementation details |
| Authentication flows | Component visual states (use Storybook) |

**Rule of thumb:** If it would devastate your business to break, E2E test it. If it's just inconvenient, test it faster with unit/integration tests.

---

## Core Principles

| Principle | Why | How |
|-----------|-----|-----|
| **Test behavior, not implementation** | Survives refactors | Assert on user-visible outcomes, not DOM structure |
| **Independent tests** | Parallelizable, debuggable | Each test creates its own data, cleans up after |
| **Deterministic waits** | No flakiness | Wait for conditions, not fixed timeouts |
| **Stable selectors** | Survives UI changes | Use `data-testid`, roles, labels — never CSS classes |
| **Fast feedback** | Developers run them | Mock external services, parallelize, shard |

---

## Playwright Patterns

### Configuration

```typescript
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./e2e",
  timeout: 30000,
  expect: { timeout: 5000 },
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [["html"], ["junit", { outputFile: "results.xml" }]],
  use: {
    baseURL: "http://localhost:3000",
    trace: "on-first-retry",
    screenshot: "only-on-failure",
    video: "retain-on-failure",
  },
  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
    { name: "webkit", use: { ...devices["Desktop Safari"] } },
    { name: "mobile", use: { ...devices["iPhone 13"] } },
  ],
});
```

### Pattern: Page Object Model

Encapsulate page logic. Tests read like user stories.

```typescript
// pages/LoginPage.ts
import { Page, Locator } from "@playwright/test";

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByLabel("Email");
    this.passwordInput = page.getByLabel("Password");
    this.loginButton = page.getByRole("button", { name: "Login" });
    this.errorMessage = page.getByRole("alert");
  }

  async goto() {
    await this.page.goto("/login");
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }
}

// tests/login.spec.ts
import { test, expect } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";

test("successful login redirects to dashboard", async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login("user@example.com", "password123");

  await expect(page).toHaveURL("/dashboard");
  await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
});
```

### Pattern: Fixtures for Test Data

Create and clean up test data automatically.

```typescript
// fixtures/test-data.ts
import { test as base } from "@playwright/test";

export const test = base.extend<{ testUser: TestUser }>({
  testUser: async ({}, use) => {
    // Setup: Create user
    const user = await createTestUser({
      email: `test-${Date.now()}@example.com`,
      password: "Test123!@#",
    });

    await use(user);

    // Teardown: Clean up
    await deleteTestUser(user.id);
  },
});

// Usage — testUser is created before, deleted after
test("user can update profile", async ({ page, testUser }) => {
  await page.goto("/login");
  await page.getByLabel("Email").fill(testUser.email);
  // ...
});
```

### Pattern: Smart Waiting

Never use fixed timeouts. Wait for specific conditions.

```typescript
// ❌ FLAKY: Fixed timeout
await page.waitForTimeout(3000);

// ✅ STABLE: Wait for conditions
await page.waitForLoadState("networkidle");
await page.waitForURL("/dashboard");

// ✅ BEST: Auto-waiting assertions
await expect(page.getByText("Welcome")).toBeVisible();
await expect(page.getByRole("button", { name: "Submit" })).toBeEnabled();

// Wait for API response
const responsePromise = page.waitForResponse(
  (r) => r.url().includes("/api/users") && r.status() === 200
);
await page.getByRole("button", { name: "Load" }).click();
await responsePromise;
```

### Pattern: Network Mocking

Isolate tests from real external services.

```typescript
test("shows error when API fails", async ({ page }) => {
  // Mock the API response
  await page.route("**/api/users", (route) => {
    route.fulfill({
      status: 500,
      body: JSON.stringify({ error: "Server Error" }),
    });
  });

  await page.goto("/users");
  await expect(page.getByText("Failed to load users")).toBeVisible();
});

test("handles slow network gracefully", async ({ page }) => {
  await page.route("**/api/data", async (route) => {
    await new Promise((r) => setTimeout(r, 3000)); // Simulate delay
    await route.continue();
  });

  await page.goto("/dashboard");
  await expect(page.getByText("Loading...")).toBeVisible();
});
```

---

## Cypress Patterns

### Custom Commands

```typescript
// cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>;
      dataCy(value: string): Chainable<JQuery<HTMLElement>>;
    }
  }
}

Cypress.Commands.add("login", (email, password) => {
  cy.visit("/login");
  cy.get('[data-testid="email"]').type(email);
  cy.get('[data-testid="password"]').type(password);
  cy.get('[data-testid="login-button"]').click();
  cy.url().should("include", "/dashboard");
});

Cypress.Commands.add("dataCy", (value) => {
  return cy.get(`[data-cy="${value}"]`);
});

// Usage
cy.login("user@example.com", "password");
cy.dataCy("submit-button").click();
```

### Network Intercepts

```typescript
// Mock API
cy.intercept("GET", "/api/users", {
  statusCode: 200,
  body: [{ id: 1, name: "John" }],
}).as("getUsers");

cy.visit("/users");
cy.wait("@getUsers");
cy.get('[data-testid="user-list"]').children().should("have.length", 1);
```

---

## Selector Strategy

| Priority | Selector Type | Example | Why |
|----------|--------------|---------|-----|
| 1 | **Role + name** | `getByRole("button", { name: "Submit" })` | Accessible, user-facing |
| 2 | **Label** | `getByLabel("Email address")` | Accessible, semantic |
| 3 | **data-testid** | `getByTestId("checkout-form")` | Stable, explicit for testing |
| 4 | **Text content** | `getByText("Welcome back")` | User-facing |
| ❌ | CSS classes | `.btn-primary` | Breaks on styling changes |
| ❌ | DOM structure | `div > form > input:nth-child(2)` | Breaks on any restructure |

```typescript
// ❌ BAD: Brittle selectors
cy.get(".btn.btn-primary.submit-button").click();
cy.get("div > form > div:nth-child(2) > input").type("text");

// ✅ GOOD: Stable selectors
page.getByRole("button", { name: "Submit" }).click();
page.getByLabel("Email address").fill("user@example.com");
page.getByTestId("email-input").fill("user@example.com");
```

---

## Visual Regression Testing

```typescript
// Playwright visual comparisons
test("homepage looks correct", async ({ page }) => {
  await page.goto("/");
  await expect(page).toHaveScreenshot("homepage.png", {
    fullPage: true,
    maxDiffPixels: 100,
  });
});

test("button states", async ({ page }) => {
  const button = page.getByRole("button", { name: "Submit" });

  await expect(button).toHaveScreenshot("button-default.png");

  await button.hover();
  await expect(button).toHaveScreenshot("button-hover.png");
});
```

---

## Accessibility Testing

```typescript
// npm install @axe-core/playwright
import AxeBuilder from "@axe-core/playwright";

test("page has no accessibility violations", async ({ page }) => {
  await page.goto("/");

  const results = await new AxeBuilder({ page })
    .exclude("#third-party-widget")  // Exclude things you can't control
    .analyze();

  expect(results.violations).toEqual([]);
});
```

---

## Debugging Failed Tests

```bash
# Run in headed mode (see the browser)
npx playwright test --headed

# Debug mode (step through)
npx playwright test --debug

# Show trace viewer for failed tests
npx playwright show-report
```

```typescript
// Add test steps for better failure reports
test("checkout flow", async ({ page }) => {
  await test.step("Add item to cart", async () => {
    await page.goto("/products");
    await page.getByRole("button", { name: "Add to Cart" }).click();
  });

  await test.step("Complete checkout", async () => {
    await page.goto("/checkout");
    // ... if this fails, you know which step
  });
});

// Pause for manual inspection
await page.pause();
```

---

## Flaky Test Checklist

When a test fails intermittently, check:

| Issue | Fix |
|-------|-----|
| Fixed `waitForTimeout()` calls | Replace with `waitForSelector()` or expect assertions |
| Race conditions on page load | Wait for `networkidle` or specific elements |
| Test data pollution | Ensure tests create/clean their own data |
| Animation timing | Wait for animations to complete or disable them |
| Viewport inconsistency | Set explicit viewport in config |
| Random test order issues | Tests must be independent |
| Third-party service flakiness | Mock external APIs |

---

## CI/CD Integration

```yaml
# GitHub Actions example
name: E2E Tests
on: [push, pull_request]

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run build
      - run: npm run start & npx wait-on http://localhost:3000
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/
```

---

## NEVER Do

1. **NEVER use fixed `waitForTimeout()` or `cy.wait(ms)`** — they cause flaky tests and slow down suites
2. **NEVER rely on CSS classes or DOM structure for selectors** — use roles, labels, or data-testid
3. **NEVER share state between tests** — each test must be completely independent
4. **NEVER test implementation details** — test what users see and do, not internal structure
5. **NEVER skip cleanup** — always delete test data you created, even on failure
6. **NEVER test everything with E2E** — reserve for critical paths; use faster tests for edge cases
7. **NEVER ignore flaky tests** — fix them immediately or delete them; a flaky test is worse than no test
8. **NEVER hardcode test data in selectors** — use dynamic waits for content that varies

---

## Quick Reference

### Playwright Commands

```typescript
// Navigation
await page.goto("/path");
await page.goBack();
await page.reload();

// Interactions
await page.click("selector");
await page.fill("selector", "text");
await page.type("selector", "text");  // Types character by character
await page.selectOption("select", "value");
await page.check("checkbox");

// Assertions
await expect(page).toHaveURL("/expected");
await expect(locator).toBeVisible();
await expect(locator).toHaveText("expected");
await expect(locator).toBeEnabled();
await expect(locator).toHaveCount(3);
```

### Cypress Commands

```typescript
// Navigation
cy.visit("/path");
cy.go("back");
cy.reload();

// Interactions
cy.get("selector").click();
cy.get("selector").type("text");
cy.get("selector").clear().type("text");
cy.get("select").select("value");
cy.get("checkbox").check();

// Assertions
cy.url().should("include", "/expected");
cy.get("selector").should("be.visible");
cy.get("selector").should("have.text", "expected");
cy.get("selector").should("have.length", 3);
```

Related Skills

testing-workflow

7
from wpank/ai

Meta-skill that orchestrates comprehensive testing across a project by coordinating testing-patterns, e2e-testing, and testing agents. Use when setting up testing for a new project, improving coverage for an existing project, establishing a testing strategy, or verifying quality before a release.

testing-patterns

7
from wpank/ai

Unit, integration, and E2E testing patterns with framework-specific guidance. Use when asked to "write tests", "add test coverage", "testing strategy", "test this function", "create test suite", "fix flaky tests", or "improve test quality".

websocket-hub-patterns

7
from wpank/ai

Horizontally-scalable WebSocket hub pattern with lazy Redis subscriptions, connection registry, and graceful shutdown. Use when building real-time WebSocket servers that scale across multiple instances. Triggers on WebSocket hub, WebSocket scaling, connection registry, Redis WebSocket, real-time gateway, horizontal scaling.

workflow-patterns

7
from wpank/ai

Systematic task implementation using TDD, phase checkpoints, and structured commits. Ensures quality through red-green-refactor cycles, 80% coverage gates, and verification protocols before proceeding.

estimation-patterns

7
from wpank/ai

Practical estimation techniques for software tasks — methods comparison, decomposition, complexity multipliers, buffer calculation, bias awareness, and communication strategies. Use when estimating features, sprint planning, or presenting timelines to stakeholders.

10x-patterns

7
from wpank/ai

Patterns and practices that dramatically accelerate development velocity. Covers parallel execution, automation, feedback loops, workflow optimization, and anti-pattern avoidance. Use when starting projects, planning sprints, optimizing workflows, or onboarding developers.

react-composition-patterns

7
from wpank/ai

No description provided.

loading-state-patterns

7
from wpank/ai

Patterns for skeleton loaders, shimmer effects, and loading states that match design system aesthetics. Covers skeleton components, shimmer animations, and progressive loading. Use when building polished loading experiences. Triggers on skeleton, loading state, shimmer, placeholder, loading animation.

design-system-patterns

7
from wpank/ai

Foundational design system architecture — token hierarchies, theming infrastructure, token pipelines, and governance. Use when creating design tokens, implementing theme switching, setting up Style Dictionary, or establishing multi-brand theming. Triggers on design tokens, theme provider, Style Dictionary, token pipeline, multi-brand theming, CSS custom properties architecture.

nodejs-patterns

7
from wpank/ai

WHAT: Production-ready Node.js backend patterns - Express/Fastify setup, layered architecture, middleware, error handling, validation, database integration, authentication, and caching. WHEN: User is building REST APIs, setting up Node.js servers, implementing authentication, integrating databases, adding validation/caching, or structuring backend applications. KEYWORDS: nodejs, node, express, fastify, typescript, api, rest, middleware, authentication, jwt, validation, zod, postgres, mongodb, redis, caching, rate limiting, error handling

microservices-patterns

7
from wpank/ai

No description provided.

architecture-patterns

7
from wpank/ai

No description provided.