Status: ready-for-dev
作为测试开发者, 我想要创建人才小程序的 Page Object, 以便组织人才小程序相关的页面元素和操作。
Given Playwright E2E 测试框架已配置 When 创建人才小程序 Page Object Then 测试应满足以下要求:
web/tests/e2e/pages/mini/talent-mini.page.ts 文件TalentMiniPage 类,继承 Playwright 的 Page 对象模式data-testid 属性(优先级高于文本选择器)Given 人才小程序 Page Object 已创建 When 实现页面导航方法 Then 测试应满足以下要求:
goto() 方法导航到人才小程序 H5 页面 (/talent-mini)expectToBeVisible() 方法验证页面可见性Given 人才小程序 Page Object 已创建 When 实现登录相关方法 Then 测试应满足以下要求:
login(username, password) 方法执行登录操作fillUsername(username)fillPassword(password)clickLoginButton()expectLoginSuccess()Given 小程序登录后需要存储 token 用于后续操作 When 实现 token 管理方法 Then 测试应满足以下要求:
getToken() 方法获取当前存储的 tokensetToken(token) 方法设置 token(用于测试前置条件)Given 人才小程序登录后进入主页 When 定义主页元素选择器 Then 测试应满足以下要求:
data-testid 属性Given 遵循项目测试规范 When 编写 Page Object 代码 Then 代码应符合以下标准:
any 类型pnpm typecheck 类型检查[ ] 任务 1: 创建 Page Object 基础结构 (AC: #1, #6)
web/tests/e2e/pages/mini/ 目录存在talent-mini.page.ts 文件TalentMiniPage 类data-testid)[ ] 任务 2: 实现页面导航功能 (AC: #2)
goto() 方法(导航到 /talent-mini)expectToBeVisible() 方法[ ] 任务 3: 实现登录功能封装 (AC: #3)
fillUsername() 方法(使用手机号或用户名)fillPassword() 方法clickLoginButton() 方法login() 完整登录方法expectLoginSuccess() 验证方法expectLoginError() 错误验证方法[ ] 任务 4: 实现 Token 管理 (AC: #4)
getToken() 方法setToken() 方法clearAuth() 清除认证方法[ ] 任务 5: 定义主页元素选择器 (AC: #5)
[ ] 任务 6: 代码质量验证 (AC: #6)
pnpm typecheck 验证类型检查[ ] 任务 7: 更新 fixtures 文件 (AC: #6)
web/tests/e2e/fixtures.ts 中添加 talentMiniPage fixture[ ] 任务 8: 添加 data-testid 属性 (AC: #1)
data-testid 属性Epic 12 目标: 为用户管理和小程序登录编写 E2E 测试,解锁小程序端的测试能力
小程序技术要点(来自 Epic 12 文档):
http://localhost:8080/minihttp://localhost:8080/talent-miniEpic 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(企业小程序 Page Object)中学习到的模式:
企业小程序登录页面选择器模式:
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-inputTaro Input 组件处理方式:
// 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 });
}
预期登录流程:
/talent-mini 页面测试用户创建(参考 Story 12.3):
// Story 12.3 创建的人才用户
const talentUserData = {
username: `test_talent_${Date.now()}`,
password: 'password123',
nickname: '测试人才用户',
userType: UserType.TALENT,
disabilityPersonId: 1, // 关联残疾人
};
存储位置: localStorage 或 sessionStorage(根据实际小程序实现)
Token 操作方法(参考 Story 12.4):
// 获取 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');
});
}
在 web/tests/e2e/fixtures.ts 中添加 fixture:
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 Objectweb/tests/e2e/pages/admin/user-management.page.ts (Story 12.1) - 用户管理 Page Objectweb/tests/e2e/utils/timeouts.ts - TIMEOUTS 常量根据 Epic 12 文档:
E2E 测试计划:
主页元素选择器状态:
原因说明: 主页元素(工作列表、导航菜单、用户信息)的选择器需要在实际主页实现时才能确定对应的 testid。当前 Story 12.6 专注于 Page Object 的基础结构设计和登录功能,主页相关元素的选择器将在主页实现时同步添加,并在 Story 12.7 的 E2E 测试中验证。
Token 持久性验证: Token 在页面刷新后的持久性验证将在 Story 12.7 的 E2E 测试中实现,包括:
优先级(遵循项目标准):
data-testid 属性(最高优先级)示例:
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=用户名',
};
Page Object 类类型:
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 常量:
import { TIMEOUTS } from '../utils/timeouts';
await expect(this.page.locator(this.selectors.loginButton)).toBeVisible({
timeout: TIMEOUTS.default,
});
架构文档:
_bmad-output/planning-artifacts/architecture.mddocs/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)Claude (d8d-model)
N/A - 无需调试
待开发完成后填写
待开发完成后填写