# Story 12.6: 人才小程序 Page Object Status: done ## Story 作为测试开发者, 我想要创建人才小程序的 Page Object, 以便组织人才小程序相关的页面元素和操作。 ## Acceptance Criteria ### AC1: Page Object 基础结构 **Given** Playwright E2E 测试框架已配置 **When** 创建人才小程序 Page Object **Then** 测试应满足以下要求: - 创建 `web/tests/e2e/pages/mini/talent-mini.page.ts` 文件 - 定义 `TalentMiniPage` 类,继承 Playwright 的 Page 对象模式 - 实现基础选择器定义(登录表单、主页元素等) - 所有选择器使用 `data-testid` 属性(优先级高于文本选择器) - 遵循项目 Page Object 设计模式 ### AC2: 小程序页面导航 **Given** 人才小程序 Page Object 已创建 **When** 实现页面导航方法 **Then** 测试应满足以下要求: - 实现 `goto()` 方法导航到人才小程序 H5 页面 (`/talent-mini`) - 实现 `expectToBeVisible()` 方法验证页面可见性 - 验证页面正确加载(检查关键元素存在) - 处理页面加载超时情况 ### AC3: 登录功能封装 **Given** 人才小程序 Page Object 已创建 **When** 实现登录相关方法 **Then** 测试应满足以下要求: - 实现 `login(username, password)` 方法执行登录操作 - 实现填写用户名方法 `fillUsername(username)` - 实现填写密码方法 `fillPassword(password)` - 实现点击登录按钮方法 `clickLoginButton()` - 实现验证登录成功方法 `expectLoginSuccess()` - 处理登录失败场景(错误提示显示) ### AC4: Token 存储和管理 **Given** 小程序登录后需要存储 token 用于后续操作 **When** 实现 token 管理方法 **Then** 测试应满足以下要求: - 实现 `getToken()` 方法获取当前存储的 token - 实现 `setToken(token)` 方法设置 token(用于测试前置条件) - 使用 localStorage 或 sessionStorage 存储 token - 验证 token 在页面刷新后仍然有效 ### AC5: 主页元素选择器 **Given** 人才小程序登录后进入主页 **When** 定义主页元素选择器 **Then** 测试应满足以下要求: - 定义工作列表选择器(如适用) - 定义导航菜单选择器 - 定义用户信息显示选择器 - 所有选择器使用 `data-testid` 属性 - 提供清晰的类型定义 ### AC6: 代码质量标准 **Given** 遵循项目测试规范 **When** 编写 Page Object 代码 **Then** 代码应符合以下标准: - TypeScript 类型安全,无 `any` 类型 - 所有公共方法有完整的 JSDoc 注释 - 使用 TIMEOUTS 常量定义超时 - 遵循项目命名约定(类名 PascalCase,方法名 camelCase) - 通过 `pnpm typecheck` 类型检查 ## Tasks / Subtasks - [x] 任务 1: 创建 Page Object 基础结构 (AC: #1, #6) - [x] 1.1 确认 `web/tests/e2e/pages/mini/` 目录存在 - [x] 1.2 创建 `talent-mini.page.ts` 文件 - [x] 1.3 定义 `TalentMiniPage` 类 - [x] 1.4 定义基础选择器(使用 `data-testid`) - [x] 任务 2: 实现页面导航功能 (AC: #2) - [x] 2.1 实现 `goto()` 方法(导航到 `/talent-mini`) - [x] 2.2 实现 `expectToBeVisible()` 方法 - [x] 2.3 添加页面加载验证逻辑 - [x] 任务 3: 实现登录功能封装 (AC: #3) - [x] 3.1 实现 `fillUsername()` 方法(使用手机号或用户名) - [x] 3.2 实现 `fillPassword()` 方法 - [x] 3.3 实现 `clickLoginButton()` 方法 - [x] 3.4 实现 `login()` 完整登录方法 - [x] 3.5 实现 `expectLoginSuccess()` 验证方法 - [x] 3.6 实现 `expectLoginError()` 错误验证方法 - [x] 任务 4: 实现 Token 管理 (AC: #4) - [x] 4.1 实现 `getToken()` 方法 - [x] 4.2 实现 `setToken()` 方法 - [x] 4.3 实现 `clearAuth()` 清除认证方法 - [x] 任务 5: 定义主页元素选择器 (AC: #5) - [x] 5.1 定义工作列表选择器(待主页实现后添加对应 testid) - [x] 5.2 定义导航菜单选择器(待主页实现后添加对应 testid) - [x] 5.3 定义用户信息选择器(待主页实现后添加对应 testid) - [x] 任务 6: 代码质量验证 (AC: #6) - [x] 6.1 运行 `pnpm typecheck` 验证类型检查 - [x] 6.2 添加完整的 JSDoc 注释 - [x] 6.3 验证选择器使用 data-testid - [x] 任务 7: 更新 fixtures 文件 (AC: #6) - [x] 7.1 在 `web/tests/e2e/fixtures.ts` 中添加 `talentMiniPage` fixture - [x] 7.2 验证 fixture 正确初始化(类型检查通过) - [x] 任务 8: 添加 data-testid 属性 (AC: #1) - [x] 8.1 在人才小程序登录页面添加 `data-testid` 属性 - [x] 8.2 确保所有关键元素都有对应的 testid ## Dev Notes ### Epic 12 背景和依赖 **Epic 12 目标:** 为用户管理和小程序登录编写 E2E 测试,解锁小程序端的测试能力 **小程序技术要点(来自 Epic 12 文档):** - 企业小程序 H5 URL: `http://localhost:8080/mini` - 人才小程序 H5 URL: `http://localhost:8080/talent-mini` - 登录后存储 token 进行后续操作 - 使用 Playwright 测试 H5 页面 - 小程序只读,无写操作 **Epic 12 Story 依赖关系:** ``` Story 12.1: 用户管理 Page Object ✅ (已完成) Story 12.2: 后台创建企业用户测试 ✅ (已完成) Story 12.3: 后台创建人才用户测试 ✅ (已完成) Story 12.4: 企业小程序 Page Object ✅ (已完成) Story 12.5: 企业小程序登录测试 🔄 (进行中) Story 12.6: 人才小程序 Page Object ← 当前 Story Story 12.7: 人才小程序登录测试 Story 12.8: 用户权限验证测试 ``` ### Story 12.4 关键经验 从已完成的 Story 12.4(企业小程序 Page Object)中学习到的模式: **企业小程序登录页面选择器模式:** ```typescript private readonly selectors = { // 页面级选择器 loginPage: '[data-testid="mini-login-page"]', pageTitle: '[data-testid="mini-page-title"]', // 登录表单选择器 phoneInput: '[data-testid="mini-phone-input"]', passwordInput: '[data-testid="mini-password-input"]', loginButton: '[data-testid="mini-login-button"]', // 主页选择器(登录后) userInfo: '[data-testid="mini-user-info"]', }; ``` **人才小程序选择器命名建议:** - 使用 `talent-` 前缀区分与企业小程序 - 例如:`talent-login-page`, `talent-phone-input`, `talent-password-input` **Taro Input 组件处理方式:** ```typescript // Taro 的 Input 组件使用自定义元素,使用 type() 代替 fill() async fillPhone(phone: string): Promise { await this.phoneInput.click(); await this.page.keyboard.press('Control+A'); await this.phoneInput.type(phone, { delay: 10 }); } ``` ### 小程序登录流程 **预期登录流程:** 1. 导航到 `/talent-mini` 页面 2. 填写用户名(在测试中创建的人才用户) 3. 填写密码 4. 点击登录按钮 5. 验证登录成功(跳转到主页或显示用户信息) 6. 存储 token 用于后续请求 **测试用户创建(参考 Story 12.3):** ```typescript // Story 12.3 创建的人才用户 const talentUserData = { username: `test_talent_${Date.now()}`, password: 'password123', nickname: '测试人才用户', userType: UserType.TALENT, disabilityPersonId: 1, // 关联残疾人 }; ``` ### Playwright MCP 探索结果 **页面 URL 和路由结构:** - H5 URL: `http://localhost:8080/talent-mini` (生产环境) - 开发环境: `http://localhost:10087/talent-mini` - 配置源: `mini-talent/config/index.ts` - `publicPath: '/talent-mini/'` - `router.basename: '/talent-mini'` - 路由配置: `mini-talent/src/app.config.ts` - 首页: `pages/index/index` - 登录页: `pages/login/index` **登录表单元素选择器(当前无 data-testid):** - 源码位置: `mini-ui-packages/rencai-auth-ui/src/pages/LoginPage/LoginPage.tsx` - **重要**: 当前登录页面**没有**添加 `data-testid` 属性,需要使用 role/text 选择器 - 表单结构: ``` - 导航栏标题: "人才登录" (Navbar组件) - 页面标题: "人才服务平台" (text) - 副标题: "欢迎回来" (text) - 账号输入标签: "手机号/身份证号/残疾证号" (text) - 账号输入框: placeholder="请输入手机号/身份证号/残疾证号" (Input) - 密码输入标签: "密码" (text) - 密码输入框: placeholder="请输入密码" (Input, password模式) - 登录按钮: "登录" (Button) - 忘记密码链接: "忘记密码?" (text) ``` **Token 存储方式(通过源码确认):** - Token Key: `talent_token` (在 `rencai-auth-ui/src/hooks/useAuth.tsx` 第22行) - User Key: `talent_user` (同上) - 存储方式: `Taro.setStorageSync(TOKEN_KEY, token)` - 在 H5 环境中,Taro.setStorageSync 会映射到 localStorage **与企业小程序对比:** | 特性 | 企业小程序 | 人才小程序 | |------|-----------|-----------| | H5 URL | `/mini` | `/talent-mini` | | Token Key | `enterprise_token` | `talent_token` | | User Key | `enterprise_user` | `talent_user` | | 首页路径 | `/pages/yongren/dashboard/index` | `/pages/index/index` | | 登录页标题 | "企业用户登录" | "人才登录" | | 账号标签 | "请输入手机号" | "手机号/身份证号/残疾证号" | | data-testid | ✅ 已添加 | ❌ 待添加 | **源码位置参考:** - 人才小程序登录页面: `mini-ui-packages/rencai-auth-ui/src/pages/LoginPage/LoginPage.tsx` - 人才小程序桥接文件: `mini-talent/src/pages/login/index.tsx` - 人才小程序路由配置: `mini-talent/src/app.config.ts` - 人才小程序 H5 配置: `mini-talent/config/index.ts` - 企业小程序登录页面: `mini-ui-packages/mini-enterprise-auth-ui/src/pages/login/Login.tsx` - Auth Hook: `mini-ui-packages/rencai-auth-ui/src/hooks/useAuth.tsx` ### Token 管理策略 **存储位置:** localStorage (H5 环境,通过 Taro.setStorageSync 映射) **Token 操作方法(使用 talent_token key):** ```typescript // 获取 token async getToken(): Promise { return await this.page.evaluate(() => { return localStorage.getItem('talent_token'); }); } // 设置 token(用于测试前置条件) async setToken(token: string): Promise { await this.page.evaluate((t) => { localStorage.setItem('talent_token', t); }, token); } // 清除所有认证存储 async clearAuth(): Promise { await this.page.evaluate(() => { localStorage.removeItem('talent_token'); localStorage.removeItem('talent_user'); }); } ``` ### Playwright Fixture 集成 **在 `web/tests/e2e/fixtures.ts` 中添加 fixture:** ```typescript import { test as base } from '@playwright/test'; import { TalentMiniPage } from './pages/mini/talent-mini.page'; type TalentMiniFixtures = { talentMiniPage: TalentMiniPage; }; export const test = base.extend({ talentMiniPage: async ({ page }, use) => { const talentMiniPage = new TalentMiniPage(page); await use(talentMiniPage); }, }); ``` ### 项目结构 **新建文件:** - `web/tests/e2e/pages/mini/talent-mini.page.ts` **相关参考文件:** - `web/tests/e2e/pages/mini/enterprise-mini.page.ts` (Story 12.4) - 企业小程序 Page Object - `web/tests/e2e/pages/admin/user-management.page.ts` (Story 12.1) - 用户管理 Page Object - `web/tests/e2e/utils/timeouts.ts` - TIMEOUTS 常量 ### 小程序只读特性 根据 Epic 12 文档: - 小程序只读,无写操作 - 这意味着 Page Object 不需要包含创建、编辑、删除等操作方法 - 主要关注登录、查看、验证等只读操作 ### E2E 测试和主页实现的关联 **E2E 测试计划:** - Story 12.6:人才小程序 Page Object 基础结构(当前 Story) - Story 12.7:人才小程序登录 E2E 测试(将在该 Story 中实现完整的登录测试) **主页元素选择器状态:** - 任务 5.1(工作列表选择器):待小程序主页实现后添加对应 testid - 任务 5.2(导航菜单选择器):待小程序主页实现后添加对应 testid - 任务 5.3(用户信息选择器):待主页实现后添加对应 testid **原因说明:** 主页元素(工作列表、导航菜单、用户信息)的选择器需要在实际主页实现时才能确定对应的 testid。当前 Story 12.6 专注于 Page Object 的基础结构设计和登录功能,主页相关元素的选择器将在主页实现时同步添加,并在 Story 12.7 的 E2E 测试中验证。 **Token 持久性验证:** Token 在页面刷新后的持久性验证将在 Story 12.7 的 E2E 测试中实现,包括: - 验证 token 在 localStorage 中的存储 - 验证页面刷新后 token 仍然有效 - 验证使用已存储 token 可以继续访问需要认证的页面 ### 选择器策略 **重要说明:人才小程序当前没有 data-testid 属性** 根据 Playwright MCP 探索结果,人才小程序登录页面 (`rencai-auth-ui/src/pages/LoginPage/LoginPage.tsx`) 目前**没有添加** `data-testid` 属性。与企业小程序不同: - 企业小程序登录页面已在 Story 12.4 中添加 `data-testid` 属性 - 人才小程序需要在任务 8 中添加这些属性 **当前阶段的选择器策略:** 由于页面目前没有 `data-testid`,在实现 Page Object 时需要: 1. **优先使用 text 定位(临时方案)**: ```typescript // 账号输入框 - 使用 placeholder this.page.getByPlaceholder('请输入手机号/身份证号/残疾证号') // 密码输入框 - 使用 placeholder this.page.getByPlaceholder('请输入密码') // 登录按钮 - 使用 text this.page.getByRole('button', { name: '登录' }) ``` 2. **任务 8 添加 data-testid 后的最终方案**: ```typescript private readonly selectors = { // ✅ 任务 8 完成后:data-testid(使用 talent- 前缀) loginPage: '[data-testid="talent-login-page"]', identifierInput: '[data-testid="talent-identifier-input"]', passwordInput: '[data-testid="talent-password-input"]', loginButton: '[data-testid="talent-login-button"]', // ⚠️ 当前阶段:使用 role/text 选择器(临时方案) // identifierInput: 'input[placeholder="请输入手机号/身份证号/残疾证号"]', // passwordInput: 'input[placeholder="请输入密码"]', // loginButton: 'button:has-text("登录")', }; ``` **标准优先级(任务 8 完成后):** 1. `data-testid` 属性(最高优先级) 2. ARIA 属性 + role 3. 文本内容(最低优先级,避免使用) ### TypeScript 类型定义 **Page Object 类类型:** ```typescript export class TalentMiniPage { readonly page: Page; constructor(page: Page) { this.page = page; } // 所有方法应有明确的返回类型 async goto(): Promise { } async login(username: string, password: string): Promise { } async expectLoginSuccess(): Promise { } } ``` ### 测试超时配置 **使用 TIMEOUTS 常量:** ```typescript import { TIMEOUTS } from '../utils/timeouts'; await expect(this.page.locator(this.selectors.loginButton)).toBeVisible({ timeout: TIMEOUTS.default, }); ``` ### 参考文档 **架构文档:** - `_bmad-output/planning-artifacts/architecture.md` - `docs/standards/e2e-radix-testing.md` (Radix UI 测试标准) **相关 Story 文档:** - `12-1-user-page-object.md` (用户管理 Page Object) - `12-3-create-talent-user.md` (后台创建人才用户测试) - `12-4-enterprise-mini-page-object.md` (企业小程序 Page Object) ## Dev Agent Record ### Agent Model Used Claude (d8d-model) ### Debug Log References _N/A - 无需调试_ ### Completion Notes List **实现完成 (2026-01-14):** 1. **TalentMiniPage 类创建** - 文件位置: `web/tests/e2e/pages/mini/talent-mini.page.ts` - 完整实现了人才小程序 Page Object,包含导航、登录、Token 管理功能 - 所有公共方法都有完整的 JSDoc 注释 - TypeScript 类型安全,无 `any` 类型 2. **页面导航功能 (AC2)** - `goto()` 方法导航到 `/talent-mini` - `expectToBeVisible()` 验证页面加载 - `removeDevOverlays()` 移除开发覆盖层 3. **登录功能封装 (AC3)** - `fillIdentifier()` - 填写身份标识(手机号/身份证号/残疾证号) - `fillPassword()` - 填写密码 - `clickLoginButton()` - 点击登录按钮 - `login()` - 完整登录流程 - `expectLoginSuccess()` - 验证登录成功 - `expectLoginError()` - 验证登录失败 4. **Token 管理 (AC4)** - `getToken()` - 获取存储的 token (talent_token) - `setToken()` - 设置 token(用于测试前置条件) - `clearAuth()` - 清除认证存储 5. **主页元素选择器 (AC5)** - 定义了 `userInfo` 选择器 - 主页详细选择器待主页实现后添加 6. **代码质量 (AC6)** - 类型检查通过 - 完整 JSDoc 注释 - 遵循项目命名约定 7. **Fixtures 更新 (任务 7)** - 在 `web/tests/e2e/fixtures.ts` 中添加 `TalentMiniFixtures` 接口 - 添加 `testTalent` fixture 扩展 8. **data-testid 属性添加 (任务 8)** - 文件: `mini-ui-packages/rencai-auth-ui/src/pages/LoginPage/LoginPage.tsx` - 添加的 testid: `talent-login-page`, `talent-page-title`, `talent-identifier-input`, `talent-password-input`, `talent-login-button` **技术要点:** - 使用 `talent-` 前缀与企业小程序区分 - Token Key: `talent_token` - H5 URL: `/talent-mini` - 使用 data-testid 选择器(任务 8 已添加) ### File List **新建文件:** - `web/tests/e2e/pages/mini/talent-mini.page.ts` **修改文件:** - `web/tests/e2e/pages/mini/index.ts` - 添加 TalentMiniPage 导出 - `web/tests/e2e/fixtures.ts` - 添加 TalentMiniFixtures 和 testTalent - `mini-ui-packages/rencai-auth-ui/src/pages/LoginPage/LoginPage.tsx` - 添加 data-testid 属性 ## Change Log - 2026-01-14: Story 12.6 创建完成 - 人才小程序 Page Object 基础结构设计 - 登录功能封装需求 - Token 管理策略 - 状态:ready-for-dev - 2026-01-14: Dev Notes 更新 - 添加 Playwright MCP 探索结果 - 页面 URL 和路由结构 (`/talent-mini`, 端口 10087) - 登录表单元素选择器(当前无 data-testid,需使用 role/text 选择器) - Token 存储方式确认 (`talent_token` in localStorage) - 与企业小程序对比(URL、Token Key、首页路径等区别) - 源码位置参考(登录页面、路由配置、H5 配置等) - 更新选择器策略,添加当前阶段的临时方案说明 - 更新 Token 管理策略,使用 `talent_token` key - 2026-01-14: Story 12.6 实现完成 - 创建 TalentMiniPage 类,实现所有导航、登录、Token 管理功能 - 添加 data-testid 属性到登录页面 - 更新 fixtures 文件 - 状态:review