talent-mini-login.spec.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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. await talentMiniPage.page.waitForTimeout(500);
  73. // 验证仍然在登录页面(未跳转)
  74. // Toast 消息在 headless 模式下可能不稳定,但页面不应该跳转
  75. const currentUrl = talentMiniPage.page.url();
  76. expect(currentUrl).toContain('/talent-mini');
  77. expect(currentUrl).toContain('/pages/login/index');
  78. // 验证未存储 token
  79. const token = await talentMiniPage.getToken();
  80. expect(token).toBeNull();
  81. });
  82. test('只输入账号不输入密码应该无法登录', async ({ talentMiniPage }) => {
  83. // 导航到登录页面
  84. await talentMiniPage.goto();
  85. // 只填写账号,不填写密码
  86. await talentMiniPage.fillIdentifier('13800128219');
  87. // 尝试点击登录按钮
  88. await talentMiniPage.clickLoginButton();
  89. // 等待表单验证完成
  90. await talentMiniPage.page.waitForTimeout(500);
  91. // 验证仍然在登录页面(未跳转)
  92. const currentUrl = talentMiniPage.page.url();
  93. expect(currentUrl).toContain('/talent-mini');
  94. expect(currentUrl).toContain('/pages/login/index');
  95. // 验证未存储 token
  96. const token = await talentMiniPage.getToken();
  97. expect(token).toBeNull();
  98. });
  99. test('只输入密码不输入账号应该无法登录', async ({ talentMiniPage }) => {
  100. // 导航到登录页面
  101. await talentMiniPage.goto();
  102. // 只填写密码,不填写账号
  103. await talentMiniPage.fillPassword('admin123');
  104. // 尝试点击登录按钮
  105. await talentMiniPage.clickLoginButton();
  106. // 等待表单验证完成
  107. await talentMiniPage.page.waitForTimeout(500);
  108. // 验证仍然在登录页面(未跳转)
  109. const currentUrl = talentMiniPage.page.url();
  110. expect(currentUrl).toContain('/talent-mini');
  111. expect(currentUrl).toContain('/pages/login/index');
  112. // 验证未存储 token
  113. const token = await talentMiniPage.getToken();
  114. expect(token).toBeNull();
  115. });
  116. });
  117. test.describe('登录失败测试 (AC2)', () => {
  118. test('使用不存在的用户名登录失败', async ({ talentMiniPage }) => {
  119. // 导航到登录页面
  120. await talentMiniPage.goto();
  121. // 使用不存在的用户名尝试登录
  122. const fakeUsername = '12345678901';
  123. await talentMiniPage.login(fakeUsername, 'password123');
  124. // 等待 API 响应
  125. await talentMiniPage.page.waitForTimeout(2000);
  126. // 验证仍然在登录页面(未跳转)
  127. const currentUrl = talentMiniPage.page.url();
  128. expect(currentUrl).toContain('/talent-mini');
  129. // 验证未存储 token
  130. const token = await talentMiniPage.getToken();
  131. expect(token).toBeNull();
  132. });
  133. test('使用错误的密码登录失败', async ({ talentMiniPage }) => {
  134. // 导航到登录页面
  135. await talentMiniPage.goto();
  136. // 使用存在的用户但错误的密码尝试登录
  137. await talentMiniPage.login('13800128219', 'wrongpassword');
  138. // 等待 API 响应
  139. await talentMiniPage.page.waitForTimeout(2000);
  140. // 验证仍然在登录页面(未跳转)
  141. const currentUrl = talentMiniPage.page.url();
  142. expect(currentUrl).toContain('/talent-mini');
  143. // 验证未存储 token
  144. const token = await talentMiniPage.getToken();
  145. expect(token).toBeNull();
  146. });
  147. test('登录失败后登录按钮可以重新点击', async ({ talentMiniPage }) => {
  148. // 导航到登录页面
  149. await talentMiniPage.goto();
  150. // 使用错误的凭据尝试登录
  151. await talentMiniPage.login('12345678901', 'wrongpassword');
  152. // 等待 API 响应
  153. await talentMiniPage.page.waitForTimeout(2000);
  154. // 验证登录按钮仍然可见且可点击
  155. const loginButton = talentMiniPage.page.getByText('登录').nth(1);
  156. await expect(loginButton).toBeVisible();
  157. await expect(loginButton).toBeEnabled();
  158. });
  159. });
  160. test.describe.serial('基本登录成功测试 (AC1)', () => {
  161. // 使用 Story 12.3 创建的固定测试用户
  162. const TEST_USER = {
  163. account: '13800128219',
  164. password: 'admin123',
  165. username: 'talent_test_e2e',
  166. };
  167. test.afterEach(async ({ talentMiniPage }) => {
  168. // 清理认证状态
  169. await talentMiniPage.clearAuth();
  170. });
  171. test('应该成功登录人才小程序', async ({ talentMiniPage }) => {
  172. // 1. 导航到登录页面
  173. await talentMiniPage.goto();
  174. // 2. 使用测试用户登录
  175. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  176. // 3. 验证登录成功(URL 跳转到主页)
  177. await talentMiniPage.expectLoginSuccess();
  178. });
  179. test('登录成功后应该显示主页或用户信息', async ({ talentMiniPage }) => {
  180. // 1. 导航到登录页面
  181. await talentMiniPage.goto();
  182. // 2. 使用测试用户登录
  183. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  184. // 3. 验证登录成功
  185. await talentMiniPage.expectLoginSuccess();
  186. // 4. 验证 URL 跳转到主页
  187. const currentUrl = talentMiniPage.page.url();
  188. expect(currentUrl).toMatch(/pages\/index\/index/);
  189. });
  190. test('登录成功后 token 应该正确存储到 localStorage (AC1)', async ({ talentMiniPage }) => {
  191. // 1. 导航到登录页面
  192. await talentMiniPage.goto();
  193. // 2. 使用测试用户登录
  194. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  195. // 3. 验证登录成功
  196. await talentMiniPage.expectLoginSuccess();
  197. // 4. 验证 token 被正确存储到 localStorage(key: talent_token)
  198. const token = await talentMiniPage.getToken();
  199. expect(token).not.toBeNull();
  200. expect(token?.length).toBeGreaterThan(0);
  201. });
  202. });
  203. test.describe.serial('Token 持久性测试 (AC4)', () => {
  204. const TEST_USER = {
  205. account: '13800128219',
  206. password: 'admin123',
  207. username: 'talent_test_e2e',
  208. };
  209. test.afterEach(async ({ talentMiniPage }) => {
  210. // 清理认证状态
  211. await talentMiniPage.clearAuth();
  212. });
  213. test('页面刷新后 token 仍然有效', async ({ talentMiniPage }) => {
  214. // 1. 导航到登录页面
  215. await talentMiniPage.goto();
  216. // 2. 使用测试用户登录
  217. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  218. // 3. 验证登录成功
  219. await talentMiniPage.expectLoginSuccess();
  220. // 4. 获取登录后的 token
  221. const tokenBeforeRefresh = await talentMiniPage.getToken();
  222. expect(tokenBeforeRefresh).not.toBeNull();
  223. // 5. 刷新页面
  224. await talentMiniPage.page.reload();
  225. // 6. 等待页面加载完成
  226. await talentMiniPage.page.waitForLoadState('domcontentloaded');
  227. // 7. 验证 token 仍然存在
  228. const tokenAfterRefresh = await talentMiniPage.getToken();
  229. expect(tokenAfterRefresh).toBe(tokenBeforeRefresh);
  230. });
  231. test('使用已存储 token 可以继续访问', async ({ talentMiniPage }) => {
  232. // 1. 导航到登录页面
  233. await talentMiniPage.goto();
  234. // 2. 使用测试用户登录
  235. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  236. // 3. 验证登录成功
  237. await talentMiniPage.expectLoginSuccess();
  238. // 4. 获取 token
  239. const token = await talentMiniPage.getToken();
  240. expect(token).not.toBeNull();
  241. // 5. 重新导航到小程序(模拟关闭后重新打开)
  242. await talentMiniPage.goto();
  243. // 6. 验证由于 token 存在,用户保持登录状态
  244. await talentMiniPage.page.waitForURL(
  245. url => url.pathname.includes('/pages/index/index') || url.hash.includes('/pages/index/index'),
  246. { timeout: 10000 }
  247. ).catch(() => {
  248. // 如果没有自动跳转,检查当前 URL
  249. const currentUrl = talentMiniPage.page.url();
  250. expect(currentUrl).toMatch(/pages\/index\/index/);
  251. });
  252. });
  253. });
  254. test.describe.serial('退出登录测试 (AC5)', () => {
  255. const TEST_USER = {
  256. account: '13800128219',
  257. password: 'admin123',
  258. username: 'talent_test_e2e',
  259. };
  260. test.afterEach(async ({ talentMiniPage }) => {
  261. // 清理认证状态
  262. await talentMiniPage.clearAuth();
  263. });
  264. test('应该成功退出登录', async ({ talentMiniPage }) => {
  265. // 1. 导航到登录页面
  266. await talentMiniPage.goto();
  267. // 2. 使用测试用户登录
  268. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  269. // 3. 验证登录成功
  270. await talentMiniPage.expectLoginSuccess();
  271. // 4. 退出登录
  272. await talentMiniPage.gotoMorePage();
  273. await talentMiniPage.clickLogout();
  274. // 5. 验证返回到登录页面
  275. await talentMiniPage.expectToBeOnLoginPage();
  276. // 6. 验证 URL 返回到登录页面
  277. const currentUrl = talentMiniPage.page.url();
  278. expect(currentUrl).toContain('/talent-mini');
  279. expect(currentUrl).toContain('/pages/login/index');
  280. });
  281. test('退出后 token 应该被清除', async ({ talentMiniPage }) => {
  282. // 1. 导航到登录页面
  283. await talentMiniPage.goto();
  284. // 2. 使用测试用户登录
  285. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  286. // 3. 验证登录成功
  287. await talentMiniPage.expectLoginSuccess();
  288. // 4. 验证 token 存在
  289. const tokenBeforeLogout = await talentMiniPage.getToken();
  290. expect(tokenBeforeLogout).not.toBeNull();
  291. // 5. 退出登录
  292. await talentMiniPage.gotoMorePage();
  293. await talentMiniPage.clickLogout();
  294. // 6. 等待退出完成
  295. await talentMiniPage.page.waitForTimeout(1000);
  296. // 7. 验证 token 已被清除
  297. const tokenAfterLogout = await talentMiniPage.getToken();
  298. expect(tokenAfterLogout).toBeNull();
  299. });
  300. test('退出后无法访问需要认证的页面', async ({ talentMiniPage }) => {
  301. // 1. 导航到登录页面
  302. await talentMiniPage.goto();
  303. // 2. 使用测试用户登录
  304. await talentMiniPage.login(TEST_USER.account, TEST_USER.password);
  305. // 3. 验证登录成功
  306. await talentMiniPage.expectLoginSuccess();
  307. // 4. 退出登录
  308. await talentMiniPage.gotoMorePage();
  309. await talentMiniPage.clickLogout();
  310. await talentMiniPage.expectToBeOnLoginPage();
  311. // 5. 尝试直接访问需要认证的页面(主页)
  312. await talentMiniPage.page.goto('/talent-mini/#/talent-mini/pages/index/index');
  313. // 6. 验证被重定向回登录页面
  314. await talentMiniPage.page.waitForLoadState('domcontentloaded');
  315. const currentUrl = talentMiniPage.page.url();
  316. expect(currentUrl).toContain('/pages/login/index');
  317. // 7. 验证登录页面可见
  318. // 验证仍然在登录页面(未跳转)
  319. });
  320. });
  321. test.describe.serial('测试隔离和清理 (AC6)', () => {
  322. test('每个测试使用独立的测试用户', async ({ talentMiniPage, browser }) => {
  323. // 1. 创建唯一的测试用户
  324. const uniqueId = Date.now();
  325. const testUsername = `talent_isolated_${uniqueId}`;
  326. const testPassword = 'Test123!@#';
  327. await createTestUser(browser, {
  328. username: testUsername,
  329. password: testPassword,
  330. nickname: `隔离测试用户_${uniqueId}`,
  331. disabilityPersonId: 1,
  332. });
  333. // 2. 使用该用户登录小程序
  334. await talentMiniPage.goto();
  335. await talentMiniPage.login(testUsername, testPassword);
  336. // 3. 验证登录成功
  337. await talentMiniPage.expectLoginSuccess();
  338. // 4. 验证 token 存储
  339. const token = await talentMiniPage.getToken();
  340. expect(token).not.toBeNull();
  341. });
  342. test('测试后清理认证状态', async ({ talentMiniPage }) => {
  343. // 1. 导航到登录页面
  344. await talentMiniPage.goto();
  345. // 2. 使用测试用户登录
  346. await talentMiniPage.login('13800128219', 'admin123');
  347. // 3. 验证登录成功
  348. await talentMiniPage.expectLoginSuccess();
  349. // 4. 验证 token 存在
  350. const tokenBeforeClear = await talentMiniPage.getToken();
  351. expect(tokenBeforeClear).not.toBeNull();
  352. // 5. 清理认证状态
  353. await talentMiniPage.clearAuth();
  354. // 6. 验证 token 已被清除
  355. const tokenAfterClear = await talentMiniPage.getToken();
  356. expect(tokenAfterClear).toBeNull();
  357. });
  358. });
  359. });