|
|
@@ -0,0 +1,374 @@
|
|
|
+# Story 12.6: 人才小程序 Page Object
|
|
|
+
|
|
|
+Status: ready-for-dev
|
|
|
+
|
|
|
+<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
|
|
+
|
|
|
+## 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
|
|
|
+
|
|
|
+- [ ] 任务 1: 创建 Page Object 基础结构 (AC: #1, #6)
|
|
|
+ - [ ] 1.1 确认 `web/tests/e2e/pages/mini/` 目录存在
|
|
|
+ - [ ] 1.2 创建 `talent-mini.page.ts` 文件
|
|
|
+ - [ ] 1.3 定义 `TalentMiniPage` 类
|
|
|
+ - [ ] 1.4 定义基础选择器(使用 `data-testid`)
|
|
|
+
|
|
|
+- [ ] 任务 2: 实现页面导航功能 (AC: #2)
|
|
|
+ - [ ] 2.1 实现 `goto()` 方法(导航到 `/talent-mini`)
|
|
|
+ - [ ] 2.2 实现 `expectToBeVisible()` 方法
|
|
|
+ - [ ] 2.3 添加页面加载验证逻辑
|
|
|
+
|
|
|
+- [ ] 任务 3: 实现登录功能封装 (AC: #3)
|
|
|
+ - [ ] 3.1 实现 `fillUsername()` 方法(使用手机号或用户名)
|
|
|
+ - [ ] 3.2 实现 `fillPassword()` 方法
|
|
|
+ - [ ] 3.3 实现 `clickLoginButton()` 方法
|
|
|
+ - [ ] 3.4 实现 `login()` 完整登录方法
|
|
|
+ - [ ] 3.5 实现 `expectLoginSuccess()` 验证方法
|
|
|
+ - [ ] 3.6 实现 `expectLoginError()` 错误验证方法
|
|
|
+
|
|
|
+- [ ] 任务 4: 实现 Token 管理 (AC: #4)
|
|
|
+ - [ ] 4.1 实现 `getToken()` 方法
|
|
|
+ - [ ] 4.2 实现 `setToken()` 方法
|
|
|
+ - [ ] 4.3 实现 `clearAuth()` 清除认证方法
|
|
|
+
|
|
|
+- [ ] 任务 5: 定义主页元素选择器 (AC: #5)
|
|
|
+ - [ ] 5.1 定义工作列表选择器(待主页实现后添加对应 testid)
|
|
|
+ - [ ] 5.2 定义导航菜单选择器(待主页实现后添加对应 testid)
|
|
|
+ - [ ] 5.3 定义用户信息选择器(待主页实现后添加对应 testid)
|
|
|
+
|
|
|
+- [ ] 任务 6: 代码质量验证 (AC: #6)
|
|
|
+ - [ ] 6.1 运行 `pnpm typecheck` 验证类型检查
|
|
|
+ - [ ] 6.2 添加完整的 JSDoc 注释
|
|
|
+ - [ ] 6.3 验证选择器使用 data-testid
|
|
|
+
|
|
|
+- [ ] 任务 7: 更新 fixtures 文件 (AC: #6)
|
|
|
+ - [ ] 7.1 在 `web/tests/e2e/fixtures.ts` 中添加 `talentMiniPage` fixture
|
|
|
+ - [ ] 7.2 验证 fixture 正确初始化(类型检查通过)
|
|
|
+
|
|
|
+- [ ] 任务 8: 添加 data-testid 属性 (AC: #1)
|
|
|
+ - [ ] 8.1 在人才小程序登录页面添加 `data-testid` 属性
|
|
|
+ - [ ] 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<void> {
|
|
|
+ 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, // 关联残疾人
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### Token 管理策略
|
|
|
+
|
|
|
+**存储位置:** localStorage 或 sessionStorage(根据实际小程序实现)
|
|
|
+
|
|
|
+**Token 操作方法(参考 Story 12.4):**
|
|
|
+```typescript
|
|
|
+// 获取 token
|
|
|
+async getToken(): Promise<string | null> {
|
|
|
+ return await this.page.evaluate(() => {
|
|
|
+ return localStorage.getItem('token') || sessionStorage.getItem('token');
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 设置 token(用于测试前置条件)
|
|
|
+async setToken(token: string): Promise<void> {
|
|
|
+ await this.page.evaluate((t) => {
|
|
|
+ localStorage.setItem('token', t);
|
|
|
+ }, token);
|
|
|
+}
|
|
|
+
|
|
|
+// 清除所有认证存储
|
|
|
+async clearAuth(): Promise<void> {
|
|
|
+ await this.page.evaluate(() => {
|
|
|
+ localStorage.removeItem('token');
|
|
|
+ localStorage.removeItem('auth_token');
|
|
|
+ sessionStorage.removeItem('token');
|
|
|
+ sessionStorage.removeItem('auth_token');
|
|
|
+ });
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 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<TalentMiniFixtures>({
|
|
|
+ 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 可以继续访问需要认证的页面
|
|
|
+
|
|
|
+### 选择器策略
|
|
|
+
|
|
|
+**优先级(遵循项目标准):**
|
|
|
+1. `data-testid` 属性(最高优先级)
|
|
|
+2. ARIA 属性 + role
|
|
|
+3. 文本内容(最低优先级,避免使用)
|
|
|
+
|
|
|
+**示例:**
|
|
|
+```typescript
|
|
|
+private readonly selectors = {
|
|
|
+ // ✅ 优先:data-testid(使用 talent- 前缀)
|
|
|
+ usernameInput: '[data-testid="talent-username-input"]',
|
|
|
+ passwordInput: '[data-testid="talent-password-input"]',
|
|
|
+ loginButton: '[data-testid="talent-login-button"]',
|
|
|
+
|
|
|
+ // ⚠️ 备选:ARIA + role(如 data-testid 不可用)
|
|
|
+ // usernameInput: '[aria-label="用户名"]',
|
|
|
+
|
|
|
+ // ❌ 避免:纯文本选择器
|
|
|
+ // usernameInput: 'text=用户名',
|
|
|
+};
|
|
|
+```
|
|
|
+
|
|
|
+### TypeScript 类型定义
|
|
|
+
|
|
|
+**Page Object 类类型:**
|
|
|
+```typescript
|
|
|
+export class TalentMiniPage {
|
|
|
+ readonly page: Page;
|
|
|
+
|
|
|
+ constructor(page: Page) {
|
|
|
+ this.page = page;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 所有方法应有明确的返回类型
|
|
|
+ async goto(): Promise<void> { }
|
|
|
+ async login(username: string, password: string): Promise<void> { }
|
|
|
+ async expectLoginSuccess(): Promise<void> { }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 测试超时配置
|
|
|
+
|
|
|
+**使用 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
|
|
|
+
|
|
|
+_待开发完成后填写_
|
|
|
+
|
|
|
+### File List
|
|
|
+
|
|
|
+_待开发完成后填写_
|
|
|
+
|
|
|
+## Change Log
|
|
|
+
|
|
|
+- 2026-01-14: Story 12.6 创建完成
|
|
|
+ - 人才小程序 Page Object 基础结构设计
|
|
|
+ - 登录功能封装需求
|
|
|
+ - Token 管理策略
|
|
|
+ - 状态:ready-for-dev
|