Files
novalon-manage-system/docs/superpowers/specs/2026-04-04-e2e-test-optimization-design.md
T

14 KiB
Raw Blame History

E2E测试优化设计方案

文档版本: 1.0
创建日期: 2026-04-04
作者: 张翔
目标: 将E2E测试通过率从17.3%提升至100%,并优化测试执行时间


1. 背景与目标

1.1 当前状态

  • 总测试数: 52个测试用例
  • 通过: 9个测试用例 (17.3%)
  • 失败: 43个测试用例 (82.7%)
  • 执行时间: 17.2分钟

1.2 主要问题

  1. 页面导航问题: 大部分测试用例无法正确导航到目标页面
  2. 选择器问题: 测试用例使用的选择器无法找到对应的页面元素
  3. 测试执行时间: 当前执行时间较长,需要优化

1.3 目标

  • 测试通过率: 100% (所有52个测试用例通过)
  • 执行时间: 减少30%以上 (从17.2分钟降至12分钟以内)
  • 测试稳定性: 所有测试用例稳定可重复执行

2. 实施策略

采用分阶段实施策略,按照问题的影响范围,从基础到高级逐步修复。

2.1 为什么选择分阶段实施?

  • 风险可控: 每个阶段都可以验证效果,及时调整方案
  • 效率最高: 先解决基础问题,再解决复杂问题,避免重复工作
  • 符合测试金字塔: 从基础功能到高级功能,逐步提高测试覆盖率
  • 易于管理: 每个阶段都有明确的目标和验收标准

3. 第一阶段:基础导航修复

预计时间: 2-3天
目标: 测试通过率提升至50%以上(至少26个测试用例通过)

3.1 问题分析

43个失败的测试用例中,大部分都是因为无法正确导航到目标页面。主要原因包括:

  1. 页面不存在: 某些管理页面可能还未实现
  2. 路由配置问题: 路由路径与测试用例中的路径不一致
  3. 页面加载超时: 页面加载时间过长,导致测试超时
  4. 权限问题: 某些页面需要特定权限才能访问

3.2 修复策略

3.2.1 页面存在性验证

首先验证所有测试用例涉及的页面是否都已经实现:

  • /users - 用户管理页面
  • /roles - 角色管理页面
  • /menus - 菜单管理页面
  • /sys/config - 系统配置页面
  • /dict - 字典管理页面
  • /files - 文件管理页面
  • /loginlog - 登录日志页面
  • /oplog - 操作日志页面
  • /exceptionlog - 异常日志页面

3.2.2 Page Object类优化

为每个Page Object类添加更健壮的导航逻辑:

async goto() {
  await this.page.goto('/users');
  
  // 等待页面加载完成
  await this.page.waitForLoadState('networkidle');
  
  // 等待关键元素出现
  await this.page.waitForSelector('.el-table', { timeout: 10000 });
  
  // 验证页面标题或URL
  await expect(this.page).toHaveURL(/.*users/);
}

3.2.3 错误处理机制

添加完善的错误处理机制:

async goto() {
  try {
    await this.page.goto('/users');
    await this.page.waitForLoadState('networkidle');
    await this.page.waitForSelector('.el-table', { timeout: 10000 });
  } catch (error) {
    // 截图保存错误状态
    await this.page.screenshot({ path: `test-results/error-${Date.now()}.png` });
    
    // 记录错误信息
    console.error('页面导航失败:', error);
    
    // 抛出更详细的错误信息
    throw new Error(`导航到用户管理页面失败: ${error.message}`);
  }
}

3.3 任务清单

  1. 验证页面存在性0.5天)

    • 检查所有测试用例涉及的页面是否已实现
    • 确认路由配置是否正确
    • 验证页面权限设置
  2. 优化Page Object类1天)

    • 为每个Page Object类添加健壮的导航方法
    • 添加错误处理机制
    • 添加页面加载验证逻辑
  3. 运行测试验证0.5天)

    • 运行完整测试套件
    • 收集通过率数据
    • 分析剩余失败原因

3.4 验收标准

  • 测试通过率提升至50%以上(至少26个测试用例通过)
  • 所有页面都能正确导航
  • 页面加载错误有清晰的错误信息

4. 第二阶段:选择器精准化

预计时间: 2-3天
目标: 测试通过率提升至90%以上(至少47个测试用例通过)

4.1 问题分析

测试用例中使用的选择器无法找到对应的页面元素,主要原因包括:

  1. 选择器过时: 前端代码修改后,选择器未同步更新
  2. 选择器不够健壮: 使用class选择器,容易受CSS变化影响
  3. 动态元素: 某些元素是动态生成的,需要更灵活的定位方式
  4. 异步加载: 元素加载有延迟,需要添加等待逻辑

4.2 修复策略

4.2.1 选择器诊断工具

使用Playwright的trace功能,捕获实际页面元素:

// 在测试配置中启用trace
use: {
  trace: 'on-first-retry',
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
}

4.2.2 选择器优化原则

优先使用以下选择器(按优先级排序):

  1. data-testid属性(最推荐)

    page.getByTestId('submit-button')
    
  2. 角色和文本组合

    page.getByRole('button', { name: '确定' })
    page.getByText('用户管理')
    
  3. CSS选择器(最后选择)

    page.locator('.el-button--primary')
    

4.2.3 Page Object类选择器更新

为每个Page Object类更新选择器:

export class UserManagementPage {
  readonly page: Page;
  readonly table: Locator;
  readonly createUserButton: Locator;
  readonly searchInput: Locator;
  readonly searchButton: Locator;

  constructor(page: Page) {
    this.page = page;
    
    // 使用更健壮的选择器
    this.table = page.locator('.el-table').first();
    this.createUserButton = page.getByRole('button', { name: '新增用户' });
    this.searchInput = page.getByPlaceholder('搜索用户名或邮箱');
    this.searchButton = page.getByRole('button', { name: '搜索' });
  }
}

4.2.4 等待策略优化

添加智能等待逻辑:

async waitForTableReady() {
  // 等待表格出现
  await this.table.waitFor({ state: 'visible', timeout: 10000 });
  
  // 等待表格数据加载完成
  await this.page.waitForFunction(
    () => document.querySelectorAll('.el-table__body tr').length > 0,
    { timeout: 5000 }
  );
}

4.2.5 动态元素处理

处理动态生成的元素:

async clickDynamicButton(buttonText: string) {
  // 使用文本内容定位动态按钮
  await this.page.getByRole('button', { name: buttonText }).click();
  
  // 或者使用正则表达式匹配
  await this.page.getByRole('button', { name: /确定|确认/ }).click();
}

4.3 任务清单

  1. 选择器诊断0.5天)

    • 使用Playwright trace捕获实际页面元素
    • 分析所有失败测试的选择器问题
    • 生成选择器诊断报告
  2. 批量更新选择器1.5天)

    • 更新所有Page Object类的选择器
    • 添加智能等待逻辑
    • 处理动态元素
  3. 运行测试验证0.5天)

    • 运行完整测试套件
    • 收集通过率数据
    • 分析剩余失败原因

4.4 验收标准

  • 测试通过率提升至90%以上(至少47个测试用例通过)
  • 所有选择器都能正确找到元素
  • 动态元素有稳定的处理逻辑

5. 第三阶段:性能优化

预计时间: 1-2天
目标: 测试通过率达到100%,执行时间减少30%以上

5.1 问题分析

当前测试套件执行时间为17.2分钟,主要耗时在:

  1. 全局setup/teardown: 启动后端服务、数据库初始化等
  2. 页面加载等待: 每个测试用例都等待页面加载完成
  3. 固定等待时间: 使用waitForTimeout固定等待,不够智能
  4. 串行执行: 测试用例逐个执行,无法并行

5.2 优化策略

5.2.1 全局setup优化

优化后端服务启动时间:

// global-setup.ts
export default async function globalSetup() {
  console.log('🚀 开始全局测试环境设置...');
  
  // 使用JAR文件启动(比Maven快50%
  const jarFile = path.join(backendDir, 'target/manage-app-1.0.0.jar');
  
  // 减少健康检查间隔(从1秒改为0.5秒)
  const healthCheckInterval = 500;
  
  // 减少最大等待时间(从60秒改为30秒)
  const maxWaitTime = 30;
  
  // 并行启动多个服务(如果需要)
  await Promise.all([
    startBackendService(),
    startFrontendService(),
  ]);
}

5.2.2 页面加载等待优化

使用更智能的等待策略:

// 优化前
await page.waitForTimeout(2000);

// 优化后:等待特定条件
await page.waitForLoadState('domcontentloaded'); // 只等待DOM加载
await page.waitForSelector('.el-table', { state: 'visible' }); // 等待关键元素

5.2.3 测试用例并行执行

在确保测试独立性的前提下,启用并行执行:

// playwright.config.ts
export default defineConfig({
  // 项目级并行(不同项目并行执行)
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
  ],
  
  // 文件级并行(同一项目内,不同文件并行执行)
  workers: process.env.CI ? 1 : 4, // CI环境串行,本地并行
  
  // 完全并行(需要确保测试完全独立)
  fullyParallel: false, // 暂不启用,避免localStorage冲突
});

5.2.4 测试数据缓存

缓存测试数据,避免重复创建:

// 使用全局状态存储测试数据
let testUserId: string | null = null;

test.beforeAll(async ({ request }) => {
  if (!testUserId) {
    // 只创建一次测试用户
    const response = await request.post('/api/users', {
      data: { username: 'testuser', password: 'Test@123' }
    });
    testUserId = (await response.json()).id;
  }
});

test.afterAll(async ({ request }) => {
  if (testUserId) {
    // 清理测试数据
    await request.delete(`/api/users/${testUserId}`);
    testUserId = null;
  }
});

5.2.5 智能重试机制

为不稳定的测试用例添加智能重试:

// playwright.config.ts
export default defineConfig({
  // 失败后重试2次
  retries: process.env.CI ? 2 : 1,
  
  // 只重试失败的测试用例
  retryOnlyFailed: true,
});

5.2.6 测试报告优化

生成更详细的测试报告:

// 自定义报告器
export default class CustomReporter {
  onTestEnd(test: TestCase, result: TestResult) {
    const duration = result.duration;
    const status = result.status;
    
    // 记录慢测试
    if (duration > 10000) {
      console.log(`⚠️ 慢测试: ${test.title} (${duration}ms)`);
    }
    
    // 记录失败测试的详细信息
    if (status === 'failed') {
      console.log(`❌ 失败: ${test.title}`);
      console.log(`   错误: ${result.error?.message}`);
    }
  }
}

5.3 任务清单

  1. 优化全局setup/teardown0.5天)

    • 使用JAR文件启动后端服务
    • 减少健康检查等待时间
    • 并行启动多个服务
  2. 优化页面加载等待0.5天)

    • 移除固定等待时间
    • 使用智能等待策略
    • 优化关键元素等待逻辑
  3. 生成最终报告0.5天)

    • 运行完整测试套件
    • 生成详细的测试报告
    • 分析性能指标

5.4 验收标准

  • 测试通过率达到100%(所有52个测试用例通过)
  • 测试执行时间减少30%以上(从17.2分钟降至12分钟以内)
  • 生成完整的测试报告和性能分析

6. 总体验收标准

6.1 功能验收

  • 所有52个测试用例100%通过
  • 测试覆盖所有核心业务流程
  • 测试报告清晰展示测试结果

6.2 性能验收

  • 测试执行时间在12分钟以内
  • 全局setup时间在30秒以内
  • 单个测试用例平均执行时间在20秒以内

6.3 质量验收

  • 所有Page Object类有完善的错误处理
  • 所有选择器使用最佳实践
  • 测试代码有清晰的注释和文档

7. 风险与应对

7.1 页面未实现风险

风险: 某些测试页面可能还未实现
应对:

  • 优先检查页面存在性
  • 如果页面未实现,暂时跳过相关测试用例
  • 记录未实现页面的测试用例,后续补充

7.2 选择器不稳定风险

风险: 某些选择器可能不稳定,导致测试时好时坏
应对:

  • 使用多个备选选择器
  • 添加重试机制
  • 使用更健壮的等待策略

7.3 测试数据冲突风险

风险: 多个测试用例共享测试数据,可能导致冲突
应对:

  • 每个测试用例使用唯一的测试数据(如时间戳)
  • 测试完成后清理测试数据
  • 使用独立的测试数据库

7.4 执行时间过长风险

风险: 即使优化后,执行时间可能仍然较长
应对:

  • 进一步优化等待策略
  • 考虑并行执行更多测试用例
  • 减少不必要的测试步骤

8. 后续优化建议

8.1 短期优化(1-2周)

  1. 添加更多测试用例: 覆盖更多边界场景
  2. 优化测试数据管理: 使用测试数据工厂模式
  3. 集成到CI/CD: 配置Woodpecker CI自动运行E2E测试

8.2 中期优化(2-4周)

  1. 添加可视化测试: 使用Percy或Applitools进行视觉回归测试
  2. 性能监控: 集成Lighthouse进行性能监控
  3. 测试报告优化: 生成更详细的HTML报告

8.3 长期优化(1-2个月)

  1. 测试框架升级: 考虑使用更先进的测试框架
  2. AI辅助测试: 使用AI工具自动生成测试用例
  3. 持续优化: 定期审查测试用例,优化测试执行速度

9. 参考资料


文档结束