Selaa lähdekoodia

fix(story-12.3): 修复批量测试超时问题

## 问题
- 每个 test 在 beforeEach 中创建/删除残疾人数据
- 批量运行时累积开销导致超时

## 解决方案
1. 添加初始化测试,使用固定的残疾人数据名称
2. 初始化测试只在首次运行时创建残疾人数据
3. 移除 beforeEach 中的残疾人数据创建逻辑
4. 移除 afterEach 中的残疾人数据清理逻辑

## 测试结果
- 16 个测试(含 1 个初始化测试)
- 14 passed, 2 skipped
- 总运行时间约 4.5 分钟

Co-Authored-By: Claude <noreply@anthropic.com>
yourname 4 päivää sitten
vanhempi
sitoutus
8dd343b732

+ 22 - 20
_bmad-output/implementation-artifacts/12-3-create-talent-user.md

@@ -1,6 +1,6 @@
 # Story 12.3: 后台创建人才用户测试
 
-Status: in-progress
+Status: completed
 
 <!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
 
@@ -100,13 +100,13 @@ Status: in-progress
 
 - [x] 任务 7: 验证代码质量 (AC: #6)
   - [x] 7.1 运行 `pnpm typecheck` 验证类型检查(通过)
-  - [x] 7.2 运行测试确保所有测试通过(单个测试通过,批量测试超时需调查
+  - [x] 7.2 运行测试确保所有测试通过(所有测试通过
   - [x] 7.3 验证选择器使用 data-testid
 
-- [ ] 任务 8: 调查并修复批量测试超时问题
-  - [ ] 8.1 分析批量测试超时的根本原因
-  - [ ] 8.2 优化测试数据创建和清理策略
-  - [ ] 8.3 确保所有测试批量运行时稳定通过
+- [x] 任务 8: 调查并修复批量测试超时问题
+  - [x] 8.1 分析批量测试超时的根本原因
+  - [x] 8.2 优化测试数据创建和清理策略
+  - [x] 8.3 确保所有测试批量运行时稳定通过
 
 ## Dev Notes
 
@@ -241,14 +241,14 @@ Claude (d8d-model)
 
 **关键问题和修复:**
 
-1. **测试批量运行超时问题**
-   - 问题:运行所有测试时出现超时和"Cannot navigate to invalid URL"错误
-   - 分析:单个测试("应该成功创建基本人才用户")成功通过(36.4s)
-   - 可能原因
-     - 残疾人数据创建耗时较长(每个测试都创建/删除残疾人
-     - afterEach 清理策略可能与后续测试产生竞争条件
-     - 多测试并行执行时资源竞争
-   - 状态:单个测试通过,批量测试需要进一步优化
+1. **测试批量运行超时问题(已修复)**
+   - 问题:运行所有测试时出现超时错误
+   - 根本原因:每个测试都在 `beforeEach` 中创建/删除残疾人数据,累积开销导致超时
+   - 解决方案
+     1. 添加初始化测试,使用固定的残疾人数据名称(`E2E测试_人才用户专用残疾人`
+     2. 初始化测试只在首次运行时创建残疾人数据,后续运行检测到数据存在则跳过
+     3. 移除 `beforeEach` 中的残疾人数据创建逻辑和 `afterEach` 中的清理逻辑
+   - 结果:所有测试批量运行通过,总时间约 4.5 分钟(16 个测试,14 passed, 2 skipped)
 
 2. **后端验证未实现(已知问题)**
    - 问题:后端允许创建没有 personId 的 TALENT 用户
@@ -257,7 +257,7 @@ Claude (d8d-model)
 ### Completion Notes List
 
 1. **测试文件创建**: `web/tests/e2e/specs/admin/user-create-talent.spec.ts`
-   - 13 个测试用例
+   - 16 个测试用例(含 1 个初始化测试)
    - 2 个测试跳过(后端验证未实现)
    - 测试场景完整覆盖
 
@@ -285,8 +285,10 @@ Claude (d8d-model)
    - ✅ 完整的 JSDoc 注释
 
 5. **测试结果**:
-   - 单个测试通过:"应该成功创建基本人才用户"(36.4s)
-   - 批量测试存在超时问题,需要进一步调查优化
+   - 批量测试通过:14 passed, 2 skipped
+   - 总运行时间:约 4.5 分钟(16 个测试)
+   - 初始化测试确保残疾人数据存在(约 10 秒)
+   - 各功能测试平均耗时:约 20 秒/测试
 
 ### File List
 
@@ -300,6 +302,6 @@ Claude (d8d-model)
 
 - 2026-01-13: 完成 Story 12.3 开发
   - 创建人才用户创建 E2E 测试
-  - 13 个测试用例,2 个跳过(后端验证未实现)
-  - 单个测试通过(36.4s)
-  - 批量测试存在超时问题,需进一步调查
+  - 16 个测试用例(含 1 个初始化测试),2 个跳过(后端验证未实现)
+  - 修复批量测试超时问题:使用固定测试数据 + 初始化测试
+  - 所有测试批量运行通过(14 passed, 2 skipped,总时间约 4.5 分钟)

+ 2 - 2
_bmad-output/implementation-artifacts/sprint-status.yaml

@@ -192,8 +192,8 @@ development_status:
   epic-12: in-progress
   12-1-user-page-object: done           # 用户管理 Page Object ✅ 代码审查问题全部修复完成 (2026-01-13)
   12-2-create-employer-user: done         # 后台创建企业用户测试 - 13 passed, 2 skipped (2026-01-13)
-  12-3-create-talent-user: in-progress      # 后台创建人才用户测试 - 批量测试超时问题需调查 (2026-01-13)
-  12-4-enterprise-mini-page-object: backlog  # 企业小程序 Page Object
+  12-3-create-talent-user: done            # 后台创建人才用户测试 - 14 passed, 2 skipped (2026-01-13)
+  12-4-enterprise-mini-page-object: ready-for-dev  # 企业小程序 Page Object ✅ Story 文档创建完成 (2026-01-13)
   12-5-enterprise-mini-login: backlog      # 企业小程序登录测试
   12-6-talent-mini-page-object: backlog    # 人才小程序 Page Object
   12-7-talent-mini-login: backlog          # 人才小程序登录测试

+ 48 - 53
web/tests/e2e/specs/admin/user-create-talent.spec.ts

@@ -12,62 +12,57 @@ import { UserType } from '@d8d/shared-types';
  * @see {@link ../pages/admin/disability-person.page.ts} DisabilityPersonManagementPage
  */
 test.describe('人才用户创建功能', () => {
-  // 测试创建的残疾人姓名,用于清理
-  let testPersonName: string;
+  // 使用固定的残疾人数据名称
+  const FIXED_TEST_PERSON_NAME = 'E2E测试_人才用户专用残疾人';
 
-  test.beforeEach(async ({ adminLoginPage, disabilityPersonPage, userManagementPage }) => {
-    // 以管理员身份登录后台
+  // 初始化测试:确保残疾人数据存在
+  // 此测试会先于其他测试运行,创建所需的残疾人数据
+  test('初始化:创建测试用残疾人数据', async ({ adminLoginPage, disabilityPersonPage }) => {
     await adminLoginPage.goto();
     await adminLoginPage.login('admin', 'admin123');
     await adminLoginPage.expectLoginSuccess();
 
-    // 创建测试残疾人(人才用户必须关联残疾人)
-    const timestamp = Date.now();
-    testPersonName = `测试残疾人_${timestamp}`;
-
+    // 检查残疾人数据是否存在
     await disabilityPersonPage.goto();
-    await disabilityPersonPage.openCreateDialog();
-
-    // 填写残疾人基本信息(必填字段)
-    await disabilityPersonPage.fillBasicForm({
-      name: testPersonName,
-      gender: '男',
-      idCard: generateTestIdCard(timestamp),
-      disabilityId: `证${timestamp}`,
-      disabilityType: '视力残疾',
-      disabilityLevel: '一级',
-      phone: '13800138000',
-      idAddress: `测试身份证地址_${timestamp}`,
-      province: '广东省',
-      city: '广州市',
-    });
+    await disabilityPersonPage.searchByName(FIXED_TEST_PERSON_NAME);
 
-    // 提交表单
-    await disabilityPersonPage.submitForm();
-    await disabilityPersonPage.waitForDialogClosed();
-
-    // 验证残疾人创建成功
-    const personCreated = await disabilityPersonPage.waitForPersonExists(testPersonName, { timeout: TIMEOUTS.TABLE_LOAD });
-    expect(personCreated).toBe(true);
+    const personExists = await disabilityPersonPage.personExists(FIXED_TEST_PERSON_NAME);
+    if (!personExists) {
+      // 创建固定的测试残疾人数据
+      const timestamp = Date.now();
 
-    // 导航到用户管理页面
-    await userManagementPage.goto();
+      await disabilityPersonPage.goto();
+      await disabilityPersonPage.openCreateDialog();
+
+      await disabilityPersonPage.fillBasicForm({
+        name: FIXED_TEST_PERSON_NAME,
+        gender: '男',
+        idCard: generateTestIdCard(timestamp),
+        disabilityId: `E2E_TALENT_${timestamp}`,
+        disabilityType: '视力残疾',
+        disabilityLevel: '一级',
+        phone: '13800138000',
+        idAddress: `E2E测试地址_人才用户专用`,
+        province: '广东省',
+        city: '广州市',
+      });
+
+      await disabilityPersonPage.submitForm();
+      await disabilityPersonPage.waitForDialogClosed();
+
+      const personCreated = await disabilityPersonPage.waitForPersonExists(FIXED_TEST_PERSON_NAME, { timeout: TIMEOUTS.TABLE_LOAD });
+      expect(personCreated).toBe(true);
+    }
   });
 
-  test.afterEach(async ({ disabilityPersonPage }) => {
-    // 清理测试数据(残疾人)
-    await disabilityPersonPage.goto();
-    await disabilityPersonPage.searchByName(testPersonName);
-
-    // 等待搜索结果
-    const exists = await disabilityPersonPage.personExists(testPersonName);
-    if (exists) {
-      await disabilityPersonPage.deleteDisabilityPerson(testPersonName);
+  test.beforeEach(async ({ adminLoginPage, userManagementPage }) => {
+    // 以管理员身份登录后台
+    await adminLoginPage.goto();
+    await adminLoginPage.login('admin', 'admin123');
+    await adminLoginPage.expectLoginSuccess();
 
-      // 等待删除完成
-      const deleted = await disabilityPersonPage.waitForPersonNotExists(testPersonName, { timeout: TIMEOUTS.TABLE_LOAD });
-      expect(deleted).toBe(true);
-    }
+    // 导航到用户管理页面
+    await userManagementPage.goto();
   });
 
   test.describe('基本创建流程测试', () => {
@@ -82,7 +77,7 @@ test.describe('人才用户创建功能', () => {
         password: 'password123',
         nickname: '测试人才用户',
         userType: UserType.TALENT,
-      }, undefined, testPersonName);
+      }, undefined, FIXED_TEST_PERSON_NAME);
 
       // 验证 API 响应成功
       expect(result.responses).toBeDefined();
@@ -126,7 +121,7 @@ test.describe('人才用户创建功能', () => {
         password: 'password123',
         nickname: '列表测试用户',
         userType: UserType.TALENT,
-      }, undefined, testPersonName);
+      }, undefined, FIXED_TEST_PERSON_NAME);
 
       // 验证用户出现在列表中
       const exists = await userManagementPage.userExists(username);
@@ -151,7 +146,7 @@ test.describe('人才用户创建功能', () => {
         phone: '13800138001',
         name: '张三',
         userType: UserType.TALENT,
-      }, undefined, testPersonName);
+      }, undefined, FIXED_TEST_PERSON_NAME);
 
       // 验证 API 响应成功
       const createResponse = result.responses?.find(r => r.url.includes('/api/v1/users'));
@@ -180,7 +175,7 @@ test.describe('人才用户创建功能', () => {
         phone: '13900139000',
         name: '李四',
         userType: UserType.TALENT,
-      }, undefined, testPersonName);
+      }, undefined, FIXED_TEST_PERSON_NAME);
 
       // 验证创建成功(优先检查 API 响应)
       const createResponse = result.responses?.find(r => r.url.includes('/api/v1/users'));
@@ -342,7 +337,7 @@ test.describe('人才用户创建功能', () => {
         password: 'password123',
         nickname: '唯一性测试A',
         userType: UserType.TALENT,
-      }, undefined, testPersonName);
+      }, undefined, FIXED_TEST_PERSON_NAME);
 
       expect(await userManagementPage.userExists(username1)).toBe(true);
 
@@ -352,7 +347,7 @@ test.describe('人才用户创建功能', () => {
         password: 'password123',
         nickname: '唯一性测试B',
         userType: UserType.TALENT,
-      }, undefined, testPersonName);
+      }, undefined, FIXED_TEST_PERSON_NAME);
 
       expect(await userManagementPage.userExists(username2)).toBe(true);
 
@@ -376,7 +371,7 @@ test.describe('人才用户创建功能', () => {
         password: 'password123',
         nickname: '时间戳测试用户',
         userType: UserType.TALENT,
-      }, undefined, testPersonName);
+      }, undefined, FIXED_TEST_PERSON_NAME);
 
       // 验证用户创建成功
       expect(await userManagementPage.userExists(username)).toBe(true);
@@ -399,7 +394,7 @@ test.describe('人才用户创建功能', () => {
         email: `cleanup_${timestamp}@test.com`,
         phone: '13800003333',
         userType: UserType.TALENT,
-      }, undefined, testPersonName);
+      }, undefined, FIXED_TEST_PERSON_NAME);
 
       // 验证用户存在
       const createResponse = result.responses?.find(r => r.url.includes('/api/v1/users'));