e2e-test-optimizer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

E2E Test Optimizer

E2E测试优化方案

Specialized skill for optimizing Playwright E2E tests to eliminate flakiness, improve speed, and ensure reliable CI/CD execution.
这是一款专门用于优化Playwright E2E测试的方案,旨在消除测试不稳定问题、提升测试速度,并确保CI/CD执行的可靠性。

Quick Reference

快速参考

  • Remove Anti-patterns - Eliminate waitForTimeout and fixed delays
  • Smart Waits - Implement state-based waiting strategies
  • Test Sharding - Configure parallel test execution
  • Reliability Patterns - Reduce test flakiness
  • 移除反模式 - 消除waitForTimeout和固定延迟
  • 智能等待 - 实现基于状态的等待策略
  • 测试分片 - 配置并行测试执行
  • 可靠性模式 - 减少测试不稳定问题

When to Use

适用场景

Use this skill when:
  • E2E tests are timing out in CI
  • Tests fail intermittently (flaky tests)
  • Test execution time exceeds CI limits
  • Multiple retries needed for tests to pass
  • Tests rely on fixed time delays (anti-patterns)
  • Looking to speed up CI/CD pipeline
  • Need to improve test maintainability
在以下场景中使用本方案:
  • E2E测试在CI中出现超时
  • 测试间歇性失败(不稳定测试)
  • 测试执行时间超出CI限制
  • 测试需要多次重试才能通过
  • 测试依赖固定时长延迟(反模式)
  • 希望加速CI/CD流水线
  • 需要提升测试可维护性

Core Methodology

核心方法论

Systematic E2E optimization focusing on three pillars: Reliability, Speed, and Maintainability.
系统化的E2E优化围绕三大核心:可靠性速度可维护性

Key Principles

关键原则

  1. State-based waiting - Wait for conditions, not arbitrary time
  2. Smart selectors - Use stable, semantic element targeting
  3. Test isolation - Clean setup/teardown to prevent interference
  4. Parallel execution - Shard tests for faster CI runs
  5. Proper mocking - Mock external dependencies for reliability
  6. Network awareness - Wait for network completion
  7. Accessibility-first - Use semantic selectors for stability
  1. 基于状态的等待 - 等待特定条件而非固定时长
  2. 智能选择器 - 使用稳定、语义化的元素定位方式
  3. 测试隔离 - 清理测试前后的状态以避免相互干扰
  4. 并行执行 - 通过分片实现更快的CI运行速度
  5. 合理模拟 - 模拟外部依赖以提升可靠性
  6. 网络感知 - 等待网络请求完成
  7. 优先可访问性 - 使用语义化选择器提升稳定性

Optimization Hierarchy

优化优先级

Priority 1: Remove anti-patterns (waitForTimeout)
Priority 2: Implement smart waits (toBeVisible, toBeAttached)
Priority 3: Optimize selectors (data-testid, locators)
Priority 4: Enable test sharding (parallel execution)
Priority 5: Add performance monitoring
优先级1:移除反模式(waitForTimeout)
优先级2:实现智能等待(toBeVisible、toBeAttached)
优先级3:优化选择器(data-testid、定位器)
优先级4:启用测试分片(并行执行)
优先级5:添加性能监控

Integration Points

集成点

Works with these agents for comprehensive testing strategy:
  • qa-engineer: Test writing strategies and conventions
  • performance-engineer: Test performance benchmarking
  • debugger: Diagnose and fix flaky tests
  • mock-infrastructure-engineer: Optimize mock setup and caching
可与以下角色协作以构建全面的测试策略:
  • qa-engineer:测试编写策略与规范
  • performance-engineer:测试性能基准测试
  • debugger:诊断并修复不稳定测试
  • mock-infrastructure-engineer:优化模拟环境的设置与缓存

Anti-patterns to Eliminate

需要消除的反模式

1. waitForTimeout Anti-pattern

1. waitForTimeout反模式

Problem: Fixed time delays are flaky and slow down tests
typescript
// ❌ BAD - Flaky, slow
await page.waitForTimeout(2000);
await expect(element).toBeVisible();

// ✅ GOOD - Reliable, fast
await expect(element).toBeVisible({ timeout: 5000 });
问题:固定时长延迟会导致测试不稳定且拖慢速度
typescript
// ❌ 不良实践 - 不稳定、缓慢
await page.waitForTimeout(2000);
await expect(element).toBeVisible();

// ✅ 良好实践 - 可靠、快速
await expect(element).toBeVisible({ timeout: 5000 });

2. Brittle Selectors Anti-pattern

2. 脆弱选择器反模式

Problem: CSS/XPath selectors break on UI changes
typescript
// ❌ BAD - Brittle
await page.locator('div:nth-child(2) > button').click();
await page.locator('.btn.primary').click();

// ✅ GOOD - Stable
await page.getByTestId('submit-button').click();
await page.getByRole('button', { name: 'Submit' }).click();
问题:CSS/XPath选择器会因UI变更而失效
typescript
// ❌ 不良实践 - 脆弱
await page.locator('div:nth-child(2) > button').click();
await page.locator('.btn.primary').click();

// ✅ 良好实践 - 稳定
await page.getByTestId('submit-button').click();
await page.getByRole('button', { name: 'Submit' }).click();

3. Missing Network Waits Anti-pattern

3. 缺少网络等待反模式

Problem: Race conditions between user action and network requests
typescript
// ❌ BAD - Race condition
await page.click('button');
await expect(page.getByText('Loaded')).toBeVisible();

// ✅ GOOD - Waits for network
await page.click('button');
await page.waitForLoadState('networkidle');
await expect(page.getByText('Loaded')).toBeVisible();
问题:用户操作与网络请求之间存在竞争条件
typescript
// ❌ 不良实践 - 竞争条件
await page.click('button');
await expect(page.getByText('Loaded')).toBeVisible();

// ✅ 良好实践 - 等待网络完成
await page.click('button');
await page.waitForLoadState('networkidle');
await expect(page.getByText('Loaded')).toBeVisible();

4. Hardcoded Timeout Anti-pattern

4. 硬编码超时反模式

Problem: One-size-fits-all timeout doesn't work for all tests
typescript
// ❌ BAD - Too short for slow tests, too long for fast
test.setTimeout(10000); // Global timeout

// ✅ GOOD - Per-test or per-assertion timeout
await expect(element).toBeVisible({ timeout: 5000 });
await expect(slowElement).toBeVisible({ timeout: 30000 });
问题:统一的超时设置无法适配所有测试
typescript
// ❌ 不良实践 - 对慢测试太短,对快测试太长
test.setTimeout(10000); // 全局超时

// ✅ 良好实践 - 为单个测试或断言设置超时
await expect(element).toBeVisible({ timeout: 5000 });
await expect(slowElement).toBeVisible({ timeout: 30000 });

Best Practices

最佳实践

✅ Do

✅ 推荐做法

  • Use
    expect().toBeVisible()
    for automatic waiting
  • Use
    data-testid
    attributes for element selection
  • Wait for network idle:
    page.waitForLoadState('networkidle')
  • Use Playwright locators for efficient queries
  • Clean state between tests
  • Mock external dependencies (AI services, databases)
  • Implement test-level retry logic
  • Shard tests for parallel execution
  • Test at realistic network speeds
  • Use semantic selectors (roles, labels)
  • 使用
    expect().toBeVisible()
    实现自动等待
  • 使用
    data-testid
    属性进行元素选择
  • 等待网络空闲:
    page.waitForLoadState('networkidle')
  • 使用Playwright定位器实现高效查询
  • 在测试之间清理状态
  • 模拟外部依赖(AI服务、数据库)
  • 实现测试级别的重试逻辑
  • 分片测试以实现并行执行
  • 在真实网络速度下进行测试
  • 使用语义化选择器(角色、标签)

❌ Don't

❌ 不推荐做法

  • Use
    waitForTimeout()
    - always wait for state
  • Rely on brittle selectors (CSS, XPath)
  • Skip tests without fixing the root cause
  • Ignore flaky tests - investigate and fix
  • Test implementation details
  • Use global timeouts - use per-assertion timeouts
  • Assume immediate UI updates - wait for changes
  • 使用
    waitForTimeout()
    - 始终等待特定状态
  • 依赖脆弱的选择器(CSS、XPath)
  • 不修复根本原因就跳过测试
  • 忽略不稳定测试 - 应调查并修复
  • 测试实现细节
  • 使用全局超时 - 应为单个断言设置超时
  • 假设UI会立即更新 - 等待状态变化

Performance Targets

性能目标

Speed Metrics

速度指标

  • Test timeout: 30 seconds per test (configurable)
  • Assertion timeout: 5 seconds typical (adjust per test)
  • Worker count: 2-4 workers for CI
  • Shard factor: 2-4 shards based on test count
  • Target execution time: < 2 minutes for smoke tests
  • 测试超时:每个测试30秒(可配置)
  • 断言超时:通常为5秒(可根据测试调整)
  • 工作进程数:CI环境中使用2-4个工作进程
  • 分片系数:根据测试数量设置2-4个分片
  • 目标执行时间:冒烟测试<2分钟

Optimization Goals

优化目标

  • Reduce flaky tests: < 1% failure rate
  • Improve execution speed: 20-30% faster with sharding
  • Reduce CI time: Minimize total pipeline duration
  • Maintain readability: Keep tests easy to understand
  • 减少不稳定测试:失败率<1%
  • 提升执行速度:通过分片提升20-30%
  • 缩短CI时间:最小化流水线总时长
  • 保持可读性:确保测试易于理解

Smart Waiting Strategies

智能等待策略

waitForLoadState

waitForLoadState

Wait for different load states based on context:
typescript
// For navigation
await page.waitForLoadState('load');

// For AJAX requests
await page.waitForLoadState('networkidle');

// For dynamic content
await page.waitForLoadState('domcontentloaded');
根据上下文等待不同的加载状态:
typescript
// 页面导航时
await page.waitForLoadState('load');

// AJAX请求时
await page.waitForLoadState('networkidle');

// 动态内容加载时
await page.waitForLoadState('domcontentloaded');

expect() Built-in Waiting

expect()内置等待

Use Playwright's auto-waiting assertions:
typescript
// Wait for element to appear
await expect(page.getByTestId('element')).toBeVisible();

// Wait for element to disappear
await expect(page.getByTestId('loading')).toBeHidden();

// Wait for text content
await expect(page.getByText('Success')).toBeVisible();

// Wait for element to be attached
await expect(page.getByTestId('element')).toBeAttached();
使用Playwright的自动等待断言:
typescript
// 等待元素出现
await expect(page.getByTestId('element')).toBeVisible();

// 等待元素消失
await expect(page.getByTestId('loading')).toBeHidden();

// 等待文本内容出现
await expect(page.getByText('Success')).toBeVisible();

// 等待元素附加到DOM
await expect(page.getByTestId('element')).toBeAttached();

Locators with Filters

带筛选器的定位器

Combine locators with smart filters:
typescript
// Wait for specific element in list
await expect(
  page.getByTestId('item').filter({ hasText: 'Target' })
).toBeVisible();

// Wait for enabled button
await expect(
  page.getByRole('button', { name: 'Submit' })
    .and(page.getByRole('button', { disabled: false })
).toBeVisible();
结合定位器与智能筛选器:
typescript
// 等待列表中的特定元素
await expect(
  page.getByTestId('item').filter({ hasText: 'Target' })
).toBeVisible();

// 等待可用按钮
await expect(
  page.getByRole('button', { name: 'Submit' })
    .and(page.getByRole('button', { disabled: false })
).toBeVisible();

Test Sharding Strategy

测试分片策略

GitHub Actions Matrix

GitHub Actions矩阵配置

yaml
strategy:
  matrix:
    shard_index: [0, 1, 2, 3]
    total_shards: [4]

steps:
  - name: Run E2E tests
    run: |
      pnpm exec playwright test \
        --project=chromium \
        --shard=${{ matrix.shard_index }}/${{ matrix.total_shards }} \
        --retries=2
yaml
strategy:
  matrix:
    shard_index: [0, 1, 2, 3]
    total_shards: [4]

steps:
  - name: Run E2E tests
    run: |
      pnpm exec playwright test \
        --project=chromium \
        --shard=${{ matrix.shard_index }}/${{ matrix.total_shards }} \
        --retries=2

Dynamic Sharding

动态分片

bash
undefined
bash
undefined

Calculate shards based on test count

根据测试数量计算分片

SHARD_COUNT=4 TEST_COUNT=$(pnpm exec playwright test --list 2>/dev/null | grep -c '›') SHARD_SIZE=$((TEST_COUNT / SHARD_COUNT + 1))
for i in $(seq 0 $((SHARD_COUNT - 1))); do pnpm exec playwright test
--shard=$i/$SHARD_COUNT
--output=test-results/shard-$i done
undefined
SHARD_COUNT=4 TEST_COUNT=$(pnpm exec playwright test --list 2>/dev/null | grep -c '›') SHARD_SIZE=$((TEST_COUNT / SHARD_COUNT + 1))
for i in $(seq 0 $((SHARD_COUNT - 1))); do pnpm exec playwright test
--shard=$i/$SHARD_COUNT
--output=test-results/shard-$i done
undefined

CI/CD Integration

CI/CD集成

Environment Variables

环境变量

bash
undefined
bash
undefined

Required for Playwright in CI

Playwright在CI中运行所需的环境变量

export CI=true export PLAYWRIGHT_BROWSERS_PATH=~/.cache/ms-playwright export NODE_ENV=test export NODE_OPTIONS=--max-old-space-size=4096
undefined
export CI=true export PLAYWRIGHT_BROWSERS_PATH=~/.cache/ms-playwright export NODE_ENV=test export NODE_OPTIONS=--max-old-space-size=4096
undefined

Optimized Test Command

优化后的测试命令

bash
pnpm exec playwright test \
  --project=chromium \
  --reporter=list,html,json \
  --retries=2 \
  --timeout=30000 \
  --workers=2 \
  --max-failures=5
bash
pnpm exec playwright test \
  --project=chromium \
  --reporter=list,html,json \
  --retries=2 \
  --timeout=30000 \
  --workers=2 \
  --max-failures=5

Caching Strategy

缓存策略

yaml
- name: Cache Playwright browsers
  uses: actions/cache@v3
  with:
    path: ~/.cache/ms-playwright
    key: playwright-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
yaml
- name: Cache Playwright browsers
  uses: actions/cache@v3
  with:
    path: ~/.cache/ms-playwright
    key: playwright-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}

Common Pitfalls

常见陷阱

Animation Delays

动画延迟

Animations can cause tests to be flaky:
typescript
// ❌ DON'T - Wait arbitrary time
await page.waitForTimeout(500);

// ✅ DO - Wait for animation to complete
await page.waitForLoadState('domcontentloaded');
动画可能导致测试不稳定:
typescript
// ❌ 不推荐 - 等待固定时长
await page.waitForTimeout(500);

// ✅ 推荐 - 等待动画完成
await page.waitForLoadState('domcontentloaded');

Lazy Loading

懒加载

Lazy-loaded components need special handling:
typescript
// ❌ DON'T - Element might not exist yet
await expect(page.getByTestId('lazy-content')).toBeVisible();

// ✅ DO - Scroll into view first
await page.getByTestId('lazy-container').scrollIntoViewIfNeeded();
await expect(page.getByTestId('lazy-content')).toBeVisible();
懒加载组件需要特殊处理:
typescript
// ❌ 不推荐 - 元素可能尚未存在
await expect(page.getByTestId('lazy-content')).toBeVisible();

// ✅ 推荐 - 先滚动到视图中
await page.getByTestId('lazy-container').scrollIntoViewIfNeeded();
await expect(page.getByTestId('lazy-content')).toBeVisible();

Multiple Loading States

多重加载状态

Handle skeleton loaders gracefully:
typescript
// Wait for loading state to complete
await expect(page.getByTestId('loading')).toBeVisible();
await expect(page.getByTestId('loading')).toBeHidden();
await expect(page.getByTestId('content')).toBeVisible();
优雅处理骨架加载器:
typescript
// 等待加载状态完成
await expect(page.getByTestId('loading')).toBeVisible();
await expect(page.getByTestId('loading')).toBeHidden();
await expect(page.getByTestId('content')).toBeVisible();

Monitoring and Metrics

监控与指标

Test Execution Time

测试执行时间

Track per-test execution to identify slow tests:
typescript
test.describe('Feature', () => {
  test('slow test', async ({ page }) => {
    const startTime = Date.now();
    // ... test code ...
    const duration = Date.now() - startTime;
    if (duration > 5000) {
      console.warn(`Test took ${duration}ms - consider optimization`);
    }
  });
});
跟踪单个测试的执行时间以识别慢测试:
typescript
test.describe('Feature', () => {
  test('slow test', async ({ page }) => {
    const startTime = Date.now();
    // ... 测试代码 ...
    const duration = Date.now() - startTime;
    if (duration > 5000) {
      console.warn(`测试耗时${duration}ms - 建议优化`);
    }
  });
});

Failure Analysis

失败分析

Categorize failures for targeted fixes:
typescript
test.afterEach(async () => {
  if (test.info().status !== 'passed') {
    // Take screenshot on failure
    await page.screenshot({
      path: `failures/${test.info().title}.png`,
      fullPage: true,
    });
  }
});
对失败进行分类以实现针对性修复:
typescript
test.afterEach(async () => {
  if (test.info().status !== 'passed') {
    // 失败时截图
    await page.screenshot({
      path: `failures/${test.info().title}.png`,
      fullPage: true,
    });
  }
});

Content Modules

内容模块

  • Remove Anti-patterns - Detailed anti-pattern removal guide
  • Smart Waits - Comprehensive waiting strategies
  • Test Sharding - Parallel execution setup
  • Reliability Patterns - Reduce test flakiness
  • 移除反模式 - 详细的反模式移除指南
  • 智能等待 - 全面的等待策略
  • 测试分片 - 并行执行设置
  • 可靠性模式 - 减少测试不稳定问题

Create fast, reliable, and maintainable E2E tests.

打造快速、可靠且易于维护的E2E测试。