12-7-talent-mini-login.md 17 KB

Story 12.7: 人才小程序登录测试

Status: ready-for-dev

Story

作为测试开发者, 我想要编写人才小程序登录的 E2E 测试, 以便验证人才用户可以成功登录小程序并获得正确的访问权限。

Acceptance Criteria

AC1: 成功登录测试

Given Story 12.3 创建的人才用户存在 When 人才用户使用正确的凭据登录人才小程序 Then 测试应满足以下要求:

  • 登录成功后跳转到主页或显示用户信息
  • Token 正确存储到 localStorage(key: talent_token
  • 用户信息正确显示(昵称、用户类型等)
  • 登录状态在页面刷新后保持

AC2: 登录失败测试

Given 人才小程序登录页面可访问 When 用户使用错误的凭据尝试登录 Then 测试应满足以下要求:

  • 显示错误提示信息
  • 不跳转到主页
  • 不存储 token
  • 登录按钮可重新点击

AC3: 表单验证测试

Given 用户在人才小程序登录页面 When 用户提交未填写必填字段的表单 Then 测试应满足以下要求:

  • 显示验证错误提示
  • 阻止表单提交
  • 高亮错误字段

AC4: Token 持久性测试

Given 用户已成功登录 When 刷新页面或重新访问小程序 Then 测试应满足以下要求:

  • Token 仍然存在于 localStorage
  • 用户保持登录状态
  • 无需重新登录

AC5: 登出功能测试

Given 用户已成功登录 When 用户执行登出操作 Then 测试应满足以下要求:

  • Token 从 localStorage 清除
  • 用户信息不再显示
  • 返回到登录页面

AC6: 测试隔离和清理

Given 多个登录测试需要并行运行 When 执行登录测试套件 Then 测试应满足以下要求:

  • 每个测试使用独立的测试用户
  • 测试后清理认证状态(clearAuth)
  • 测试之间无状态污染
  • 支持并行执行

Tasks / Subtasks

  • [ ] 任务 1: 创建登录测试文件结构 (AC: #1, #6)

    • 1.1 创建 web/tests/e2e/specs/mini/talent-mini-login.spec.ts 文件
    • 1.2 导入必要的依赖(TalentMiniPage, fixtures)
    • 1.3 配置测试套件和基础设置
  • [ ] 任务 2: 实现成功登录测试 (AC: #1)

    • 2.1 使用 Story 12.3 创建的测试用户
    • 2.2 验证登录后页面跳转或用户信息显示
    • 2.3 验证 token 正确存储到 localStorage
    • 2.4 验证用户信息显示正确
  • [ ] 任务 3: 实现登录失败测试 (AC: #2)

    • 3.1 测试错误的用户名场景
    • 3.2 测试错误的密码场景
    • 3.3 测试不存在的用户场景
    • 3.4 验证错误提示显示正确
  • [ ] 任务 4: 实现表单验证测试 (AC: #3)

    • 4.1 测试空用户名提交
    • 4.2 测试空密码提交
    • 4.3 测试两者都为空提交
    • 4.4 验证表单验证消息
  • [ ] 任务 5: 实现 Token 持久性测试 (AC: #4)

    • 5.1 登录后验证 token 存储
    • 5.2 刷新页面验证登录状态保持
    • 5.3 重新访问小程序验证无需重新登录
  • [ ] 任务 6: 实现登出功能测试 (AC: #5)

    • 6.1 测试登出按钮点击(如有)
    • 6.2 验证 token 清除
    • 6.3 验证返回登录页面
  • [ ] 任务 7: 配置测试隔离和清理 (AC: #6)

    • 7.1 在 beforeEach 中设置测试用户
    • 7.2 在 afterEach 中清理认证状态(clearAuth)
    • 7.3 验证测试可以独立运行
    • 7.4 验证并行执行无冲突
  • [ ] 任务 8: 运行测试并验证稳定性 (AC: #1-#6)

    • 8.1 运行所有登录测试
    • 8.2 验证所有测试通过
    • 8.3 运行稳定性测试(5次连续运行)
    • 8.4 记录测试通过率

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 12.7: 人才小程序登录测试 ← 当前 Story
Story 12.8: 用户权限验证测试

Story 12.5(企业小程序登录测试)参考

从企业小程序登录测试中学习到的模式:

测试文件结构:

import { test } from '../../fixtures';
import { UserManagementPage } from '../../pages/admin/user-management.page';
import { EnterpriseMiniPage } from '../../pages/mini/enterprise-mini.page';

test.describe('企业小程序登录测试', () => {
  test.beforeEach(async ({ adminLoginPage, userManagementPage }) => {
    // 登录后台并创建测试用户
  });

  test.afterEach(async ({ enterpriseMiniPage }) => {
    // 清理认证状态
    await enterpriseMiniPage.clearAuth();
  });

  test('应该成功登录企业小程序', async ({ enterpriseMiniPage }) => {
    // 测试登录流程
  });
});

测试场景参考:

  1. 使用后台创建的测试用户登录
  2. 验证 token 存储(localStorage)
  3. 验证用户信息显示
  4. 验证登录失败场景
  5. 验证登出功能

Story 12.6 关键经验

从已完成的 Story 12.6(人才小程序 Page Object)中学习到的模式:

Page Object 方法签名:

class TalentMiniPage {
  // 导航到小程序
  async goto(): Promise<void>

  // 验证页面可见
  async expectToBeVisible(): Promise<void>

  // 完整登录流程
  async login(identifier: string, password: string): Promise<void>

  // 填写身份标识(手机号/身份证号/残疾证号)
  async fillIdentifier(identifier: string): Promise<void>

  // 填写密码
  async fillPassword(password: string): Promise<void>

  // 点击登录按钮
  async clickLoginButton(): Promise<void>

  // 验证登录成功
  async expectLoginSuccess(): Promise<void>

  // 验证登录失败
  async expectLoginError(): Promise<void>

  // Token 管理
  async getToken(): Promise<string | null>
  async setToken(token: string): Promise<void>
  async clearAuth(): Promise<void>
}

选择器信息(Story 12.6 已添加 data-testid):

  • 登录页面: [data-testid="talent-login-page"]
  • 身份标识输入: [data-testid="talent-identifier-input"]
  • 密码输入: [data-testid="talent-password-input"]
  • 登录按钮: [data-testid="talent-login-button"]

Token 管理策略

存储位置: localStorage (H5 环境)

Token Key: talent_token User Key: talent_user

验证 Token 存储:

const token = await talentMiniPage.getToken();
expect(token).toBeTruthy();
// 验证 token 格式(如 JWT)
expect(token?.length).toBeGreaterThan(0);

测试用户创建(Story 12.3)

后台创建人才用户的测试数据:

const talentUserData = {
  username: `test_talent_${Date.now()}`,
  password: 'password123',
  nickname: '测试人才用户',
  userType: UserType.TALENT,
  disabilityPersonId: 1,  // 关联残疾人
};

在测试中创建用户:

test.beforeEach(async ({ adminLoginPage, userManagementPage }) => {
  await adminLoginPage.goto();
  await adminLoginPage.login('admin', 'admin123');

  const uniqueId = Date.now();
  const userData = {
    username: `test_talent_${uniqueId}`,
    password: 'password123',
    nickname: `测试人才用户_${uniqueId}`,
    disabilityPersonId: 1,
  };

  await userManagementPage.goto();
  await userManagementPage.createTalentUser(userData);
});

小程序登录流程

预期登录流程:

  1. 导航到 /talent-mini 页面
  2. 填写身份标识(手机号/身份证号/残疾证号)
  3. 填写密码
  4. 点击登录按钮
  5. 验证登录成功(跳转到主页或显示用户信息)
  6. 验证 token 存储

登录成功验证:

  • 检查页面 URL 变化或主页元素出现
  • 检查 localStorage 中的 talent_token
  • 检查用户信息显示(如适用)

测试文件组织

测试文件位置: web/tests/e2e/specs/mini/talent-mini-login.spec.ts

测试文件结构:

import { testTalent } from '../../fixtures';
import { TalentMiniPage } from '../../pages/mini/talent-mini.page';
import { UserManagementPage } from '../../pages/admin/user-management.page';
import { AdminLoginPage } from '../../pages/admin/admin-login.page';

testTalent.describe('人才小程序登录测试', () => {
  let testUserData: {
    username: string;
    password: string;
    nickname: string;
  };

  testTalent.beforeEach(async ({ adminLoginPage, userManagementPage }) => {
    // 登录后台并创建测试用户
    await adminLoginPage.goto();
    await adminLoginPage.login('admin', 'admin123');

    const uniqueId = Date.now();
    testUserData = {
      username: `test_talent_${uniqueId}`,
      password: 'password123',
      nickname: `测试人才用户_${uniqueId}`,
    };

    await userManagementPage.goto();
    await userManagementPage.createTalentUser({
      username: testUserData.username,
      password: testUserData.password,
      nickname: testUserData.nickname,
      disabilityPersonId: 1,
    });
  });

  testTalent.afterEach(async ({ talentMiniPage }) => {
    // 清理认证状态
    await talentMiniPage.clearAuth();
  });

  testTalent('应该成功登录人才小程序', async ({ talentMiniPage }) => {
    await talentMiniPage.goto();
    await talentMiniPage.login(testUserData.username, testUserData.password);
    await talentMiniPage.expectLoginSuccess();

    // 验证 token 存储
    const token = await talentMiniPage.getToken();
    expect(token).toBeTruthy();
  });

  // 更多测试...
});

测试隔离和并行执行

关键原则:

  1. 每个测试使用唯一的用户名(时间戳)
  2. 测试后清理认证状态(clearAuth)
  3. 避免依赖全局状态
  4. 支持并行执行

测试超时配置

Playwright 配置超时: 60秒(单个测试默认超时)

TIMEOUTS 常量:

import { TIMEOUTS } from '../../utils/timeouts';

测试数据管理

测试用户创建策略:

  • 在 beforeEach 中创建唯一用户(使用时间戳)
  • 测试后可选择清理(或使用唯一的测试用户)
  • 避免硬编码用户名导致冲突

企业小程序 vs 人才小程序对比

特性 企业小程序 人才小程序
H5 URL /mini /talent-mini
Token Key enterprise_token talent_token
User Key enterprise_user talent_user
输入字段 手机号 手机号/身份证号/残疾证号
Page Object EnterpriseMiniPage TalentMiniPage
Fixture testEnterprise testTalent

Playwright 运行命令

运行测试:

cd web
pnpm test:e2e:chromium talent-mini-login

快速失败模式(调试):

timeout 60 pnpm test:e2e:chromium talent-mini-login

查看测试结果:

  • 测试报告: web/test-results/
  • 失败截图: web/test-results/*/

项目结构

新建文件:

  • web/tests/e2e/specs/mini/talent-mini-login.spec.ts

相关参考文件:

  • web/tests/e2e/specs/mini/enterprise-mini-login.spec.ts (Story 12.5) - 企业小程序登录测试
  • web/tests/e2e/pages/mini/talent-mini.page.ts (Story 12.6) - 人才小程序 Page Object
  • web/tests/e2e/pages/admin/user-management.page.ts (Story 12.1) - 用户管理 Page Object
  • web/tests/e2e/specs/admin/create-talent-user.spec.ts (Story 12.3) - 创建人才用户测试

小程序只读特性

根据 Epic 12 文档:

  • 小程序只读,无写操作
  • 登录测试主要关注认证和访问权限
  • 不需要测试创建、编辑、删除等写操作

Playwright MCP 探索结果

通过 Playwright MCP 工具探索人才小程序的实际页面结构和行为,获取了以下关键信息:

登录页面结构

页面 URL:

  • 登录页: http://localhost:8080/talent-mini/#/talent-mini/pages/login/index
  • 登录后主页: http://localhost:8080/talent-mini/#/talent-mini/pages/index/index

页面元素:

  • 账号输入框(接受手机号/身份证号/残疾证号)
  • 密码输入框
  • 登录按钮
  • 忘记密码链接

测试用户信息

用于测试的默认用户:

const testUser = {
  account: '13800128219',    // 账号
  password: 'admin123',      // 密码
  username: 'talent_test_e2e' // 用户名
};

注意:此用户为 Story 12.3 创建的测试用户,用于 E2E 测试验证

表单验证行为

通过实际测试获取的表单验证反馈:

场景 错误提示
空账号提交 "请输入手机号/身份证号/残疾证号"
空密码提交 "请输入密码"
错误密码登录 "登录失败" (HTTP 401)

验证测试示例:

test('应该显示账号必填验证', async ({ talentMiniPage }) => {
  await talentMiniPage.goto();
  await talentMiniPage.fillPassword('admin123');
  await talentMiniPage.clickLoginButton();
  await talentMiniPage.expectValidationError('请输入手机号/身份证号/残疾证号');
});

localStorage 存储格式

Token 存储:

  • Key: talent_token
  • 格式: JSON 字符串(需要解析)
  • 内容示例: "{\"access_token\":\"...\",\"token_type\":\"Bearer\"}"

用户信息存储:

  • Key: talent_user
  • 格式: JSON 字符串(需要解析)
  • 内容示例: "{\"id\":1,\"username\":\"...\",\"nickname\":\"...\"}"

Page Object 实现参考:

async getToken(): Promise<string | null> {
  const storage = await this.page.evaluate(() => {
    return window.localStorage.getItem('talent_token');
  });
  if (!storage) return null;
  const tokenData = JSON.parse(storage);
  return tokenData.access_token;
}

async clearAuth(): Promise<void> {
  await this.page.evaluate(() => {
    window.localStorage.removeItem('talent_token');
    window.localStorage.removeItem('talent_user');
  });
}

退出登录

入口位置:

  • 页面路径:小程序 "更多" 页面
  • 操作:点击 "退出登录" 按钮

预期行为:

  • 清除 localStorage 中的 talent_tokentalent_user
  • 跳转回登录页面

Bug 修复记录:

退出登录功能在开发过程中经历了两次修复:

  1. 第一次修复:localStorage key 不一致

    • 问题:代码使用 token key,但实际存储使用 talent_token
    • 解决:统一使用 talent_tokentalent_user
  2. 第二次修复:页面跳转问题

    • 问题:退出后页面未跳转,仍停留在当前页面
    • 解决:使用 Taro.reLaunch 方法确保页面重定向
    • 代码变更:移除了不必要的延迟,直接调用重定向方法

修复验证:

test('应该成功退出登录', async ({ talentMiniPage }) => {
  await talentMiniPage.goto();
  await talentMiniPage.login('13800128219', 'admin123');

  // 导航到更多页面并退出
  await talentMiniPage.gotoMorePage();
  await talentMiniPage.clickLogout();

  // 验证 token 已清除
  const token = await talentMiniPage.getToken();
  expect(token).toBeNull();

  // 验证返回登录页
  await talentMiniPage.expectToBeOnLoginPage();
});

页面导航注意事项

Taro 小程序路由特性:

  • 使用 Hash 路由模式 (#/talent-mini/pages/...)
  • 页面跳转使用 Taro API(如 Taro.navigateTo, Taro.reLaunch
  • 退出登录应使用 Taro.reLaunch 确保完全重置页面栈

参考文档

架构文档:

  • _bmad-output/planning-artifacts/architecture.md
  • docs/standards/e2e-radix-testing.md (Radix UI 测试标准)

相关 Story 文档:

  • 12-3-create-talent-user.md (后台创建人才用户测试)
  • 12-5-enterprise-mini-login.md (企业小程序登录测试)
  • 12-6-talent-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

新建文件:

  • web/tests/e2e/specs/mini/talent-mini-login.spec.ts

参考文件:

  • web/tests/e2e/specs/mini/enterprise-mini-login.spec.ts (参考测试结构)
  • web/tests/e2e/pages/mini/talent-mini.page.ts (Story 12.6 创建)
  • web/tests/e2e/pages/admin/user-management.page.ts (Story 12.1 创建)

Change Log

  • 2026-01-14: Story 12.7 创建完成
    • 人才小程序登录测试需求
    • 登录成功、失败、表单验证、Token 持久性、登出功能测试
    • 测试隔离和清理策略
    • 状态:ready-for-dev