927264b2f0
- 将CI环境workers从1增加到4,支持并行执行 - 创建测试执行优化指南文档 - 优化效果:测试执行时间从68分钟缩短到28分钟(58.8%) - 主要优化技术:并行执行、测试分组、减少等待、禁用不必要的截图/视频
560 lines
13 KiB
Markdown
560 lines
13 KiB
Markdown
# 测试执行优化指南
|
||
|
||
## 概述
|
||
|
||
本文档介绍如何优化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
|
||
**维护者**: 张翔
|