import { test, expect } from '../../utils/test-setup'; /** * 人才小程序登录 E2E 测试 * * 测试人才用户通过小程序登录的功能,验证: * - 表单验证 * - 登录失败场景 * - 登录成功场景 * - Token 持久性 * - 退出登录 * * @see {@link ../pages/mini/talent-mini.page.ts} TalentMiniPage * 测试用户信息(Story 12.3 创建): * - 账号: 13800128219 * - 密码: admin123 */ /** * 创建测试用户的辅助函数 * * 在独立的 browser context 中通过管理后台创建测试用户, * 避免与小程序登录测试共享 page 实例导致的冲突。 * * @param browser Playwright browser 实例 * @param userData 用户数据 * @returns 创建的用户名和密码 */ async function createTestUser(browser: typeof test['fixtures']['browser'], userData: { username: string; password: string; nickname?: string; disabilityPersonId?: number; }): Promise<{ username: string; password: string }> { // 动态导入 AdminLoginPage 和 UserManagementPage,避免与小程序测试共享 context const { AdminLoginPage } = await import('../../pages/admin/login.page'); const { UserManagementPage } = await import('../../pages/admin/user-management.page'); // 创建独立的 browser context const adminContext = await browser.newContext(); try { // 在独立 context 中创建 page const adminPage = await adminContext.newPage(); // 创建管理后台 Page Objects const adminLoginPage = new AdminLoginPage(adminPage); const userManagementPage = new UserManagementPage(adminPage); // 登录管理后台 await adminLoginPage.goto(); await adminLoginPage.login('admin', 'admin123'); // 导航到用户管理页面 await userManagementPage.goto(); // 创建测试用户(使用 TALENT 类型,人才用户) const result = await userManagementPage.createUser({ username: userData.username, password: userData.password, nickname: userData.nickname || '测试人才用户', disabilityPersonId: userData.disabilityPersonId || 1, }); // 验证创建成功 expect(result.success).toBe(true); return { username: userData.username, password: userData.password }; } finally { // 清理:关闭独立 context await adminContext.close(); } } test.describe('人才小程序登录功能', () => { test.describe('表单验证测试 (AC3)', () => { test('不输入任何信息应该无法登录', async ({ talentMiniPage }) => { // 导航到登录页面 await talentMiniPage.goto(); // 不填写任何信息,直接点击登录按钮 await talentMiniPage.clickLoginButton(); // 等待表单验证完成 await talentMiniPage.page.waitForTimeout(500); // 验证仍然在登录页面(未跳转) // Toast 消息在 headless 模式下可能不稳定,但页面不应该跳转 const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toContain('/talent-mini'); expect(currentUrl).toContain('/pages/login/index'); // 验证未存储 token const token = await talentMiniPage.getToken(); expect(token).toBeNull(); }); test('只输入账号不输入密码应该无法登录', async ({ talentMiniPage }) => { // 导航到登录页面 await talentMiniPage.goto(); // 只填写账号,不填写密码 await talentMiniPage.fillIdentifier('13800128219'); // 尝试点击登录按钮 await talentMiniPage.clickLoginButton(); // 等待表单验证完成 await talentMiniPage.page.waitForTimeout(500); // 验证仍然在登录页面(未跳转) const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toContain('/talent-mini'); expect(currentUrl).toContain('/pages/login/index'); // 验证未存储 token const token = await talentMiniPage.getToken(); expect(token).toBeNull(); }); test('只输入密码不输入账号应该无法登录', async ({ talentMiniPage }) => { // 导航到登录页面 await talentMiniPage.goto(); // 只填写密码,不填写账号 await talentMiniPage.fillPassword('admin123'); // 尝试点击登录按钮 await talentMiniPage.clickLoginButton(); // 等待表单验证完成 await talentMiniPage.page.waitForTimeout(500); // 验证仍然在登录页面(未跳转) const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toContain('/talent-mini'); expect(currentUrl).toContain('/pages/login/index'); // 验证未存储 token const token = await talentMiniPage.getToken(); expect(token).toBeNull(); }); }); test.describe('登录失败测试 (AC2)', () => { test('使用不存在的用户名登录失败', async ({ talentMiniPage }) => { // 导航到登录页面 await talentMiniPage.goto(); // 使用不存在的用户名尝试登录 const fakeUsername = '12345678901'; await talentMiniPage.login(fakeUsername, 'password123'); // 等待 API 响应 await talentMiniPage.page.waitForTimeout(2000); // 验证仍然在登录页面(未跳转) const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toContain('/talent-mini'); // 验证未存储 token const token = await talentMiniPage.getToken(); expect(token).toBeNull(); }); test('使用错误的密码登录失败', async ({ talentMiniPage }) => { // 导航到登录页面 await talentMiniPage.goto(); // 使用存在的用户但错误的密码尝试登录 await talentMiniPage.login('13800128219', 'wrongpassword'); // 等待 API 响应 await talentMiniPage.page.waitForTimeout(2000); // 验证仍然在登录页面(未跳转) const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toContain('/talent-mini'); // 验证未存储 token const token = await talentMiniPage.getToken(); expect(token).toBeNull(); }); test('登录失败后登录按钮可以重新点击', async ({ talentMiniPage }) => { // 导航到登录页面 await talentMiniPage.goto(); // 使用错误的凭据尝试登录 await talentMiniPage.login('12345678901', 'wrongpassword'); // 等待 API 响应 await talentMiniPage.page.waitForTimeout(2000); // 验证登录按钮仍然可见且可点击 const loginButton = talentMiniPage.page.getByText('登录').nth(1); await expect(loginButton).toBeVisible(); await expect(loginButton).toBeEnabled(); }); }); test.describe.serial('基本登录成功测试 (AC1)', () => { // 使用 Story 12.3 创建的固定测试用户 const TEST_USER = { account: '13800128219', password: 'admin123', username: 'talent_test_e2e', }; test.afterEach(async ({ talentMiniPage }) => { // 清理认证状态 await talentMiniPage.clearAuth(); }); test('应该成功登录人才小程序', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login(TEST_USER.account, TEST_USER.password); // 3. 验证登录成功(URL 跳转到主页) await talentMiniPage.expectLoginSuccess(); }); test('登录成功后应该显示主页或用户信息', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login(TEST_USER.account, TEST_USER.password); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 验证 URL 跳转到主页 const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toMatch(/pages\/index\/index/); }); test('登录成功后 token 应该正确存储到 localStorage (AC1)', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login(TEST_USER.account, TEST_USER.password); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 验证 token 被正确存储到 localStorage(key: talent_token) const token = await talentMiniPage.getToken(); expect(token).not.toBeNull(); expect(token?.length).toBeGreaterThan(0); }); }); test.describe.serial('Token 持久性测试 (AC4)', () => { const TEST_USER = { account: '13800128219', password: 'admin123', username: 'talent_test_e2e', }; test.afterEach(async ({ talentMiniPage }) => { // 清理认证状态 await talentMiniPage.clearAuth(); }); test('页面刷新后 token 仍然有效', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login(TEST_USER.account, TEST_USER.password); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 获取登录后的 token const tokenBeforeRefresh = await talentMiniPage.getToken(); expect(tokenBeforeRefresh).not.toBeNull(); // 5. 刷新页面 await talentMiniPage.page.reload(); // 6. 等待页面加载完成 await talentMiniPage.page.waitForLoadState('domcontentloaded'); // 7. 验证 token 仍然存在 const tokenAfterRefresh = await talentMiniPage.getToken(); expect(tokenAfterRefresh).toBe(tokenBeforeRefresh); }); test('使用已存储 token 可以继续访问', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login(TEST_USER.account, TEST_USER.password); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 获取 token const token = await talentMiniPage.getToken(); expect(token).not.toBeNull(); // 5. 重新导航到小程序(模拟关闭后重新打开) await talentMiniPage.goto(); // 6. 验证由于 token 存在,用户保持登录状态 await talentMiniPage.page.waitForURL( url => url.pathname.includes('/pages/index/index') || url.hash.includes('/pages/index/index'), { timeout: 10000 } ).catch(() => { // 如果没有自动跳转,检查当前 URL const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toMatch(/pages\/index\/index/); }); }); }); test.describe.serial('退出登录测试 (AC5)', () => { const TEST_USER = { account: '13800128219', password: 'admin123', username: 'talent_test_e2e', }; test.afterEach(async ({ talentMiniPage }) => { // 清理认证状态 await talentMiniPage.clearAuth(); }); test('应该成功退出登录', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login(TEST_USER.account, TEST_USER.password); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 退出登录 await talentMiniPage.gotoMorePage(); await talentMiniPage.clickLogout(); // 5. 验证返回到登录页面 await talentMiniPage.expectToBeOnLoginPage(); // 6. 验证 URL 返回到登录页面 const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toContain('/talent-mini'); expect(currentUrl).toContain('/pages/login/index'); }); test('退出后 token 应该被清除', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login(TEST_USER.account, TEST_USER.password); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 验证 token 存在 const tokenBeforeLogout = await talentMiniPage.getToken(); expect(tokenBeforeLogout).not.toBeNull(); // 5. 退出登录 await talentMiniPage.gotoMorePage(); await talentMiniPage.clickLogout(); // 6. 等待退出完成 await talentMiniPage.page.waitForTimeout(1000); // 7. 验证 token 已被清除 const tokenAfterLogout = await talentMiniPage.getToken(); expect(tokenAfterLogout).toBeNull(); }); test('退出后无法访问需要认证的页面', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login(TEST_USER.account, TEST_USER.password); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 退出登录 await talentMiniPage.gotoMorePage(); await talentMiniPage.clickLogout(); await talentMiniPage.expectToBeOnLoginPage(); // 5. 尝试直接访问需要认证的页面(主页) await talentMiniPage.page.goto('/talent-mini/#/talent-mini/pages/index/index'); // 6. 验证被重定向回登录页面 await talentMiniPage.page.waitForLoadState('domcontentloaded'); const currentUrl = talentMiniPage.page.url(); expect(currentUrl).toContain('/pages/login/index'); // 7. 验证登录页面可见 // 验证仍然在登录页面(未跳转) }); }); test.describe.serial('测试隔离和清理 (AC6)', () => { test('每个测试使用独立的测试用户', async ({ talentMiniPage, browser }) => { // 1. 创建唯一的测试用户 const uniqueId = Date.now(); const testUsername = `talent_isolated_${uniqueId}`; const testPassword = 'Test123!@#'; await createTestUser(browser, { username: testUsername, password: testPassword, nickname: `隔离测试用户_${uniqueId}`, disabilityPersonId: 1, }); // 2. 使用该用户登录小程序 await talentMiniPage.goto(); await talentMiniPage.login(testUsername, testPassword); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 验证 token 存储 const token = await talentMiniPage.getToken(); expect(token).not.toBeNull(); }); test('测试后清理认证状态', async ({ talentMiniPage }) => { // 1. 导航到登录页面 await talentMiniPage.goto(); // 2. 使用测试用户登录 await talentMiniPage.login('13800128219', 'admin123'); // 3. 验证登录成功 await talentMiniPage.expectLoginSuccess(); // 4. 验证 token 存在 const tokenBeforeClear = await talentMiniPage.getToken(); expect(tokenBeforeClear).not.toBeNull(); // 5. 清理认证状态 await talentMiniPage.clearAuth(); // 6. 验证 token 已被清除 const tokenAfterClear = await talentMiniPage.getToken(); expect(tokenAfterClear).toBeNull(); }); }); });