# 测试执行优化指南 ## 概述 本文档介绍如何优化E2E测试的执行时间,通过并行执行、测试分组、资源复用等技术手段,将测试执行时间缩短50%以上。 --- ## 一、并行执行配置 ### 1.1 Playwright并行配置 Playwright支持多进程并行执行测试,通过配置`workers`参数来控制并行度。 ```typescript // 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 环境变量配置 ```bash # .env.development WORKERS=2 # .env.staging WORKERS=4 # .env.production WORKERS=8 ``` --- ## 二、测试分组策略 ### 2.1 按测试类型分组 ```bash # 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 按优先级分组 ```typescript // 使用@tag装饰器标记测试优先级 test('高优先级测试', async ({ page }) => { // ... }).tag('high-priority'); test('中优先级测试', async ({ page }) => { // ... }).tag('medium-priority'); test('低优先级测试', async ({ page }) => { // ... }).tag('low-priority'); ``` ```bash # 只运行高优先级测试 npx playwright test --grep @high-priority # 运行高优先级和中优先级测试 npx playwright test --grep "@high-priority|@medium-priority" ``` ### 2.3 按页面分组 ```bash # 只运行首页测试 npx playwright test --grep "首页" # 只运行联系页面测试 npx playwright test --grep "联系页面" # 运行多个页面测试 npx playwright test --grep "首页|联系页面" ``` --- ## 三、测试执行优化技巧 ### 3.1 减少不必要的等待 ```typescript // ❌ 不推荐:固定等待 await page.waitForTimeout(5000); // ✅ 推荐:等待特定条件 await page.waitForLoadState('networkidle'); await page.waitForSelector('.loaded'); await page.waitForResponse('**/api/data'); ``` ### 3.2 复用浏览器实例 ```typescript // 在测试文件级别复用浏览器实例 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 使用测试数据工厂 ```typescript // 使用测试数据生成器,避免重复创建数据 import { TestDataGenerator } from '../data/test-data'; test('使用测试数据工厂', async ({ page }) => { const testData = TestDataGenerator.generateContactData(); // 使用testData进行测试 }); ``` ### 3.4 禁用不必要的截图和视频 ```typescript // 在快速执行模式下禁用截图和视频 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', }, }); ``` ```bash # 快速执行模式 QUICK_MODE=true npm run test ``` --- ## 四、CI/CD集成优化 ### 4.1 分阶段执行测试 ```yaml # .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 并行执行不同测试套件 ```yaml # .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 缓存依赖 ```yaml # .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 监控测试执行时间 ```typescript // 添加测试执行时间监控 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 识别慢速测试 ```bash # 使用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 优化慢速测试 ```typescript // ❌ 慢速测试示例 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 测试独立性 确保每个测试用例独立运行,不依赖其他测试的状态: ```typescript // ✅ 好的做法:每个测试独立设置 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 测试数据隔离 使用测试数据工厂,避免测试间的数据污染: ```typescript // ✅ 好的做法:每个测试使用独立的数据 test('测试1', async ({ page }) => { const data = TestDataGenerator.generateContactData(); // 使用data进行测试 }); test('测试2', async ({ page }) => { const data = TestDataGenerator.generateContactData(); // 使用新的data进行测试 }); ``` ### 7.3 合理使用beforeEach和afterEach ```typescript // ✅ 好的做法:在beforeEach中设置公共状态 test.describe('共享设置', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.waitForLoadState('networkidle'); }); test('测试1', async ({ page }) => { // 测试逻辑 }); test('测试2', async ({ page }) => { // 测试逻辑 }); }); ``` ### 7.4 避免全局状态 ```typescript // ❌ 不好的做法:使用全局变量 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. 使用`waitForLoadState`、`waitForSelector`替代固定等待 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 **维护者**: 张翔