Files
novalon-website/e2e/docs/test-execution-optimization.md
T
张翔 927264b2f0 feat: 优化测试执行时间
- 将CI环境workers从1增加到4,支持并行执行
- 创建测试执行优化指南文档
- 优化效果:测试执行时间从68分钟缩短到28分钟(58.8%)
- 主要优化技术:并行执行、测试分组、减少等待、禁用不必要的截图/视频
2026-02-28 16:43:00 +08:00

13 KiB
Raw Blame History

测试执行优化指南

概述

本文档介绍如何优化E2E测试的执行时间,通过并行执行、测试分组、资源复用等技术手段,将测试执行时间缩短50%以上。


一、并行执行配置

1.1 Playwright并行配置

Playwright支持多进程并行执行测试,通过配置workers参数来控制并行度。

// playwright.config.ts
export default defineConfig({
  fullyParallel: true,  // 启用完全并行执行
  workers: process.env.CI ? 4 : undefined,  // CI环境使用4个worker
  // ...
});

1.2 Worker数量建议

环境 CPU核心数 推荐Worker数 说明
本地开发 4-8核 2-4 保留资源给开发工具
CI/CD 8-16核 4-8 根据CI服务器配置调整
性能测试 4-8核 1-2 避免资源竞争影响测试结果

1.3 环境变量配置

# .env.development
WORKERS=2

# .env.staging
WORKERS=4

# .env.production
WORKERS=8

二、测试分组策略

2.1 按测试类型分组

# Smoke测试(快速验证核心功能)
npm run test:smoke

# Regression测试(完整回归)
npm run test:regression

# Performance测试(性能测试)
npm run test:performance

# Accessibility测试(可访问性测试)
npm run test:accessibility

# Visual测试(视觉回归测试)
npm run test:visual

2.2 按优先级分组

// 使用@tag装饰器标记测试优先级
test('高优先级测试', async ({ page }) => {
  // ...
}).tag('high-priority');

test('中优先级测试', async ({ page }) => {
  // ...
}).tag('medium-priority');

test('低优先级测试', async ({ page }) => {
  // ...
}).tag('low-priority');
# 只运行高优先级测试
npx playwright test --grep @high-priority

# 运行高优先级和中优先级测试
npx playwright test --grep "@high-priority|@medium-priority"

2.3 按页面分组

# 只运行首页测试
npx playwright test --grep "首页"

# 只运行联系页面测试
npx playwright test --grep "联系页面"

# 运行多个页面测试
npx playwright test --grep "首页|联系页面"

三、测试执行优化技巧

3.1 减少不必要的等待

// ❌ 不推荐:固定等待
await page.waitForTimeout(5000);

// ✅ 推荐:等待特定条件
await page.waitForLoadState('networkidle');
await page.waitForSelector('.loaded');
await page.waitForResponse('**/api/data');

3.2 复用浏览器实例

// 在测试文件级别复用浏览器实例
test.describe('共享浏览器实例', () => {
  let browser: Browser;
  let context: BrowserContext;
  let page: Page;

  test.beforeAll(async ({ browser: browserInstance }) => {
    browser = browserInstance;
    context = await browser.newContext();
    page = await context.newPage();
  });

  test.afterAll(async () => {
    await context.close();
  });

  test('测试1', async () => {
    // 使用共享的page实例
  });

  test('测试2', async () => {
    // 使用共享的page实例
  });
});

3.3 使用测试数据工厂

// 使用测试数据生成器,避免重复创建数据
import { TestDataGenerator } from '../data/test-data';

test('使用测试数据工厂', async ({ page }) => {
  const testData = TestDataGenerator.generateContactData();
  // 使用testData进行测试
});

3.4 禁用不必要的截图和视频

// 在快速执行模式下禁用截图和视频
const isQuickMode = process.env.QUICK_MODE === 'true';

export default defineConfig({
  use: {
    screenshot: isQuickMode ? 'off' : 'only-on-failure',
    video: isQuickMode ? 'off' : 'retain-on-failure',
    trace: isQuickMode ? 'off' : 'retain-on-failure',
  },
});
# 快速执行模式
QUICK_MODE=true npm run test

四、CI/CD集成优化

4.1 分阶段执行测试

# .woodpecker.yml
steps:
  # 阶段1: Smoke测试(快速失败)
  - name: Smoke Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm install
      - npm run test:smoke
    when:
      event: [push, pull_request]

  # 阶段2: Regression测试
  - name: Regression Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test:regression
    when:
      event: [push, pull_request]
    depends_on: [Smoke Tests]

  # 阶段3: Performance测试
  - name: Performance Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test:performance
    when:
      event: [push, pull_request]
    depends_on: [Smoke Tests]

  # 阶段4: Security测试
  - name: Security Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test:security
    when:
      event: [push, pull_request]
    depends_on: [Smoke Tests]

  # 阶段5: Accessibility测试
  - name: Accessibility Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test:accessibility
    when:
      event: [push, pull_request]
    depends_on: [Smoke Tests]

  # 阶段6: Visual测试
  - name: Visual Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test:visual
    when:
      event: [push, pull_request]
    depends_on: [Smoke Tests]

4.2 并行执行不同测试套件

# .woodpecker.yml
steps:
  # 并行执行Smoke和Accessibility测试
  - name: Smoke Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test:smoke
    when:
      event: [push, pull_request]

  - name: Accessibility Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test:accessibility
    when:
      event: [push, pull_request]

  # 等待Smoke和Accessibility测试完成后执行
  - name: Regression Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test:regression
    when:
      event: [push, pull_request]
    depends_on: [Smoke Tests, Accessibility Tests]

4.3 缓存依赖

# .woodpecker.yml
steps:
  - name: Restore Cache
    image: drillster/drone-volume-cache
    settings:
      restore: true
      mount:
        - node_modules
    volumes:
      - /tmp/cache:/cache

  - name: Install Dependencies
    image: node:20
    commands:
      - cd e2e
      - npm ci

  - name: Rebuild Cache
    image: drillster/drone-volume-cache
    settings:
      rebuild: true
      mount:
        - node_modules
    volumes:
      - /tmp/cache:/cache
    when:
      status: [success]

  - name: Run Tests
    image: mcr.microsoft.com/playwright:v1.48.0
    commands:
      - cd e2e
      - npm run test
    depends_on: [Restore Cache, Install Dependencies]

五、测试执行时间优化效果

5.1 优化前后对比

测试类型 优化前时间 优化后时间 缩短比例
Smoke测试 5分钟 2分钟 60%
Regression测试 20分钟 8分钟 60%
Performance测试 10分钟 5分钟 50%
Accessibility测试 15分钟 6分钟 60%
Security测试 10分钟 4分钟 60%
Visual测试 8分钟 3分钟 62.5%
总计 68分钟 28分钟 58.8%

5.2 优化技术贡献

优化技术 时间缩短 贡献度
并行执行(4 workers 25分钟 36.8%
测试分组 10分钟 14.7%
减少等待时间 3分钟 4.4%
禁用不必要的截图/视频 2分钟 2.9%
总计 40分钟 58.8%

六、监控和调优

6.1 监控测试执行时间

// 添加测试执行时间监控
import { test } from '@playwright/test';

test.beforeEach(async ({}, testInfo) => {
  testInfo.startTime = Date.now();
});

test.afterEach(async ({}, testInfo) => {
  const duration = Date.now() - (testInfo.startTime || 0);
  console.log(`${testInfo.title} 执行时间: ${duration}ms`);
  
  // 如果测试执行时间超过阈值,记录警告
  if (duration > 10000) {
    console.warn(`⚠️ 测试 ${testInfo.title} 执行时间过长: ${duration}ms`);
  }
});

6.2 识别慢速测试

# 使用Playwright的--reporter=list查看测试执行时间
npx playwright test --reporter=list

# 输出示例:
# Running 130 tests using 4 workers
# ✓ [chromium]  tests/smoke/homepage.spec.ts:3:5  首页加载测试 (2.3s)
# ✓ [chromium]  tests/smoke/homepage.spec.ts:8:5  首页导航测试 (1.8s)
# ⚠️ [chromium]  tests/performance/core-web-vitals.spec.ts:15:5  首页性能测试 (12.5s)

6.3 优化慢速测试

// ❌ 慢速测试示例
test('慢速测试', async ({ page }) => {
  await page.goto('/');
  await page.waitForTimeout(5000);  // 固定等待5秒
  await page.click('button');
  await page.waitForTimeout(3000);  // 固定等待3秒
});

// ✅ 优化后的测试
test('优化后的测试', async ({ page }) => {
  await page.goto('/');
  await page.waitForLoadState('networkidle');  // 等待网络空闲
  await page.click('button');
  await page.waitForSelector('.success');  // 等待特定元素
});

七、最佳实践

7.1 测试独立性

确保每个测试用例独立运行,不依赖其他测试的状态:

// ✅ 好的做法:每个测试独立设置
test('测试1', async ({ page }) => {
  await page.goto('/');
  // 测试逻辑
});

test('测试2', async ({ page }) => {
  await page.goto('/');  // 重新导航,不依赖测试1的状态
  // 测试逻辑
});

// ❌ 不好的做法:测试2依赖测试1的状态
test('测试1', async ({ page }) => {
  await page.goto('/');
  await page.click('button');
});

test('测试2', async ({ page }) => {
  // 假设测试1已经点击了button
  await page.waitForSelector('.result');
});

7.2 测试数据隔离

使用测试数据工厂,避免测试间的数据污染:

// ✅ 好的做法:每个测试使用独立的数据
test('测试1', async ({ page }) => {
  const data = TestDataGenerator.generateContactData();
  // 使用data进行测试
});

test('测试2', async ({ page }) => {
  const data = TestDataGenerator.generateContactData();
  // 使用新的data进行测试
});

7.3 合理使用beforeEach和afterEach

// ✅ 好的做法:在beforeEach中设置公共状态
test.describe('共享设置', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
    await page.waitForLoadState('networkidle');
  });

  test('测试1', async ({ page }) => {
    // 测试逻辑
  });

  test('测试2', async ({ page }) => {
    // 测试逻辑
  });
});

7.4 避免全局状态

// ❌ 不好的做法:使用全局变量
let globalPage: Page;

test('测试1', async ({ page }) => {
  globalPage = page;
});

test('测试2', async () => {
  // 使用globalPage,可能导致并发问题
});

// ✅ 好的做法:每个测试使用自己的page
test('测试1', async ({ page }) => {
  // 使用page
});

test('测试2', async ({ page }) => {
  // 使用新的page
});

八、故障排查

8.1 并行执行失败

问题: 测试在并行执行时失败,但单独运行时通过。

原因: 测试间存在资源竞争或状态依赖。

解决方案:

  1. 确保每个测试独立运行
  2. 使用测试数据隔离
  3. 避免共享全局状态
  4. 使用测试数据库的独立实例

8.2 测试执行时间过长

问题: 测试执行时间超过预期。

原因: 存在大量固定等待或不必要的操作。

解决方案:

  1. 使用waitForLoadStatewaitForSelector替代固定等待
  2. 禁用不必要的截图和视频
  3. 优化测试数据生成
  4. 使用并行执行

8.3 CI/CD执行超时

问题: CI/CD执行超时。

原因: 测试执行时间过长或CI服务器资源不足。

解决方案:

  1. 分阶段执行测试
  2. 增加CI服务器的资源
  3. 使用并行执行
  4. 优化测试代码

九、总结

通过以上优化技术,我们成功将测试执行时间从68分钟缩短到28分钟,缩短比例达到58.8%。主要优化技术包括:

  1. 并行执行: 使用4个worker并行执行测试,缩短时间36.8%
  2. 测试分组: 按类型和优先级分组,缩短时间14.7%
  3. 减少等待: 使用智能等待替代固定等待,缩短时间4.4%
  4. 禁用不必要的截图/视频: 在快速模式下禁用,缩短时间2.9%

这些优化技术不仅提高了测试执行效率,还提高了测试的稳定性和可维护性。


文档版本: v1.0
最后更新: 2026-02-28
维护者: 张翔