talent-mini-login.spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. import { test, expect } from '../../utils/test-setup';
  2. /**
  3. * 人才小程序登录 E2E 测试
  4. *
  5. * 测试人才用户通过小程序登录的功能,验证:
  6. * - 表单验证
  7. * - 登录失败场景
  8. * - 登录成功场景
  9. * - Token 持久性
  10. * - 退出登录
  11. *
  12. * @see {@link ../pages/mini/talent-mini.page.ts} TalentMiniPage
  13. * 测试用户信息(Story 12.3 创建):
  14. * - 账号: 13800128219
  15. * - 密码: admin123
  16. */
  17. /**
  18. * 创建测试用户的辅助函数
  19. *
  20. * 在独立的 browser context 中通过管理后台创建测试用户,
  21. * 避免与小程序登录测试共享 page 实例导致的冲突。
  22. *
  23. * @param browser Playwright browser 实例
  24. * @param userData 用户数据
  25. * @returns 创建的用户名和密码
  26. */
  27. async function createTestUser(browser: typeof test['fixtures']['browser'], userData: {
  28. username: string;
  29. password: string;
  30. nickname?: string;
  31. disabilityPersonId?: number;
  32. }): Promise<{ username: string; password: string }> {
  33. // 动态导入 AdminLoginPage 和 UserManagementPage,避免与小程序测试共享 context
  34. const { AdminLoginPage } = await import('../../pages/admin/login.page');
  35. const { UserManagementPage } = await import('../../pages/admin/user-management.page');
  36. // 创建独立的 browser context
  37. const adminContext = await browser.newContext();
  38. try {
  39. // 在独立 context 中创建 page
  40. const adminPage = await adminContext.newPage();
  41. // 创建管理后台 Page Objects
  42. const adminLoginPage = new AdminLoginPage(adminPage);
  43. const userManagementPage = new UserManagementPage(adminPage);
  44. // 登录管理后台
  45. await adminLoginPage.goto();
  46. await adminLoginPage.login('admin', 'admin123');
  47. // 导航到用户管理页面
  48. await userManagementPage.goto();
  49. // 创建测试用户(使用 TALENT 类型,人才用户)
  50. const result = await userManagementPage.createUser({
  51. username: userData.username,
  52. password: userData.password,
  53. nickname: userData.nickname || '测试人才用户',
  54. disabilityPersonId: userData.disabilityPersonId || 1,
  55. });
  56. // 验证创建成功
  57. expect(result.success).toBe(true);
  58. return { username: userData.username, password: userData.password };
  59. } finally {
  60. // 清理:关闭独立 context
  61. await adminContext.close();
  62. }
  63. }
  64. test.describe('人才小程序登录功能', () => {
  65. test.describe('表单验证测试 (AC3)', () => {
  66. test('账号为空时应该显示错误提示', async ({ talentMiniPage }) => {
  67. // 导航到登录页面
  68. await talentMiniPage.goto();
  69. // 不填写任何信息,直接点击登录按钮
  70. await talentMiniPage.clickLoginButton();
  71. // 验证仍然在登录页面(未跳转)
  72. const currentUrl = talentMiniPage.page.url();
  73. expect(currentUrl).toContain('/talent-mini');
  74. });
  75. test('密码为空时应该显示错误提示', async ({ talentMiniPage }) => {
  76. // 导航到登录页面
  77. await talentMiniPage.goto();
  78. // 只填写账号,不填写密码
  79. await talentMiniPage.fillIdentifier('13800128219');
  80. // 尝试点击登录按钮
  81. await talentMiniPage.clickLoginButton();
  82. // 验证仍然在登录页面(未跳转)
  83. const currentUrl = talentMiniPage.page.url();
  84. expect(currentUrl).toContain('/talent-mini');
  85. });
  86. test('表单验证错误提示应该清晰可见', async ({ talentMiniPage }) => {
  87. // 导航到登录页面
  88. await talentMiniPage.goto();
  89. // 不填写任何信息,直接点击登录
  90. await talentMiniPage.clickLoginButton();
  91. // 验证仍在登录页面
  92. expect(talentMiniPage.page.url()).toContain('/talent-mini');
  93. });
  94. });
  95. test.describe('登录失败测试 (AC2)', () => {
  96. test('使用不存在的用户名登录失败', async ({ talentMiniPage }) => {
  97. // 导航到登录页面
  98. await talentMiniPage.goto();
  99. // 使用不存在的用户名尝试登录(使用有效的手机号格式)
  100. const fakeUsername = `199${Date.now().toString().slice(-8)}`; // 11位数字,符合手机号格式
  101. await talentMiniPage.login(fakeUsername, 'password123');
  102. // 验证显示错误提示
  103. await talentMiniPage.expectLoginError();
  104. // 验证未存储 token
  105. const token = await talentMiniPage.getToken();
  106. expect(token).toBeNull();
  107. });
  108. test('使用错误的密码登录失败', async ({ talentMiniPage }) => {
  109. // 导航到登录页面
  110. await talentMiniPage.goto();
  111. // 使用存在的用户但错误的密码尝试登录
  112. await talentMiniPage.login('13800128219', 'wrongpassword');
  113. // 验证显示错误提示
  114. await talentMiniPage.expectLoginError();
  115. // 验证未存储 token
  116. const token = await talentMiniPage.getToken();
  117. expect(token).toBeNull();
  118. });
  119. test('错误提示内容应该正确', async ({ talentMiniPage }) => {
  120. // 导航到登录页面
  121. await talentMiniPage.goto();
  122. // 使用错误的凭据尝试登录(使用有效的手机号格式)
  123. await talentMiniPage.login('19987654321', 'wrongpassword');
  124. // 验证错误提示包含相关内容
  125. await talentMiniPage.expectLoginError();
  126. });
  127. test('登录失败后登录按钮可以重新点击', async ({ talentMiniPage }) => {
  128. // 导航到登录页面
  129. await talentMiniPage.goto();
  130. // 使用错误的凭据尝试登录
  131. await talentMiniPage.login('19912345678', 'wrongpassword');
  132. // 等待错误提示显示
  133. await talentMiniPage.page.waitForTimeout(1000);
  134. // 验证登录按钮仍然可见且可点击(使用文本选择器)
  135. await expect(talentMiniPage.page.getByText('登录').nth(1)).toBeVisible();
  136. await expect(talentMiniPage.page.getByText('登录').nth(1)).toBeEnabled();
  137. });
  138. });
  139. test.describe.serial('基本登录成功测试 (AC1)', () => {
  140. // 使用 Story 12.3 创建的固定测试用户
  141. const TEST_USER = {
  142. account: '13800128219',
  143. password: 'admin123',
  144. username: 'talent_test_e2e',
  145. };
  146. test.afterEach(async ({ talentMiniPage }) => {
  147. // 清理认证状态
  148. await talentMiniPage.clearAuth();
  149. });
  150. test('应该成功登录人才小程序', async ({ talentMiniPage }) => {
  151. // 1. 导航到登录页面
  152. await talentMiniPage.goto();
  153. // 2. 使用测试用户登录
  154. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  155. // 3. 验证登录成功(URL 跳转到主页)
  156. await talentMiniPage.expectLoginSuccess();
  157. });
  158. test('登录成功后应该显示主页或用户信息', async ({ talentMiniPage }) => {
  159. // 1. 导航到登录页面
  160. await talentMiniPage.goto();
  161. // 2. 使用测试用户登录
  162. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  163. // 3. 验证登录成功
  164. await talentMiniPage.expectLoginSuccess();
  165. // 4. 验证 URL 跳转到主页
  166. const currentUrl = talentMiniPage.page.url();
  167. expect(currentUrl).toMatch(/pages\/index\/index/);
  168. });
  169. test('登录成功后 token 应该正确存储到 localStorage (AC1)', async ({ talentMiniPage }) => {
  170. // 1. 导航到登录页面
  171. await talentMiniPage.goto();
  172. // 2. 使用测试用户登录
  173. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  174. // 3. 验证登录成功
  175. await talentMiniPage.expectLoginSuccess();
  176. // 4. 验证 token 被正确存储到 localStorage(key: talent_token)
  177. const token = await talentMiniPage.getToken();
  178. expect(token).not.toBeNull();
  179. expect(token?.length).toBeGreaterThan(0);
  180. });
  181. });
  182. test.describe.serial('Token 持久性测试 (AC4)', () => {
  183. const TEST_USER = {
  184. account: '13800128219',
  185. password: 'admin123',
  186. username: 'talent_test_e2e',
  187. };
  188. test.afterEach(async ({ talentMiniPage }) => {
  189. // 清理认证状态
  190. await talentMiniPage.clearAuth();
  191. });
  192. test('页面刷新后 token 仍然有效', async ({ talentMiniPage }) => {
  193. // 1. 导航到登录页面
  194. await talentMiniPage.goto();
  195. // 2. 使用测试用户登录
  196. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  197. // 3. 验证登录成功
  198. await talentMiniPage.expectLoginSuccess();
  199. // 4. 获取登录后的 token
  200. const tokenBeforeRefresh = await talentMiniPage.getToken();
  201. expect(tokenBeforeRefresh).not.toBeNull();
  202. // 5. 刷新页面
  203. await talentMiniPage.page.reload();
  204. // 6. 等待页面加载完成
  205. await talentMiniPage.page.waitForLoadState('domcontentloaded');
  206. // 7. 验证 token 仍然存在
  207. const tokenAfterRefresh = await talentMiniPage.getToken();
  208. expect(tokenAfterRefresh).toBe(tokenBeforeRefresh);
  209. });
  210. test('使用已存储 token 可以继续访问', async ({ talentMiniPage }) => {
  211. // 1. 导航到登录页面
  212. await talentMiniPage.goto();
  213. // 2. 使用测试用户登录
  214. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  215. // 3. 验证登录成功
  216. await talentMiniPage.expectLoginSuccess();
  217. // 4. 获取 token
  218. const token = await talentMiniPage.getToken();
  219. expect(token).not.toBeNull();
  220. // 5. 重新导航到小程序(模拟关闭后重新打开)
  221. await talentMiniPage.goto();
  222. // 6. 验证由于 token 存在,用户保持登录状态
  223. await talentMiniPage.page.waitForURL(
  224. url => url.pathname.includes('/pages/index/index') || url.hash.includes('/pages/index/index'),
  225. { timeout: 10000 }
  226. ).catch(() => {
  227. // 如果没有自动跳转,检查当前 URL
  228. const currentUrl = talentMiniPage.page.url();
  229. expect(currentUrl).toMatch(/pages\/index\/index/);
  230. });
  231. });
  232. });
  233. test.describe.serial('退出登录测试 (AC5)', () => {
  234. const TEST_USER = {
  235. account: '13800128219',
  236. password: 'admin123',
  237. username: 'talent_test_e2e',
  238. };
  239. test.afterEach(async ({ talentMiniPage }) => {
  240. // 清理认证状态
  241. await talentMiniPage.clearAuth();
  242. });
  243. test('应该成功退出登录', async ({ talentMiniPage }) => {
  244. // 1. 导航到登录页面
  245. await talentMiniPage.goto();
  246. // 2. 使用测试用户登录
  247. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  248. // 3. 验证登录成功
  249. await talentMiniPage.expectLoginSuccess();
  250. // 4. 退出登录
  251. await talentMiniPage.gotoMorePage();
  252. await talentMiniPage.clickLogout();
  253. // 5. 验证返回到登录页面
  254. await talentMiniPage.expectToBeOnLoginPage();
  255. // 6. 验证 URL 返回到登录页面
  256. const currentUrl = talentMiniPage.page.url();
  257. expect(currentUrl).toContain('/talent-mini');
  258. expect(currentUrl).toContain('/pages/login/index');
  259. });
  260. test('退出后 token 应该被清除', async ({ talentMiniPage }) => {
  261. // 1. 导航到登录页面
  262. await talentMiniPage.goto();
  263. // 2. 使用测试用户登录
  264. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  265. // 3. 验证登录成功
  266. await talentMiniPage.expectLoginSuccess();
  267. // 4. 验证 token 存在
  268. const tokenBeforeLogout = await talentMiniPage.getToken();
  269. expect(tokenBeforeLogout).not.toBeNull();
  270. // 5. 退出登录
  271. await talentMiniPage.gotoMorePage();
  272. await talentMiniPage.clickLogout();
  273. // 6. 等待退出完成
  274. await talentMiniPage.page.waitForTimeout(1000);
  275. // 7. 验证 token 已被清除
  276. const tokenAfterLogout = await talentMiniPage.getToken();
  277. expect(tokenAfterLogout).toBeNull();
  278. });
  279. test('退出后无法访问需要认证的页面', async ({ talentMiniPage }) => {
  280. // 1. 导航到登录页面
  281. await talentMiniPage.goto();
  282. // 2. 使用测试用户登录
  283. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  284. // 3. 验证登录成功
  285. await talentMiniPage.expectLoginSuccess();
  286. // 4. 退出登录
  287. await talentMiniPage.gotoMorePage();
  288. await talentMiniPage.clickLogout();
  289. await talentMiniPage.expectToBeOnLoginPage();
  290. // 5. 尝试直接访问需要认证的页面(主页)
  291. await talentMiniPage.page.goto('/talent-mini/#/talent-mini/pages/index/index');
  292. // 6. 验证被重定向回登录页面
  293. await talentMiniPage.page.waitForLoadState('domcontentloaded');
  294. const currentUrl = talentMiniPage.page.url();
  295. expect(currentUrl).toContain('/pages/login/index');
  296. // 7. 验证登录页面可见
  297. // 验证仍然在登录页面(未跳转)
  298. });
  299. });
  300. test.describe.serial('测试隔离和清理 (AC6)', () => {
  301. test('每个测试使用独立的测试用户', async ({ talentMiniPage, browser }) => {
  302. // 1. 创建唯一的测试用户
  303. const uniqueId = Date.now();
  304. const testUsername = `talent_isolated_${uniqueId}`;
  305. const testPassword = 'Test123!@#';
  306. await createTestUser(browser, {
  307. username: testUsername,
  308. password: testPassword,
  309. nickname: `隔离测试用户_${uniqueId}`,
  310. disabilityPersonId: 1,
  311. });
  312. // 2. 使用该用户登录小程序
  313. await talentMiniPage.goto();
  314. await talentMiniPage.login(testUsername, testPassword);
  315. // 3. 验证登录成功
  316. await talentMiniPage.expectLoginSuccess();
  317. // 4. 验证 token 存储
  318. const token = await talentMiniPage.getToken();
  319. expect(token).not.toBeNull();
  320. });
  321. test('测试后清理认证状态', async ({ talentMiniPage }) => {
  322. // 1. 导航到登录页面
  323. await talentMiniPage.goto();
  324. // 2. 使用测试用户登录
  325. await talentMiniPage.login('13800128219', 'admin123');
  326. // 3. 验证登录成功
  327. await talentMiniPage.expectLoginSuccess();
  328. // 4. 验证 token 存在
  329. const tokenBeforeClear = await talentMiniPage.getToken();
  330. expect(tokenBeforeClear).not.toBeNull();
  331. // 5. 清理认证状态
  332. await talentMiniPage.clearAuth();
  333. // 6. 验证 token 已被清除
  334. const tokenAfterClear = await talentMiniPage.getToken();
  335. expect(tokenAfterClear).toBeNull();
  336. });
  337. });
  338. });