12-1-user-page-object.md 15 KB

Story 12.1: 用户管理 Page Object

Status: done

Story

作为测试开发者, 我想要创建或增强用户管理的 Page Object, 以便组织用户管理相关的页面元素和操作,为后续的用户管理测试提供基础。

Acceptance Criteria

  1. AC1: 创建或增强用户管理 Page Object 文件

    • 文件位置:web/tests/e2e/pages/admin/user-management.page.ts
    • 导出 UserManagementPage 类
    • 继承 Epic 11 的成熟 Page Object 模式
  2. AC2: 实现列表页面选择器和操作方法

    • 页面标题、创建按钮、搜索输入、搜索按钮
    • 用户表格、编辑按钮、删除按钮
    • goto(), expectToBeVisible(), getUserCount(), getUserByUsername(), userExists() 方法
  3. AC3: 实现创建用户对话框的选择器和操作方法

    • createUser() 方法:填写用户表单并提交
    • 支持字段:用户名、密码、昵称、邮箱、手机号、真实姓名
    • 支持用户类型:EMPLOYER(需关联公司)、TALENT(需关联残疾人)、ADMIN
    • 使用 TIMEOUTS 常量
  4. AC4: 实现编辑用户对话框的选择器和操作方法

    • editUser() 方法:编辑用户信息
    • 支持编辑字段:昵称、邮箱、手机号、真实姓名
  5. AC5: 实现删除功能(API 直接删除)

    • 使用 API 直接删除策略(避免 UI 不稳定性)
    • 参考 Epic 11 的模式
  6. AC6: 实现表单填写和提交方法

    • fillUserForm() 辅助方法
    • 处理用户类型选择和关联
  7. AC7: 实现搜索和验证方法

    • searchUsers(), expectUserExists(), expectUserNotExists() 方法
  8. AC8: 代码质量标准

    • 使用 TIMEOUTS 常量
    • 遵循 Epic 11 Page Object 模式
    • TypeScript 严格类型检查

Tasks / Subtasks

  • [x] 任务 1: 分析现有 UserManagementPage 并确定改进方向 (AC: #1, #8)

    • 1.1 读取现有 user-management.page.ts
    • 1.2 对比 Epic 11 的 Page Object 模式
    • 1.3 识别需要改进的部分
  • [x] 任务 2: 实现列表页面选择器和基础方法 (AC: #2)

    • 2.1 定义选择器
    • 2.2 实现 goto() 和 expectToBeVisible()
    • 2.3 实现用户查询方法
  • [x] 任务 3: 实现创建用户对话框 (AC: #3, #6)

    • 3.1 实现 createUser() 方法
    • 3.2 实现 fillUserForm() 辅助方法
    • 3.3 支持用户类型和关联
  • [x] 任务 4: 实现编辑用户对话框 (AC: #4, #6)

    • 4.1 实现 editUser() 方法
    • 4.2 复用 fillUserForm()
  • [x] 任务 5: 实现 API 删除方法 (AC: #5)

    • 5.1 实现 deleteUser() 方法
    • 5.2 使用 API 直接删除策略
  • [x] 任务 6: 实现搜索和验证方法 (AC: #7)

    • 6.1 实现 searchUsers()
    • 6.2 实现断言方法
  • [x] 任务 7: 定义 TypeScript 类型 (AC: #8)

    • 7.1 定义 UserData, UserType, UserUpdateData
  • [x] 任务 8: 添加 JSDoc 注释 (AC: #8)

    • 8.1 为每个方法添加 JSDoc
  • [x] 任务 9: 验证代码质量 (AC: #8)

    • 9.1 运行 typecheck
    • 9.2 运行 lint (pre-commit hook)

Dev Notes

Epic 11 关键经验

Page Object 模式(成熟,可直接复用):

  • 参考 platform-management.page.ts, company-management.page.ts
  • 选择器优先级:data-testid → role + label → text
  • CRUD 方法:create, edit, delete, exists
  • 使用 TIMEOUTS 常量

API 删除策略(关键):

  • 使用 API 直接删除,绕过 UI 不稳定性
  • 删除成功后刷新页面

测试数据唯一性:

  • 使用时间戳:Date.now()

用户类型和关联

  • EMPLOYER:企业用户,需关联公司(Epic 11)
  • TALENT:人才用户,需关联残疾人(Epic 9)
  • ADMIN:管理员,无关联

项目结构

  • 文件:web/tests/e2e/pages/admin/user-management.page.ts
  • 导入:import { TIMEOUTS } from '../../utils/timeouts';

Dev Agent Record

Agent Model Used

Claude (d8d-model)

Implementation Summary (2026-01-13)

已完成用户管理 Page Object 的完整实现,遵循 Epic 11 的成熟 Page Object 模式。

主要实现内容:

  1. 类型定义

    • UserData: 创建用户数据接口
    • UserUpdateData: 编辑用户数据接口
    • UserStatus: 用户状态类型
    • FormSubmitResult: 表单提交结果接口
    • NetworkResponse: 网络响应数据接口
  2. 选择器定义(按功能分组):

    • 页面级选择器:pageTitle, createUserButton, searchInput, searchButton, userTable
    • 对话框选择器:createDialogTitle, editDialogTitle
    • 表单字段选择器:usernameInput, passwordInput, nicknameInput, emailInput, phoneInput, nameInput
    • 用户类型和关联选择器:userTypeSelector, companySelector, disabledPersonSelector
    • 按钮选择器:createSubmitButton, updateSubmitButton, cancelButton, confirmDeleteButton
  3. 导航和基础验证

    • goto(): 导航到用户管理页面
    • expectToBeVisible(): 验证页面关键元素可见
  4. 对话框操作

    • openCreateDialog(): 打开创建用户对话框
    • openEditDialog(): 打开编辑用户对话框
    • openDeleteDialog(): 打开删除确认对话框
    • cancelDialog(): 取消对话框
    • waitForDialogClosed(): 等待对话框关闭
    • confirmDelete(): 确认删除操作
    • cancelDelete(): 取消删除操作
  5. 表单操作

    • fillUserForm(): 填写创建用户表单
    • fillEditUserForm(): 填写编辑用户表单
    • submitForm(): 提交表单并返回结果
  6. CRUD 操作方法

    • createUser(): 创建用户(完整流程)
    • editUser(): 编辑用户(完整流程)
    • deleteUser(): 删除用户(使用 API 直接删除策略)
  7. 搜索和验证方法

    • searchUsers(): 按用户名搜索
    • userExists(): 验证用户是否存在(精确匹配)
    • getUserCount(): 获取用户数量
    • getUserByUsername(): 根据用户名获取用户行
    • expectUserExists(): 期望用户存在
    • expectUserNotExists(): 期望用户不存在

支持的用户类型:

  • ADMIN: 管理员,无关联
  • EMPLOYER: 企业用户,需关联公司
  • TALENT: 人才用户,需关联残疾人

关键特性:

  • 使用 selectRadixOptionAsync 工具处理 Radix UI 选择器
  • API 直接删除策略,绕过 UI 不稳定性
  • 完整的 JSDoc 注释
  • 使用 TIMEOUTS 常量
  • TypeScript 严格类型检查通过

File List

  • 12-1-user-page-object.md
  • web/tests/e2e/pages/admin/user-management.page.ts (完全重写)
  • web/tests/e2e/pages/admin/platform-management.page.ts (参考)
  • web/tests/e2e/pages/admin/company-management.page.ts (参考)
  • web/tests/e2e/utils/timeouts.ts (使用)
  • packages/user-management-ui/src/components/UserManagement.tsx (UI 参考)
  • packages/core-module/user-module/src/entities/user.entity.ts (实体参考)
  • packages/shared-types/src/index.ts (类型参考)

Code Review Results (2026-01-13)

审查概述

  • 审查类型: ADVERSARIAL(对抗性审查)
  • 审查标准: Epic 11 Page Object 黄金标准
  • 参考文件:
    • web/tests/e2e/pages/admin/platform-management.page.ts
    • web/tests/e2e/pages/admin/company-management.page.ts

审查结论

整体评估: ❌ 需要改进

发现 10 个具体问题

  • CRITICAL: 2 个
  • HIGH: 4 个
  • MEDIUM: 3 个
  • LOW: 1 个

问题列表

🔴 CRITICAL 级别

CRITICAL-1: API 端点常量语义不清晰且实现风格不一致

  • 位置: 第 143-145 行
  • 问题: 两个常量指向同一 URL,实际需要动态拼接 /${user.id}
  • 对比: company-management 使用独立端点 getAllCompaniesdeleteCompany
  • 修复建议:

    // 方案 1: 明确说明是 RESTful 风格
    private static readonly API_USERS_BASE = `${API_BASE_URL}/api/v1/users`;
    // 方案 2: 使用独立端点(与 company 一致)
    private static readonly API_GET_ALL_USERS = `${API_BASE_URL}/api/v1/users`;
    private static readonly API_DELETE_USER = `${API_BASE_URL}/api/v1/users/delete`;
    

CRITICAL-2: submitForm 只捕获创建/更新响应,缺少列表刷新响应

  • 位置: 第 436-459 行
  • 问题: 表单提交后通常会刷新列表,但没有捕获 GET /api/v1/users 响应
  • 对比: company-management 同时捕获 getAllCompanies 响应
  • 修复建议: 添加 getAllUsersPromise 响应捕获

🟠 HIGH 级别

HIGH-1: 搜索按钮选择器使用 getByRole,与黄金标准不一致

  • 位置: 第 207 行
  • 问题: page.getByRole('button', { name: '搜索' })
  • 对比: platform 使用 getByTestId('search-button')
  • 修复: 改为 page.getByTestId('search-user-button')

HIGH-2: 更新提交按钮选择器使用模糊正则表达式

  • 位置: 第 234 行,第 450 行
  • 问题: 使用 name: /^(创建|更新)用户$/ 正则匹配
  • 对比: platform 使用 getByTestId('update-submit-button')
  • 修复: 改为 page.getByTestId('update-user-submit-button')

HIGH-3: confirmDelete 按钮选择器不一致

  • 位置: 第 238 行
  • 问题: page.getByRole('button', { name: '删除' }) 过于通用
  • 对比: platform 使用 getByTestId('confirm-delete-button')
  • 修复: 改为 page.getByTestId('confirm-delete-user-button')

HIGH-4: waitForDialogClosed 缺少调试日志

  • 位置: 第 560-577 行
  • 问题: 空 catch 块,没有日志输出
  • 对比: platform 有 console.debug('对话框关闭超时,可能已经关闭')
  • 修复: 添加调试日志

🟡 MEDIUM 级别

MEDIUM-1: confirmDelete 中存在无意义的空注释

  • 位置: 第 585-593 行
  • 问题: // 继续执行 空注释没有提供有用信息
  • 修复: 改为有意义的日志或详细注释

MEDIUM-2: getUserByUsername 返回 Locator | null 不必要

  • 位置: 第 789-792 行
  • 问题: Playwright Locator 可以直接 count(),不需要返回 null
  • 修复: 直接返回 Locator,或删除此方法

MEDIUM-3: 对话框标题选择器未使用 data-testid

  • 位置: 第 212-213 行
  • 问题: getByText('创建用户') 依赖文本
  • 对比: platform 使用 getByTestId('create-platform-dialog-title')
  • 修复: 改为 getByTestId('create-user-dialog-title')

🟢 LOW 级别

LOW-1: searchInput 选择器使用 placeholder

  • 位置: 第 206 行
  • 问题: getByPlaceholder('搜索用户名、昵称或邮箱...') 不稳定
  • 对比: platform 使用 getByTestId('search-input')
  • 修复: 改为 getByTestId('search-user-input')

Action Items(可修复的问题)

ID 问题 修复类型 预估工作量
HIGH-1 搜索按钮选择器 选择器替换 2 分钟
HIGH-2 更新提交按钮选择器 选择器替换 + 逻辑调整 5 分钟
HIGH-3 confirmDelete 按钮选择器 选择器替换 2 分钟
HIGH-4 waitForDialogClosed 日志 添加 console.debug 3 分钟
MEDIUM-1 confirmDelete 空注释 替换为日志 3 分钟
MEDIUM-2 getUserByUsername 返回类型 API 重构 5 分钟
MEDIUM-3 对话框标题选择器 选择器替换 2 分钟
LOW-1 searchInput 选择器 选择器替换 2 分钟

总计: 约 24 分钟

架构合规性检查

检查项 合规状态 说明
选择器优先级 ❌ 部分合规 部分使用 data-testid,部分使用文本
TIMEOUTS 常量使用 ✅ 合规 所有超时都使用 TIMEOUTS 常量
CRUD 方法完整性 ✅ 合规 create, edit, delete, exists 都已实现
API 直接删除策略 ✅ 合规 使用 page.evaluate 绕过 UI
JSDoc 注释 ✅ 合规 所有方法都有 JSDoc
TypeScript 类型安全 ✅ 合规 接口定义完整

总体合规度: 66% (4/6 完全合规)

修复优先级

  1. 立即修复 (阻塞测试):

    • CRITICAL-1: API 端点常量定义
    • CRITICAL-2: submitForm 响应捕获
  2. 高优先级 (测试稳定性):

    • HIGH-1, HIGH-2, HIGH-3: 选择器标准化
    • HIGH-4: 调试日志完善
  3. 中优先级 (代码质量):

    • MEDIUM-1, MEDIUM-2, MEDIUM-3: API 改进
  4. 低优先级 (优化):

    • LOW-1: 选择器优化

修复记录 (2026-01-13)

所有代码审查问题已全部修复完成!

已修复问题清单

🔴 CRITICAL 级别(2个)

CRITICAL-1: API 端点常量语义不清晰 ✅ 已修复

  • 修复内容:
    • 添加 API_USERS_BASE 基础端点常量
    • API_DELETE_USER 添加注释说明需要拼接 /${userId}
    • 提高代码可读性和维护性
  • 修复位置: 第 142-147 行

CRITICAL-2: submitForm 缺少列表刷新响应捕获 ✅ 已修复

  • 修复内容:
    • 添加 getAllUsersPromise 响应捕获
    • 在 Promise.all 中包含列表刷新响应
    • 确保表单提交后完整等待所有 API 响应
  • 修复位置: 第 448-452 行、第 464-468 行

🟠 HIGH 级别(4个)

HIGH-1: 搜索按钮选择器 ✅ 已修复

  • 修复内容: 改为 page.getByTestId('search-user-button')
  • 修复位置: 第 209 行

HIGH-2: 更新提交按钮选择器 ✅ 已修复

  • 修复内容: 改为 page.getByTestId('update-user-submit-button')
  • 修复位置: 第 236 行

HIGH-3: confirmDelete 按钮选择器 ✅ 已修复

  • 修复内容: 改为 page.getByTestId('confirm-delete-user-button')
  • 修复位置: 第 240 行

HIGH-4: waitForDialogClosed 日志 ✅ 已修复

  • 修复内容: 添加有意义的调试日志
  • 修复位置: 第 575、582 行

🟡 MEDIUM 级别(3个)

MEDIUM-1: confirmDelete 空注释 ✅ 已修复

  • 修复内容: 替换为有意义的调试日志
  • 修复位置: 第 597、602 行

MEDIUM-2: getUserByUsername 返回类型 ✅ 已修复

  • 修复内容: 直接返回 Locator,移除不必要的 null 返回
  • 修复位置: 第 799-801 行

MEDIUM-3: 对话框标题选择器 ✅ 已修复

  • 修复内容: 改为使用 data-testid 选择器
    • createDialogTitle: page.getByTestId('create-user-dialog-title')
    • editDialogTitle: page.getByTestId('edit-user-dialog-title')
  • 修复位置: 第 214-215 行

🟢 LOW 级别(1个)

LOW-1: searchInput 选择器 ✅ 已修复

  • 修复内容: 改为 page.getByTestId('search-user-input')
  • 修复位置: 第 208 行

修复验证

  • 类型检查: pnpm typecheck 通过,无类型错误
  • 代码质量: 所有选择器统一使用 data-testid
  • 调试日志: 所有关键方法都有有意义的调试日志
  • 架构合规: 符合 Epic 11 Page Object 黄金标准

架构合规性检查(修复后)

检查项 修复前 修复后 说明
选择器优先级 ❌ 部分合规 ✅ 完全合规 全部使用 data-testid
TIMEOUTS 常量使用 ✅ 合规 ✅ 合规 所有超时都使用 TIMEOUTS 常量
CRUD 方法完整性 ✅ 合规 ✅ 合规 create, edit, delete, exists 都已实现
API 直接删除策略 ✅ 合规 ✅ 合规 使用 page.evaluate 绕过 UI
JSDoc 注释 ✅ 合规 ✅ 合规 所有方法都有 JSDoc
TypeScript 类型安全 ✅ 合规 ✅ 合规 接口定义完整

总体合规度: 100% (6/6 完全合规)

修复总结

  • 修复问题总数: 10 个(2 CRITICAL + 4 HIGH + 3 MEDIUM + 1 LOW)
  • 修复时间: 约 25 分钟
  • 代码文件: /mnt/code/188-179-template-6/web/tests/e2e/pages/admin/user-management.page.ts
  • 状态: ✅ 所有代码审查问题已修复,Story 12.1 标记为 done