搜索

PythonReactSEOPDF
GitHub Skill

e2e-test

End-to-end testing with Playwright. Use when writing E2E tests, implementing page objects, setting up test fixtures, handling authentication in tests, adding visual regression testing, or configuring E2E tests for CI. Covers when to use E2E vs integration tests, critical path identification, page object pattern, test isolation, and CI considerations.

wodsmith☆ 1 仓库 Stars1 仓库 Forks

安装与使用

请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。

查看来源 ↗使用指南

技能说明

E2E Testing with Playwright

E2E tests verify critical user journeys through the entire stack. Highest confidence, slowest to run, most expensive to maintain.

When to Use E2E

Use for: Critical revenue flows, auth flows, multi-step journeys, paths that cannot break in production.

Don't use for: Business logic (unit), API validation (integration), edge cases (unit/integration), component behavior (component tests).

Rule: If integration tests catch it, don't write E2E. E2E is for happy paths and critical flows only.

Critical Path Identification

Tier 1 (Must): Signup/signin, core product actions, revenue flows Tier 2 (Should): Team features, secondary features, admin ops Tier 3 (Nice): Settings, edge workflows, power user features

Start with Tier 1. One signup test > 20 preference tests.

Page Object Pattern

Encapsulate page selectors and interactions for maintainability.

export class LoginPage {
  constructor(private readonly page: Page) {}

  readonly emailInput = () => this.page.locator('input[type="email"]');
  readonly submitButton = () => this.page.locator('button[type="submit"]');

  async goto() { await this.page.goto('/sign-in'); }
  async login(email: string) {
    await this.emailInput().fill(email);
    await this.submitButton().click();
  }
  async assertLoggedIn() { await this.page.waitForURL('/workouts'); }
}

Principles:

  • Selectors as methods (lazy, prevents stale elements)
  • Actions return void
  • Assertions in page objects only if reused
  • One page object per logical page, not per route

Test Fixtures & Setup

// fixtures/auth.ts
export const TEST_USER = { email: 'e2e@test.com', password: 'Test123!' };

export async function loginAsTestUser(page: Page) {
  await page.goto('/sign-in');
  await page.getByLabel(/email/i).fill(TEST_USER.email);
  await page.getByRole('button', { name: /sign in/i }).click();
  await page.waitForURL(/\/(workouts|dashboard)/);
}
// global-setup.ts - runs once before all tests
async function globalSetup() { await seedTestUsers(); }

Key: Pre-seed test users in global setup. Faster and more reliable than creating per-test.

Test Isolation

Strategy 1: Reset per test (slowest) - beforeEach(() => resetDB()) Strategy 2: Unique data (recommended) - const name = \Test-${Date.now()}`` Strategy 3: Read-only (fastest) - global seed, no mutations

Use Strategy 2. Move to 1 only if tests interfere.

Authentication

Strategy 1: Login each test - Simple, slow (2-3s/test) Strategy 2: Session storage - Save once, reuse. Caveat: expires mid-run Strategy 3: API auth - Fastest, bypass UI

// Strategy 2: global-setup.ts
await page.context().storageState({ path: '.auth/user.json' });

// Strategy 3: beforeEach
const session = await createSessionViaAPI(TEST_USER);
await page.context().addCookies([{ name: 'session', value: session.token }]);

Use Strategy 3 for most tests. Use Strategy 1 only to test login flow itself.

Visual Regression

test('page looks correct', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('baseline.png');
});

Best practices:

  • Disable animations: page.emulateMedia({ reducedMotion: 'reduce' })
  • Stabilize dynamic content (mock timestamps/randomness)
  • Use sparingly (brittle, slow)
  • Focus on critical UI only (login, checkout)

Don't screenshot every page. Do screenshot where design regressions are risky.

CI Configuration

// playwright.config.ts
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? '50%' : undefined,
  reporter: process.env.CI ? [['blob'], ['github']] : [['html']],
});

Flakiness mitigation:

  • Explicit waits: await page.waitForSelector('.result') before asserting
  • Semantic selectors: getByRole('button', { name: /submit/i })
  • Retry in CI: 2 retries catches transient issues

Speed:

  • Parallelize (4+ workers in CI)
  • API for setup (bypass UI)
  • Shard across machines
  • Run on critical branches only (main, release)

Decision Tree

Single function → Unit | API endpoint → Integration | Multi-page flow → E2E | Business logic → Integration

WODsmith Structure

e2e/
├── fixtures/auth.ts      # Login helpers
├── pages/login.ts        # Page objects
├── auth.spec.ts          # Test specs
├── global-setup.ts       # DB seeding
└── playwright.config.ts

Global setup seeds DB. Fixtures provide auth. Page objects encapsulate UI. Specs test journeys.

Common Pitfalls

Testing too much: 10-step test fails at step 7 → unclear what broke. Split into focused tests.

No cleanup: Use unique data (Test-${Date.now()}) instead of cleanup. No conflicts, faster.

Implicit waits: Always waitForSelector() before assertions. Prevents race conditions.

References

See WODsmith e2e/ directory for real patterns: fixtures, page objects, global setup.

使用示例

查看来源仓库中的使用示例与配置说明。

查看来源 ↗

来源历史

收录时间
2026-01-20
索引更新时间
2026-02-03
Branch
main

收录与同步日期属于本站记录。源码修改历史请查看来源仓库。

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!