enterprise-mini-login.spec.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. import { test, expect } from '../../utils/test-setup';
  2. import { AdminLoginPage } from '../../pages/admin/login.page';
  3. import { UserManagementPage } from '../../pages/admin/user-management.page';
  4. import { UserType } from '@d8d/shared-types';
  5. /**
  6. * 企业小程序登录 E2E 测试
  7. *
  8. * 测试企业用户通过小程序登录的功能,验证:
  9. * - 登录失败场景
  10. * - 表单验证
  11. * - 登录成功场景(使用独立 context 创建测试用户)
  12. * - Token 持久性
  13. * - 退出登录
  14. *
  15. * @see {@link ../pages/mini/enterprise-mini.page.ts} EnterpriseMiniPage
  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. phone?: string;
  32. email?: string;
  33. userType?: UserType;
  34. companyName?: string;
  35. }): Promise<{ username: string; password: string }> {
  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. // 创建测试用户(使用 EMPLOYER 类型,企业用户)
  50. const result = await userManagementPage.createUser({
  51. username: userData.username,
  52. password: userData.password,
  53. nickname: userData.nickname || '测试企业用户',
  54. phone: userData.phone,
  55. email: userData.email,
  56. userType: userData.userType || UserType.EMPLOYER,
  57. }, userData.companyName || '测试公司_E2E');
  58. // 验证创建成功
  59. expect(result.success).toBe(true);
  60. return { username: userData.username, password: userData.password };
  61. } finally {
  62. // 清理:关闭独立 context
  63. await adminContext.close();
  64. }
  65. }
  66. test.describe('企业小程序登录功能', () => {
  67. test.describe('表单验证测试 (AC3)', () => {
  68. test('手机号为空时应该显示错误提示', async ({ enterpriseMiniPage }) => {
  69. // 导航到登录页面
  70. await enterpriseMiniPage.goto();
  71. // 不填写任何信息,直接点击登录按钮
  72. await enterpriseMiniPage.clickLoginButton();
  73. // 验证仍然在登录页面(未跳转)
  74. const currentUrl = enterpriseMiniPage.page.url();
  75. expect(currentUrl).toContain('/mini');
  76. // 验证登录页面容器仍然可见
  77. await expect(enterpriseMiniPage.loginPage).toBeVisible();
  78. });
  79. test('密码为空时应该显示错误提示', async ({ enterpriseMiniPage }) => {
  80. // 导航到登录页面
  81. await enterpriseMiniPage.goto();
  82. // 只填写手机号,不填写密码
  83. await enterpriseMiniPage.fillPhone('13800138000');
  84. // 尝试点击登录按钮
  85. await enterpriseMiniPage.clickLoginButton();
  86. // 验证仍然在登录页面(未跳转)
  87. const currentUrl = enterpriseMiniPage.page.url();
  88. expect(currentUrl).toContain('/mini');
  89. // 验证登录页面容器仍然可见
  90. await expect(enterpriseMiniPage.loginPage).toBeVisible();
  91. });
  92. test('表单验证错误提示应该清晰可见', async ({ enterpriseMiniPage }) => {
  93. // 导航到登录页面
  94. await enterpriseMiniPage.goto();
  95. // 不填写任何信息,直接点击登录
  96. await enterpriseMiniPage.clickLoginButton();
  97. // 验证仍在登录页面
  98. expect(enterpriseMiniPage.page.url()).toContain('/mini');
  99. // 验证登录页面容器仍然可见
  100. await expect(enterpriseMiniPage.loginPage).toBeVisible();
  101. });
  102. });
  103. test.describe('登录失败测试 (AC2)', () => {
  104. test('使用不存在的用户名登录失败', async ({ enterpriseMiniPage }) => {
  105. // 导航到登录页面
  106. await enterpriseMiniPage.goto();
  107. // 使用不存在的用户名尝试登录(使用有效的手机号格式)
  108. const fakeUsername = `199${Date.now().toString().slice(-8)}`; // 11位数字,符合手机号格式
  109. await enterpriseMiniPage.login(fakeUsername, 'password123');
  110. // 验证显示错误提示
  111. await enterpriseMiniPage.expectLoginError('用户名或密码错误');
  112. // 验证未存储 token
  113. const token = await enterpriseMiniPage.getToken();
  114. expect(token).toBeNull();
  115. });
  116. test('使用错误的密码登录失败', async ({ enterpriseMiniPage }) => {
  117. // 导航到登录页面
  118. await enterpriseMiniPage.goto();
  119. // 使用不存在的用户尝试登录(使用有效的手机号格式)
  120. await enterpriseMiniPage.login('19912345678', 'wrongpassword');
  121. // 验证显示错误提示
  122. await enterpriseMiniPage.expectLoginError('用户名或密码错误');
  123. // 验证未存储 token
  124. const token = await enterpriseMiniPage.getToken();
  125. expect(token).toBeNull();
  126. });
  127. test('错误提示内容应该正确', async ({ enterpriseMiniPage }) => {
  128. // 导航到登录页面
  129. await enterpriseMiniPage.goto();
  130. // 使用错误的凭据尝试登录(使用有效的手机号格式)
  131. await enterpriseMiniPage.login('19987654321', 'wrongpassword');
  132. // 验证错误提示包含"用户名或密码错误"或类似内容
  133. await enterpriseMiniPage.expectLoginError();
  134. });
  135. });
  136. test.describe.serial('基本登录成功测试 (AC1)', () => {
  137. test('应该成功登录企业小程序', async ({ enterpriseMiniPage, browser }) => {
  138. // 1. 创建测试用户(使用独立 context)
  139. const testUsername = `mini_test_${Date.now()}`;
  140. const testPassword = 'Test123!@#';
  141. const testPhone = `138${Date.now().toString().slice(-8)}`;
  142. await createTestUser(browser, {
  143. username: testUsername,
  144. password: testPassword,
  145. phone: testPhone,
  146. nickname: '小程序测试用户',
  147. userType: UserType.EMPLOYER,
  148. });
  149. // 2. 使用该用户登录小程序
  150. await enterpriseMiniPage.goto();
  151. await enterpriseMiniPage.login(testPhone, testPassword);
  152. // 3. 验证登录成功(URL 跳转到 dashboard 或显示用户信息)
  153. await enterpriseMiniPage.expectLoginSuccess();
  154. });
  155. test('登录成功后应该显示用户信息', async ({ enterpriseMiniPage, browser }) => {
  156. // 1. 创建测试用户
  157. const testUsername = `mini_info_${Date.now()}`;
  158. const testPassword = 'Test123!@#';
  159. const testPhone = `139${Date.now().toString().slice(-8)}`;
  160. await createTestUser(browser, {
  161. username: testUsername,
  162. password: testPassword,
  163. phone: testPhone,
  164. nickname: '小程序信息测试',
  165. userType: UserType.EMPLOYER,
  166. });
  167. // 2. 登录小程序
  168. await enterpriseMiniPage.goto();
  169. await enterpriseMiniPage.login(testPhone, testPassword);
  170. // 3. 验证登录成功
  171. await enterpriseMiniPage.expectLoginSuccess();
  172. // 4. 验证用户信息显示(检查 userInfo 元素或 dashboard 可见)
  173. const currentUrl = enterpriseMiniPage.page.url();
  174. expect(currentUrl).toMatch(/dashboard|pages/);
  175. });
  176. test('登录成功后 token 应该正确存储', async ({ enterpriseMiniPage, browser }) => {
  177. // 1. 创建测试用户
  178. const testUsername = `mini_token_${Date.now()}`;
  179. const testPassword = 'Test123!@#';
  180. const testPhone = `137${Date.now().toString().slice(-8)}`;
  181. await createTestUser(browser, {
  182. username: testUsername,
  183. password: testPassword,
  184. phone: testPhone,
  185. nickname: '小程序 Token 测试',
  186. userType: UserType.EMPLOYER,
  187. });
  188. // 2. 登录小程序
  189. await enterpriseMiniPage.goto();
  190. await enterpriseMiniPage.login(testPhone, testPassword);
  191. // 3. 验证登录成功
  192. await enterpriseMiniPage.expectLoginSuccess();
  193. // 4. 验证 token 被正确存储
  194. const token = await enterpriseMiniPage.getToken();
  195. expect(token).not.toBeNull();
  196. expect(token?.length).toBeGreaterThan(0);
  197. });
  198. });
  199. test.describe.serial('Token 持久性测试 (AC4)', () => {
  200. test('页面刷新后 token 仍然有效', async ({ enterpriseMiniPage, browser }) => {
  201. // 1. 创建测试用户
  202. const testUsername = `mini_refresh_${Date.now()}`;
  203. const testPassword = 'Test123!@#';
  204. const testPhone = `136${Date.now().toString().slice(-8)}`;
  205. await createTestUser(browser, {
  206. username: testUsername,
  207. password: testPassword,
  208. phone: testPhone,
  209. nickname: '小程序刷新测试',
  210. userType: UserType.EMPLOYER,
  211. });
  212. // 2. 登录小程序
  213. await enterpriseMiniPage.goto();
  214. await enterpriseMiniPage.login(testPhone, testPassword);
  215. await enterpriseMiniPage.expectLoginSuccess();
  216. // 3. 获取登录后的 token
  217. const tokenBeforeRefresh = await enterpriseMiniPage.getToken();
  218. expect(tokenBeforeRefresh).not.toBeNull();
  219. // 4. 刷新页面
  220. await enterpriseMiniPage.page.reload();
  221. // 5. 等待页面加载完成
  222. await enterpriseMiniPage.page.waitForLoadState('domcontentloaded');
  223. // 6. 验证 token 仍然存在
  224. const tokenAfterRefresh = await enterpriseMiniPage.getToken();
  225. expect(tokenAfterRefresh).toBe(tokenBeforeRefresh);
  226. });
  227. test('使用已存储 token 可以继续访问', async ({ enterpriseMiniPage, browser }) => {
  228. // 1. 创建测试用户
  229. const testUsername = `mini_persist_${Date.now()}`;
  230. const testPassword = 'Test123!@#';
  231. const testPhone = `135${Date.now().toString().slice(-8)}`;
  232. await createTestUser(browser, {
  233. username: testUsername,
  234. password: testPassword,
  235. phone: testPhone,
  236. nickname: '小程序持久化测试',
  237. userType: UserType.EMPLOYER,
  238. });
  239. // 2. 登录小程序
  240. await enterpriseMiniPage.goto();
  241. await enterpriseMiniPage.login(testPhone, testPassword);
  242. await enterpriseMiniPage.expectLoginSuccess();
  243. // 3. 获取 token
  244. const token = await enterpriseMiniPage.getToken();
  245. expect(token).not.toBeNull();
  246. // 4. 重新导航到登录页面(模拟关闭后重新打开)
  247. await enterpriseMiniPage.goto();
  248. // 5. 验证由于 token 存在,页面自动跳转到 dashboard
  249. await enterpriseMiniPage.page.waitForURL(
  250. url => url.pathname.includes('/dashboard') || url.pathname.includes('/pages'),
  251. { timeout: 10000 }
  252. ).catch(() => {
  253. // 如果没有自动跳转,检查当前 URL
  254. const currentUrl = enterpriseMiniPage.page.url();
  255. expect(currentUrl).toMatch(/dashboard|pages/);
  256. });
  257. });
  258. });
  259. test.describe.serial('退出登录测试 (AC5)', () => {
  260. test('应该成功退出登录', async ({ enterpriseMiniPage, browser }) => {
  261. // 1. 创建测试用户
  262. const testUsername = `mini_logout_${Date.now()}`;
  263. const testPassword = 'Test123!@#';
  264. const testPhone = `134${Date.now().toString().slice(-8)}`;
  265. await createTestUser(browser, {
  266. username: testUsername,
  267. password: testPassword,
  268. phone: testPhone,
  269. nickname: '小程序退出测试',
  270. userType: UserType.EMPLOYER,
  271. });
  272. // 2. 登录小程序
  273. await enterpriseMiniPage.goto();
  274. await enterpriseMiniPage.login(testPhone, testPassword);
  275. await enterpriseMiniPage.expectLoginSuccess();
  276. // 3. 退出登录
  277. await enterpriseMiniPage.logout();
  278. // 4. 验证返回到登录页面
  279. await enterpriseMiniPage.expectLoggedOut();
  280. // 5. 验证 URL 返回到登录页面
  281. const currentUrl = enterpriseMiniPage.page.url();
  282. expect(currentUrl).toContain('/mini');
  283. });
  284. test('退出后 token 应该被清除', async ({ enterpriseMiniPage, browser }) => {
  285. // 1. 创建测试用户
  286. const testUsername = `mini_token_clear_${Date.now()}`;
  287. const testPassword = 'Test123!@#';
  288. const testPhone = `133${Date.now().toString().slice(-8)}`;
  289. await createTestUser(browser, {
  290. username: testUsername,
  291. password: testPassword,
  292. phone: testPhone,
  293. nickname: '小程序 Token 清除测试',
  294. userType: UserType.EMPLOYER,
  295. });
  296. // 2. 登录小程序
  297. await enterpriseMiniPage.goto();
  298. await enterpriseMiniPage.login(testPhone, testPassword);
  299. await enterpriseMiniPage.expectLoginSuccess();
  300. // 3. 验证 token 存在
  301. const tokenBeforeLogout = await enterpriseMiniPage.getToken();
  302. expect(tokenBeforeLogout).not.toBeNull();
  303. // 4. 退出登录
  304. await enterpriseMiniPage.logout();
  305. await enterpriseMiniPage.expectLoggedOut();
  306. // 5. 验证 token 已被清除
  307. const tokenAfterLogout = await enterpriseMiniPage.getToken();
  308. expect(tokenAfterLogout).toBeNull();
  309. });
  310. test('退出后无法访问需要认证的页面', async ({ enterpriseMiniPage, browser }) => {
  311. // 1. 创建测试用户
  312. const testUsername = `mini_auth_${Date.now()}`;
  313. const testPassword = 'Test123!@#';
  314. const testPhone = `132${Date.now().toString().slice(-8)}`;
  315. await createTestUser(browser, {
  316. username: testUsername,
  317. password: testPassword,
  318. phone: testPhone,
  319. nickname: '小程序认证测试',
  320. userType: UserType.EMPLOYER,
  321. });
  322. // 2. 登录小程序
  323. await enterpriseMiniPage.goto();
  324. await enterpriseMiniPage.login(testPhone, testPassword);
  325. await enterpriseMiniPage.expectLoginSuccess();
  326. // 3. 退出登录
  327. await enterpriseMiniPage.logout();
  328. await enterpriseMiniPage.expectLoggedOut();
  329. // 4. 尝试直接访问需要认证的页面(dashboard)
  330. await enterpriseMiniPage.page.goto('/mini/dashboard');
  331. // 5. 验证被重定向回登录页面
  332. await enterpriseMiniPage.page.waitForLoadState('domcontentloaded');
  333. const currentUrl = enterpriseMiniPage.page.url();
  334. expect(currentUrl).toContain('/mini');
  335. // 6. 验证登录页面可见
  336. await expect(enterpriseMiniPage.loginPage).toBeVisible();
  337. });
  338. });
  339. });