user-create-talent.spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. import { TIMEOUTS } from '../../utils/timeouts';
  2. import { test, expect } from '../../utils/test-setup';
  3. import { UserType } from '@d8d/shared-types';
  4. /**
  5. * 人才用户创建 E2E 测试
  6. *
  7. * 测试后台创建人才用户功能的正确性
  8. * 验证创建基本人才用户、完整信息人才用户、残疾人关联验证、表单验证等功能
  9. *
  10. * @see {@link ../pages/admin/user-management.page.ts} UserManagementPage
  11. * @see {@link ../pages/admin/disability-person.page.ts} DisabilityPersonManagementPage
  12. */
  13. test.describe('人才用户创建功能', () => {
  14. // 使用固定的残疾人数据名称
  15. const FIXED_TEST_PERSON_NAME = 'E2E测试_人才用户专用残疾人';
  16. // 初始化测试:确保残疾人数据存在
  17. // 此测试会先于其他测试运行,创建所需的残疾人数据
  18. test('初始化:创建测试用残疾人数据', async ({ adminLoginPage, disabilityPersonPage }) => {
  19. await adminLoginPage.goto();
  20. await adminLoginPage.login('admin', 'admin123');
  21. await adminLoginPage.expectLoginSuccess();
  22. // 检查残疾人数据是否存在
  23. await disabilityPersonPage.goto();
  24. await disabilityPersonPage.searchByName(FIXED_TEST_PERSON_NAME);
  25. const personExists = await disabilityPersonPage.personExists(FIXED_TEST_PERSON_NAME);
  26. if (!personExists) {
  27. // 创建固定的测试残疾人数据
  28. const timestamp = Date.now();
  29. await disabilityPersonPage.goto();
  30. await disabilityPersonPage.openCreateDialog();
  31. await disabilityPersonPage.fillBasicForm({
  32. name: FIXED_TEST_PERSON_NAME,
  33. gender: '男',
  34. idCard: generateTestIdCard(timestamp),
  35. disabilityId: `E2E_TALENT_${timestamp}`,
  36. disabilityType: '视力残疾',
  37. disabilityLevel: '一级',
  38. phone: '13800138000',
  39. idAddress: `E2E测试地址_人才用户专用`,
  40. province: '广东省',
  41. city: '广州市',
  42. });
  43. await disabilityPersonPage.submitForm();
  44. await disabilityPersonPage.waitForDialogClosed();
  45. const personCreated = await disabilityPersonPage.waitForPersonExists(FIXED_TEST_PERSON_NAME, { timeout: TIMEOUTS.TABLE_LOAD });
  46. expect(personCreated).toBe(true);
  47. }
  48. });
  49. test.beforeEach(async ({ adminLoginPage, userManagementPage }) => {
  50. // 以管理员身份登录后台
  51. await adminLoginPage.goto();
  52. await adminLoginPage.login('admin', 'admin123');
  53. await adminLoginPage.expectLoginSuccess();
  54. // 导航到用户管理页面
  55. await userManagementPage.goto();
  56. });
  57. test.describe('基本创建流程测试', () => {
  58. test('应该成功创建基本人才用户', async ({ userManagementPage }) => {
  59. // 生成唯一用户名
  60. const timestamp = Date.now();
  61. const username = `test_talent_${timestamp}`;
  62. // 创建人才用户(填写必填字段 + 选择残疾人)
  63. const result = await userManagementPage.createUser({
  64. username,
  65. password: 'password123',
  66. nickname: '测试人才用户',
  67. userType: UserType.TALENT,
  68. }, undefined, FIXED_TEST_PERSON_NAME);
  69. // 验证 API 响应成功
  70. expect(result.responses).toBeDefined();
  71. expect(result.responses?.length).toBeGreaterThan(0);
  72. const createResponse = result.responses?.find(r => r.url.includes('/api/v1/users'));
  73. expect(createResponse?.ok).toBe(true);
  74. // 验证创建成功提示(Toast 检测不稳定,作为可选验证)
  75. // 主要验证依赖 API 响应和列表显示
  76. if (result.hasSuccess && result.successMessage) {
  77. expect(result.successMessage).toContain('成功');
  78. }
  79. // 验证用户出现在列表中
  80. await expect(async () => {
  81. const exists = await userManagementPage.userExists(username);
  82. expect(exists).toBe(true);
  83. }).toPass({ timeout: TIMEOUTS.DIALOG });
  84. // 验证用户类型徽章显示为人才用户
  85. const userRow = userManagementPage.getUserByUsername(username);
  86. const userTypeBadge = userRow.getByTestId('user-type-badge');
  87. await expect(userTypeBadge).toContainText('人才用户');
  88. // 清理测试数据(用户)
  89. const deleteResult = await userManagementPage.deleteUser(username);
  90. expect(deleteResult).toBe(true);
  91. // 验证用户已被删除
  92. const existsAfterDelete = await userManagementPage.userExists(username);
  93. expect(existsAfterDelete).toBe(false);
  94. });
  95. test('创建后人才用户应该出现在列表中', async ({ userManagementPage }) => {
  96. const timestamp = Date.now();
  97. const username = `talent_list_${timestamp}`;
  98. // 创建人才用户
  99. await userManagementPage.createUser({
  100. username,
  101. password: 'password123',
  102. nickname: '列表测试用户',
  103. userType: UserType.TALENT,
  104. }, undefined, FIXED_TEST_PERSON_NAME);
  105. // 验证用户出现在列表中
  106. const exists = await userManagementPage.userExists(username);
  107. expect(exists).toBe(true);
  108. // 清理
  109. await userManagementPage.deleteUser(username);
  110. });
  111. });
  112. test.describe('完整表单字段测试', () => {
  113. test('应该成功创建完整信息人才用户', async ({ userManagementPage }) => {
  114. const timestamp = Date.now();
  115. const username = `talent_full_${timestamp}`;
  116. // 创建人才用户(填写所有字段)
  117. const result = await userManagementPage.createUser({
  118. username,
  119. password: 'password123',
  120. nickname: '完整信息用户',
  121. email: `full_${timestamp}@example.com`,
  122. phone: '13800138001',
  123. name: '张三',
  124. userType: UserType.TALENT,
  125. }, undefined, FIXED_TEST_PERSON_NAME);
  126. // 验证 API 响应成功
  127. const createResponse = result.responses?.find(r => r.url.includes('/api/v1/users'));
  128. expect(createResponse?.ok).toBe(true);
  129. // 验证用户出现在列表中
  130. await expect(async () => {
  131. const exists = await userManagementPage.userExists(username);
  132. expect(exists).toBe(true);
  133. }).toPass({ timeout: TIMEOUTS.DIALOG });
  134. // 清理
  135. await userManagementPage.deleteUser(username);
  136. });
  137. test('应该保存所有填写的字段数据', async ({ userManagementPage }) => {
  138. const timestamp = Date.now();
  139. const username = `talent_fields_${timestamp}`;
  140. // 创建人才用户(填写所有字段)
  141. const result = await userManagementPage.createUser({
  142. username,
  143. password: 'password123',
  144. nickname: `字段测试_${timestamp}`,
  145. email: `fields_${timestamp}@test.com`,
  146. phone: '13900139000',
  147. name: '李四',
  148. userType: UserType.TALENT,
  149. }, undefined, FIXED_TEST_PERSON_NAME);
  150. // 验证创建成功(优先检查 API 响应)
  151. const createResponse = result.responses?.find(r => r.url.includes('/api/v1/users'));
  152. expect(createResponse?.ok).toBe(true);
  153. // 验证创建成功提示(Toast 检测不稳定,作为可选验证)
  154. // 主要验证依赖 API 响应和列表显示
  155. if (result.hasSuccess && result.successMessage) {
  156. expect(result.successMessage).toContain('成功');
  157. }
  158. // 验证用户出现在列表中
  159. const exists = await userManagementPage.userExists(username);
  160. expect(exists).toBe(true);
  161. // 清理
  162. await userManagementPage.deleteUser(username);
  163. });
  164. });
  165. test.describe('残疾人关联验证测试', () => {
  166. // TODO: 后端当前未强制要求人才用户必须关联残疾人
  167. // 当前行为:后端允许创建没有 personId 的 TALENT 用户
  168. // 期望行为:后端应返回 400 错误,要求人才用户必须关联残疾人
  169. test.skip('人才用户必须关联残疾人 [后端验证未实现]', async ({ userManagementPage }) => {
  170. const timestamp = Date.now();
  171. const username = `talent_no_person_${timestamp}`;
  172. // 打开创建对话框
  173. await userManagementPage.openCreateDialog();
  174. // 填写用户名和密码
  175. await userManagementPage.usernameInput.fill(username);
  176. await userManagementPage.passwordInput.fill('password123');
  177. // 选择用户类型为人才用户(但不选择残疾人)
  178. await userManagementPage.page.waitForSelector('[data-testid="用户类型-trigger"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  179. await userManagementPage.userTypeSelector.click();
  180. await userManagementPage.page.waitForSelector('[role="option"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  181. await userManagementPage.page.getByRole('option', { name: '人才用户' }).click();
  182. // 尝试提交表单
  183. const submitResult = await userManagementPage.submitForm();
  184. // 验证 API 响应包含错误(后端验证)
  185. const createResponse = submitResult.responses?.find(r => r.url.includes('/api/v1/users'));
  186. // 后端应该返回 400 错误或 Toast 错误消息
  187. expect(createResponse?.ok || submitResult.hasError).toBe(false);
  188. // 验证用户没有被创建(列表中不存在)
  189. const exists = await userManagementPage.userExists(username);
  190. expect(exists).toBe(false);
  191. });
  192. test.skip('不选择残疾人时应该显示错误提示 [后端验证未实现]', async ({ userManagementPage }) => {
  193. const timestamp = Date.now();
  194. const username = `talent_error_${timestamp}`;
  195. // 打开创建对话框
  196. await userManagementPage.openCreateDialog();
  197. // 填写必填字段
  198. await userManagementPage.usernameInput.fill(username);
  199. await userManagementPage.passwordInput.fill('password123');
  200. await userManagementPage.nicknameInput.fill('错误测试用户');
  201. // 选择用户类型为人才用户(但不选择残疾人)
  202. await userManagementPage.page.waitForSelector('[data-testid="用户类型-trigger"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  203. await userManagementPage.userTypeSelector.click();
  204. await userManagementPage.page.waitForSelector('[role="option"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  205. await userManagementPage.page.getByRole('option', { name: '人才用户' }).click();
  206. // 尝试提交表单
  207. const submitResult = await userManagementPage.submitForm();
  208. // 验证后端返回错误(残疾人必填验证)
  209. const createResponse = submitResult.responses?.find(r => r.url.includes('/api/v1/users'));
  210. expect(createResponse?.ok || submitResult.hasError).toBe(false);
  211. // 验证用户没有被创建
  212. const exists = await userManagementPage.userExists(username);
  213. expect(exists).toBe(false);
  214. });
  215. });
  216. test.describe('表单验证测试', () => {
  217. test('用户名为空时应显示验证错误', async ({ userManagementPage }) => {
  218. // 打开创建对话框
  219. await userManagementPage.openCreateDialog();
  220. // 不填写用户名,直接填写密码
  221. await userManagementPage.passwordInput.fill('password123');
  222. // 尝试提交表单
  223. await userManagementPage.submitForm();
  224. // 验证对话框仍然打开(表单验证阻止了提交)
  225. const dialog = userManagementPage.page.locator('[role="dialog"]');
  226. await expect(dialog).toBeVisible();
  227. // 关闭对话框
  228. await userManagementPage.cancelDialog();
  229. });
  230. test('密码为空时应显示验证错误', async ({ userManagementPage }) => {
  231. // 打开创建对话框
  232. await userManagementPage.openCreateDialog();
  233. // 填写用户名,但不填写密码
  234. const timestamp = Date.now();
  235. await userManagementPage.usernameInput.fill(`user_no_pwd_${timestamp}`);
  236. // 尝试提交表单
  237. await userManagementPage.submitForm();
  238. // 验证对话框仍然打开(表单验证阻止了提交)
  239. const dialog = userManagementPage.page.locator('[role="dialog"]');
  240. await expect(dialog).toBeVisible();
  241. // 关闭对话框
  242. await userManagementPage.cancelDialog();
  243. });
  244. test('邮箱格式不正确时应显示验证错误', async ({ userManagementPage }) => {
  245. const timestamp = Date.now();
  246. const username = `user_bad_email_${timestamp}`;
  247. // 打开创建对话框
  248. await userManagementPage.openCreateDialog();
  249. // 先填写无效格式的邮箱(在填写其他可选字段之前)
  250. await userManagementPage.usernameInput.fill(username);
  251. await userManagementPage.passwordInput.fill('password123');
  252. await userManagementPage.emailInput.fill('invalid-email-format');
  253. // 不填写昵称等其他字段,只测试邮箱格式验证
  254. // 尝试提交表单
  255. await userManagementPage.submitForm();
  256. // 验证对话框仍然打开(表单验证阻止了提交)
  257. const dialog = userManagementPage.page.locator('[role="dialog"]');
  258. await expect(dialog).toBeVisible();
  259. // 关闭对话框
  260. await userManagementPage.cancelDialog();
  261. });
  262. });
  263. test.describe('数据唯一性测试', () => {
  264. test('不同测试应该使用不同的用户名', async ({ userManagementPage }) => {
  265. // 生成两个不同的用户名
  266. const timestamp = Date.now();
  267. const username1 = `unique_talent_A_${timestamp}`;
  268. const username2 = `unique_talent_B_${timestamp}`;
  269. // 创建第一个人才用户
  270. await userManagementPage.createUser({
  271. username: username1,
  272. password: 'password123',
  273. nickname: '唯一性测试A',
  274. userType: UserType.TALENT,
  275. }, undefined, FIXED_TEST_PERSON_NAME);
  276. expect(await userManagementPage.userExists(username1)).toBe(true);
  277. // 创建第二个人才用户
  278. await userManagementPage.createUser({
  279. username: username2,
  280. password: 'password123',
  281. nickname: '唯一性测试B',
  282. userType: UserType.TALENT,
  283. }, undefined, FIXED_TEST_PERSON_NAME);
  284. expect(await userManagementPage.userExists(username2)).toBe(true);
  285. // 清理两个用户
  286. await userManagementPage.deleteUser(username1);
  287. await userManagementPage.deleteUser(username2);
  288. // 验证清理成功
  289. expect(await userManagementPage.userExists(username1)).toBe(false);
  290. expect(await userManagementPage.userExists(username2)).toBe(false);
  291. });
  292. test('使用时间戳确保用户名唯一', async ({ userManagementPage }) => {
  293. // 使用时间戳生成唯一用户名
  294. const timestamp = Date.now();
  295. const username = `timestamp_user_${timestamp}`;
  296. // 创建人才用户
  297. await userManagementPage.createUser({
  298. username,
  299. password: 'password123',
  300. nickname: '时间戳测试用户',
  301. userType: UserType.TALENT,
  302. }, undefined, FIXED_TEST_PERSON_NAME);
  303. // 验证用户创建成功
  304. expect(await userManagementPage.userExists(username)).toBe(true);
  305. // 清理
  306. await userManagementPage.deleteUser(username);
  307. });
  308. });
  309. test.describe('测试后清理验证', () => {
  310. test('应该能成功删除测试创建的人才用户', async ({ userManagementPage }) => {
  311. const timestamp = Date.now();
  312. const username = `cleanup_talent_${timestamp}`;
  313. // 创建人才用户
  314. const result = await userManagementPage.createUser({
  315. username,
  316. password: 'password123',
  317. nickname: '清理测试用户',
  318. email: `cleanup_${timestamp}@test.com`,
  319. phone: '13800003333',
  320. userType: UserType.TALENT,
  321. }, undefined, FIXED_TEST_PERSON_NAME);
  322. // 验证用户存在
  323. const createResponse = result.responses?.find(r => r.url.includes('/api/v1/users'));
  324. expect(createResponse?.ok).toBe(true);
  325. expect(await userManagementPage.userExists(username)).toBe(true);
  326. // 删除用户
  327. const deleteResult = await userManagementPage.deleteUser(username);
  328. expect(deleteResult).toBe(true);
  329. // 验证用户已被删除
  330. await expect(async () => {
  331. const exists = await userManagementPage.userExists(username);
  332. expect(exists).toBe(false);
  333. }).toPass({ timeout: TIMEOUTS.DIALOG });
  334. });
  335. });
  336. test.describe('对话框元素验证', () => {
  337. test('应该显示创建用户对话框的所有字段', async ({ userManagementPage }) => {
  338. // 打开创建对话框
  339. await userManagementPage.openCreateDialog();
  340. // 验证对话框存在
  341. const dialog = userManagementPage.page.locator('[role="dialog"]');
  342. await expect(dialog).toBeVisible();
  343. // 验证用户类型选择器存在
  344. await expect(userManagementPage.userTypeSelector).toBeVisible();
  345. // 验证必填字段输入框存在
  346. await expect(userManagementPage.usernameInput).toBeVisible();
  347. await expect(userManagementPage.passwordInput).toBeVisible();
  348. await expect(userManagementPage.nicknameInput).toBeVisible();
  349. // 验证可选字段输入框存在
  350. await expect(userManagementPage.emailInput).toBeVisible();
  351. await expect(userManagementPage.phoneInput).toBeVisible();
  352. await expect(userManagementPage.nameInput).toBeVisible();
  353. // 残疾人选择器是条件渲染的,只有选择了 TALENT 类型才会显示
  354. // 先选择人才用户类型
  355. await userManagementPage.userTypeSelector.click();
  356. await userManagementPage.page.waitForSelector('[role="option"]', { state: 'visible', timeout: TIMEOUTS.DIALOG });
  357. await userManagementPage.page.getByRole('option', { name: '人才用户' }).click();
  358. // 现在验证残疾人选择器存在
  359. await expect(userManagementPage.disabledPersonSelector).toBeVisible();
  360. // 验证按钮存在
  361. await expect(userManagementPage.createSubmitButton).toBeVisible();
  362. await expect(userManagementPage.cancelButton).toBeVisible();
  363. // 关闭对话框
  364. await userManagementPage.cancelDialog();
  365. });
  366. });
  367. test.describe('取消和关闭操作测试', () => {
  368. test('应该能取消创建人才用户操作', async ({ userManagementPage }) => {
  369. const timestamp = Date.now();
  370. const username = `cancel_talent_${timestamp}`;
  371. // 打开创建对话框并填写表单
  372. await userManagementPage.openCreateDialog();
  373. await userManagementPage.usernameInput.fill(username);
  374. await userManagementPage.passwordInput.fill('password123');
  375. // 点击取消按钮
  376. await userManagementPage.cancelDialog();
  377. // 验证对话框关闭
  378. const dialog = userManagementPage.page.locator('[role="dialog"]');
  379. await expect(dialog).not.toBeVisible();
  380. // 验证用户没有被创建
  381. const exists = await userManagementPage.userExists(username);
  382. expect(exists).toBe(false);
  383. });
  384. test('应该能通过关闭对话框取消创建', async ({ userManagementPage }) => {
  385. const timestamp = Date.now();
  386. const username = `close_talent_${timestamp}`;
  387. // 打开创建对话框并填写表单
  388. await userManagementPage.openCreateDialog();
  389. await userManagementPage.usernameInput.fill(username);
  390. await userManagementPage.passwordInput.fill('password123');
  391. // 按 ESC 键关闭对话框
  392. await userManagementPage.page.keyboard.press('Escape');
  393. // 等待对话框关闭
  394. await userManagementPage.waitForDialogClosed();
  395. // 验证用户没有被创建
  396. const exists = await userManagementPage.userExists(username);
  397. expect(exists).toBe(false);
  398. });
  399. });
  400. });
  401. /**
  402. * 生成测试用身份证号
  403. * @param timestamp 时间戳
  404. * @returns 18位身份证号
  405. */
  406. function generateTestIdCard(timestamp: number): string {
  407. // 生成一个有效的18位身份证号(简化版本)
  408. const prefix = '110101'; // 北京市东城区
  409. const birthDate = new Date(timestamp);
  410. const year = birthDate.getFullYear();
  411. const month = String(birthDate.getMonth() + 1).padStart(2, '0');
  412. const day = String(birthDate.getDate()).padStart(2, '0');
  413. const sequence = String(timestamp % 1000).padStart(3, '0');
  414. const base = prefix + year + month + day + sequence;
  415. // 标准中国身份证校验码算法
  416. const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
  417. const checkChars = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
  418. let sum = 0;
  419. for (let i = 0; i < 17; i++) {
  420. sum += parseInt(base[i]) * weights[i];
  421. }
  422. const checkCode = checkChars[sum % 11];
  423. return base + checkCode;
  424. }