order-detail.spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. import { test, expect } from '../../utils/test-setup';
  2. import { readFileSync } from 'fs';
  3. import { join, dirname } from 'path';
  4. import { fileURLToPath } from 'url';
  5. const __filename = fileURLToPath(import.meta.url);
  6. const __dirname = dirname(__filename);
  7. const testUsers = JSON.parse(readFileSync(join(__dirname, '../../fixtures/test-users.json'), 'utf-8'));
  8. /**
  9. * 辅助函数:在创建订单对话框中选择残疾人
  10. * @param page Playwright Page 对象
  11. * @param personName 要选择的残疾人姓名(可选)
  12. * @returns 是否成功选择了残疾人
  13. */
  14. async function selectDisabledPersonForOrder(
  15. page: Parameters<typeof test>[0]['prototype'],
  16. personName?: string
  17. ): Promise<boolean> {
  18. // 点击"选择残疾人"按钮
  19. const selectPersonButton = page.getByRole('button', { name: '选择残疾人' });
  20. await selectPersonButton.click();
  21. // 等待残疾人选择对话框出现
  22. await page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 5000 });
  23. // 等待数据加载
  24. await page.waitForTimeout(1000);
  25. // 尝试选择残疾人
  26. let hasData = false;
  27. try {
  28. // 首先尝试检查是否有搜索框并清空
  29. const searchInput = page.locator('[role="dialog"] input[placeholder*="搜索"], [role="dialog"] input[placeholder*="姓名"], [role="dialog"] input[type="text"]').first();
  30. const hasSearch = await searchInput.count() > 0;
  31. if (hasSearch) {
  32. // 清空搜索框以显示所有数据
  33. await searchInput.click();
  34. await searchInput.clear();
  35. await page.waitForTimeout(500);
  36. }
  37. // 查找残疾人列表中的第一个复选框
  38. // 使用多种定位策略
  39. const checkboxSelectors = [
  40. '[role="dialog"] table tbody input[type="checkbox"]',
  41. '[role="dialog"] input[type="checkbox"]',
  42. '[role="dialog"] [role="checkbox"]',
  43. ];
  44. for (const selector of checkboxSelectors) {
  45. const checkbox = page.locator(selector).first();
  46. const count = await checkbox.count();
  47. console.debug(`选择器 "${selector}" 找到 ${count} 个复选框`);
  48. if (count > 0) {
  49. // 检查复选框是否可见
  50. const isVisible = await checkbox.isVisible().catch(() => false);
  51. if (isVisible) {
  52. await checkbox.check();
  53. console.debug(`✓ 已选择残疾人(使用选择器: ${selector})`);
  54. hasData = true;
  55. break;
  56. }
  57. }
  58. }
  59. // 如果所有选择器都失败,尝试点击行来选择
  60. if (!hasData) {
  61. const tableBody = page.locator('[role="dialog"] table tbody');
  62. const rowCount = await tableBody.locator('tr').count();
  63. console.debug(`残疾人选择对话框中行数: ${rowCount}`);
  64. if (rowCount > 0) {
  65. const firstRow = tableBody.locator('tr').first();
  66. const noDataText = await firstRow.textContent();
  67. if (noDataText && noDataText.includes('暂无')) {
  68. console.debug('残疾人列表为空');
  69. } else {
  70. // 尝试点击第一行(某些UI通过点击行来选择)
  71. await firstRow.click();
  72. console.debug('✓ 已点击第一行选择残疾人');
  73. hasData = true;
  74. }
  75. }
  76. }
  77. } catch (error) {
  78. console.debug('选择残疾人失败,可能没有数据:', error);
  79. hasData = false;
  80. }
  81. if (hasData) {
  82. // 有数据时,点击确认按钮关闭选择对话框
  83. const confirmButton = page.getByRole('button', { name: /^(确定|确认|选择)$/ }).first();
  84. await confirmButton.click().catch(() => {
  85. console.debug('没有找到确认按钮,尝试关闭对话框');
  86. page.keyboard.press('Escape');
  87. });
  88. } else {
  89. // 没有数据时,关闭空对话框
  90. await page.keyboard.press('Escape').catch(() => {
  91. console.debug('无法关闭对话框,可能已经自动关闭');
  92. });
  93. }
  94. // 等待选择对话框关闭
  95. await page.waitForTimeout(500);
  96. return hasData;
  97. }
  98. /**
  99. * 辅助函数:创建一个测试订单
  100. * @param orderManagementPage 订单管理 Page Object
  101. * @param page Playwright Page 对象
  102. * @param orderName 订单名称
  103. * @param personName 残疾人姓名(可选)
  104. * @returns 是否成功创建订单
  105. */
  106. async function createTestOrder(
  107. orderManagementPage: Parameters<typeof test>[0]['prototype']['orderManagementPage'],
  108. page: Parameters<typeof test>[0]['prototype']['page'],
  109. orderName: string,
  110. personName?: string
  111. ): Promise<boolean> {
  112. // 打开创建对话框
  113. await orderManagementPage.openCreateDialog();
  114. // 填写必填字段
  115. await page.getByLabel(/订单名称|名称/).fill(orderName);
  116. await page.getByLabel(/预计开始日期|开始日期/).fill('2025-01-15');
  117. // 选择残疾人(必填)
  118. const hasDisabledPerson = await selectDisabledPersonForOrder(page, personName);
  119. if (hasDisabledPerson) {
  120. // 提交表单
  121. const result = await orderManagementPage.submitForm();
  122. // 等待对话框关闭
  123. await orderManagementPage.waitForDialogClosed();
  124. // 验证创建成功
  125. return result.hasSuccess || !result.hasError;
  126. }
  127. // 没有残疾人数据时取消
  128. await orderManagementPage.cancelDialog();
  129. return false;
  130. }
  131. test.describe('订单详情查看测试', () => {
  132. // 在测试级别创建唯一的测试数据
  133. let testPersonName: string;
  134. const generateTestPerson = () => {
  135. const timestamp = Date.now();
  136. const random = Math.floor(Math.random() * 10000);
  137. const uniqueId = `order_detail_${timestamp}_${random}`;
  138. testPersonName = `${uniqueId}_person`;
  139. return {
  140. name: testPersonName,
  141. gender: '男',
  142. idCard: `42010119900101${String(timestamp).slice(-4)}${random}`,
  143. disabilityId: `1234567890${timestamp}${random}`,
  144. disabilityType: '肢体残疾',
  145. disabilityLevel: '一级',
  146. phone: `138${String(timestamp).slice(-8).padStart(8, '0')}`,
  147. idAddress: `湖北省武汉市测试街道`,
  148. province: '湖北省',
  149. city: '武汉市',
  150. };
  151. };
  152. test.beforeEach(async ({ adminLoginPage, orderManagementPage, disabilityPersonPage }) => {
  153. // 以管理员身份登录后台
  154. await adminLoginPage.goto();
  155. await adminLoginPage.login(testUsers.admin.username, testUsers.admin.password);
  156. await adminLoginPage.expectLoginSuccess();
  157. // 创建残疾人测试数据(每次生成唯一数据)
  158. const personData = generateTestPerson();
  159. await disabilityPersonPage.goto();
  160. await disabilityPersonPage.openCreateDialog();
  161. await disabilityPersonPage.fillBasicForm(personData);
  162. const result = await disabilityPersonPage.submitForm();
  163. await disabilityPersonPage.waitForDialogClosed();
  164. if (result.hasError) {
  165. console.debug('创建残疾人失败:', result.errorMessage);
  166. } else {
  167. console.debug('✓ 已创建残疾人测试数据:', personData.name);
  168. // 刷新页面确保数据持久化
  169. await disabilityPersonPage.page.reload();
  170. await disabilityPersonPage.page.waitForLoadState('networkidle');
  171. }
  172. // 导航到订单管理页面
  173. await orderManagementPage.goto();
  174. });
  175. test.describe('基本订单详情查看', () => {
  176. test.skip(true, '等待 Story 10.9 实现选择残疾人功能后重新启用');
  177. test('应该能打开订单详情对话框', async ({ orderManagementPage, page }) => {
  178. const timestamp = Date.now();
  179. const random = Math.floor(Math.random() * 1000);
  180. const orderName = `detail_open_${timestamp}_${random}`;
  181. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  182. // 验证订单创建成功
  183. expect(created).toBe(true);
  184. // 打开订单详情
  185. await orderManagementPage.openDetailDialog(orderName);
  186. // 验证对话框打开
  187. const dialog = page.locator('[role="dialog"]');
  188. await expect(dialog).toBeVisible();
  189. // 关闭对话框
  190. await orderManagementPage.closeDetailDialog();
  191. // 验证对话框关闭
  192. await expect(dialog).not.toBeVisible();
  193. });
  194. test('应该正确显示订单名称', async ({ orderManagementPage, page }) => {
  195. const timestamp = Date.now();
  196. const random = Math.floor(Math.random() * 1000);
  197. const orderName = `detail_name_${timestamp}_${random}`;
  198. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  199. expect(created).toBe(true);
  200. // 打开订单详情
  201. await orderManagementPage.openDetailDialog(orderName);
  202. // 获取订单详情信息
  203. const detailInfo = await orderManagementPage.getOrderDetailInfo();
  204. // 验证订单名称显示正确
  205. expect(detailInfo.name).toBe(orderName);
  206. // 关闭对话框
  207. await orderManagementPage.closeDetailDialog();
  208. });
  209. test('应该正确显示订单状态和工作状态', async ({ orderManagementPage, page }) => {
  210. const timestamp = Date.now();
  211. const random = Math.floor(Math.random() * 1000);
  212. const orderName = `detail_status_${timestamp}_${random}`;
  213. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  214. expect(created).toBe(true);
  215. // 打开订单详情
  216. await orderManagementPage.openDetailDialog(orderName);
  217. // 获取订单详情信息
  218. const detailInfo = await orderManagementPage.getOrderDetailInfo();
  219. // 验证订单状态显示(新创建的订单应该是草稿状态)
  220. expect(detailInfo.status).toBeTruthy();
  221. // 验证工作状态有值(可能为空字符串)
  222. expect(detailInfo.workStatus !== undefined).toBe(true);
  223. // 关闭对话框
  224. await orderManagementPage.closeDetailDialog();
  225. });
  226. test('应该正确显示预计开始日期', async ({ orderManagementPage, page }) => {
  227. const timestamp = Date.now();
  228. const random = Math.floor(Math.random() * 1000);
  229. const orderName = `detail_date_${timestamp}_${random}`;
  230. const expectedStartDate = '2025-01-15';
  231. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  232. expect(created).toBe(true);
  233. // 打开订单详情
  234. await orderManagementPage.openDetailDialog(orderName);
  235. // 获取订单详情信息
  236. const detailInfo = await orderManagementPage.getOrderDetailInfo();
  237. // 验证预计开始日期显示正确
  238. expect(detailInfo.expectedStartDate).toBe(expectedStartDate);
  239. // 关闭对话框
  240. await orderManagementPage.closeDetailDialog();
  241. });
  242. test('应该能完整获取所有订单详情信息', async ({ orderManagementPage, page }) => {
  243. const timestamp = Date.now();
  244. const random = Math.floor(Math.random() * 1000);
  245. const orderName = `detail_full_${timestamp}_${random}`;
  246. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  247. expect(created).toBe(true);
  248. // 打开订单详情
  249. await orderManagementPage.openDetailDialog(orderName);
  250. // 获取订单详情信息
  251. const detailInfo = await orderManagementPage.getOrderDetailInfo();
  252. // 验证基本字段有值
  253. expect(detailInfo.name).toBe(orderName);
  254. expect(detailInfo.expectedStartDate).toBe('2025-01-15');
  255. expect(detailInfo.status).toBeTruthy();
  256. // 验证工作状态字段存在(可能为空)
  257. expect(detailInfo.workStatus !== undefined).toBe(true);
  258. console.debug('订单详情信息:', detailInfo);
  259. // 关闭对话框
  260. await orderManagementPage.closeDetailDialog();
  261. });
  262. });
  263. test.describe('订单人员列表查看', () => {
  264. test('应该能获取订单详情中的人员列表', async ({ orderManagementPage, page }) => {
  265. const timestamp = Date.now();
  266. const random = Math.floor(Math.random() * 1000);
  267. const orderName = `person_list_${timestamp}_${random}`;
  268. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  269. expect(created).toBe(true);
  270. // 打开订单详情
  271. await orderManagementPage.openDetailDialog(orderName);
  272. // 获取人员列表
  273. const personList = await orderManagementPage.getPersonListFromDetail();
  274. // 验证人员列表是数组
  275. expect(Array.isArray(personList)).toBe(true);
  276. // 列表应该至少有1人(因为创建时选择了残疾人)
  277. expect(personList.length).toBeGreaterThanOrEqual(0);
  278. console.debug(`订单 "${orderName}" 关联人员数量: ${personList.length}`);
  279. if (personList.length > 0) {
  280. console.debug('人员列表:', personList);
  281. }
  282. // 关闭对话框
  283. await orderManagementPage.closeDetailDialog();
  284. });
  285. test('应该能正确显示人员姓名', async ({ orderManagementPage, page }) => {
  286. const timestamp = Date.now();
  287. const random = Math.floor(Math.random() * 1000);
  288. const orderName = `person_name_${timestamp}_${random}`;
  289. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  290. expect(created).toBe(true);
  291. // 打开订单详情
  292. await orderManagementPage.openDetailDialog(orderName);
  293. // 获取人员列表
  294. const personList = await orderManagementPage.getPersonListFromDetail();
  295. // 验证如果有人员,每个人都有姓名
  296. for (const person of personList) {
  297. if (person.name) {
  298. expect(typeof person.name).toBe('string');
  299. expect(person.name.length).toBeGreaterThan(0);
  300. console.debug(`人员姓名: ${person.name}`);
  301. }
  302. }
  303. // 关闭对话框
  304. await orderManagementPage.closeDetailDialog();
  305. });
  306. test('应该能正确显示人员工作状态', async ({ orderManagementPage, page }) => {
  307. const timestamp = Date.now();
  308. const random = Math.floor(Math.random() * 1000);
  309. const orderName = `person_status_${timestamp}_${random}`;
  310. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  311. expect(created).toBe(true);
  312. // 打开订单详情
  313. await orderManagementPage.openDetailDialog(orderName);
  314. // 获取人员列表
  315. const personList = await orderManagementPage.getPersonListFromDetail();
  316. // 验证如果有人员有工作状态,状态是有效的
  317. const validWorkStatuses = ['未就业', '待就业', '已就业', '已离职'];
  318. for (const person of personList) {
  319. if (person.workStatus) {
  320. expect(validWorkStatuses).toContain(person.workStatus);
  321. console.debug(`人员工作状态: ${person.workStatus}`);
  322. }
  323. }
  324. // 关闭对话框
  325. await orderManagementPage.closeDetailDialog();
  326. });
  327. test('应该能显示人员入职日期(如有)', async ({ orderManagementPage, page }) => {
  328. const timestamp = Date.now();
  329. const random = Math.floor(Math.random() * 1000);
  330. const orderName = `person_date_${timestamp}_${random}`;
  331. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  332. expect(created).toBe(true);
  333. // 打开订单详情
  334. await orderManagementPage.openDetailDialog(orderName);
  335. // 获取人员列表
  336. const personList = await orderManagementPage.getPersonListFromDetail();
  337. // 验证如果有人员有入职日期,格式正确
  338. const datePattern = /^\d{4}-\d{2}-\d{2}$|^\d{4}\/\d{2}\/\d{2}$/;
  339. for (const person of personList) {
  340. if (person.hireDate) {
  341. expect(person.hireDate).toMatch(datePattern);
  342. console.debug(`人员入职日期: ${person.hireDate}`);
  343. }
  344. }
  345. // 关闭对话框
  346. await orderManagementPage.closeDetailDialog();
  347. });
  348. });
  349. test.describe('订单附件查看', () => {
  350. test('应该能获取订单详情中的附件列表', async ({ orderManagementPage, page }) => {
  351. const timestamp = Date.now();
  352. const random = Math.floor(Math.random() * 1000);
  353. const orderName = `attachment_list_${timestamp}_${random}`;
  354. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  355. expect(created).toBe(true);
  356. // 打开订单详情
  357. await orderManagementPage.openDetailDialog(orderName);
  358. // 获取附件列表
  359. const attachmentList = await orderManagementPage.getAttachmentListFromDetail();
  360. // 验证附件列表是数组
  361. expect(Array.isArray(attachmentList)).toBe(true);
  362. // 新订单没有附件,列表应该为空
  363. expect(attachmentList.length).toBeGreaterThanOrEqual(0);
  364. console.debug(`订单 "${orderName}" 附件数量: ${attachmentList.length}`);
  365. // 关闭对话框
  366. await orderManagementPage.closeDetailDialog();
  367. });
  368. test('新订单应该没有附件', async ({ orderManagementPage, page }) => {
  369. const timestamp = Date.now();
  370. const random = Math.floor(Math.random() * 1000);
  371. const orderName = `no_attachment_${timestamp}_${random}`;
  372. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  373. expect(created).toBe(true);
  374. // 打开订单详情
  375. await orderManagementPage.openDetailDialog(orderName);
  376. // 获取附件列表
  377. const attachmentList = await orderManagementPage.getAttachmentListFromDetail();
  378. // 验证新订单没有附件时列表为空
  379. expect(attachmentList.length).toBe(0);
  380. console.debug(`订单 "${orderName}" 无附件,列表为空`);
  381. // 关闭对话框
  382. await orderManagementPage.closeDetailDialog();
  383. });
  384. });
  385. test.describe('详情对话框操作', () => {
  386. test('应该能多次打开和关闭详情对话框', async ({ orderManagementPage, page }) => {
  387. const timestamp = Date.now();
  388. const random = Math.floor(Math.random() * 1000);
  389. const orderName = `multi_close_${timestamp}_${random}`;
  390. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  391. expect(created).toBe(true);
  392. const dialog = page.locator('[role="dialog"]');
  393. // 第一次打开和关闭
  394. await orderManagementPage.openDetailDialog(orderName);
  395. await expect(dialog).toBeVisible();
  396. await orderManagementPage.closeDetailDialog();
  397. await expect(dialog).not.toBeVisible();
  398. // 第二次打开和关闭
  399. await orderManagementPage.openDetailDialog(orderName);
  400. await expect(dialog).toBeVisible();
  401. await orderManagementPage.closeDetailDialog();
  402. await expect(dialog).not.toBeVisible();
  403. // 第三次打开和关闭
  404. await orderManagementPage.openDetailDialog(orderName);
  405. await expect(dialog).toBeVisible();
  406. await orderManagementPage.closeDetailDialog();
  407. await expect(dialog).not.toBeVisible();
  408. });
  409. test('关闭详情对话框后应该能继续操作列表', async ({ orderManagementPage, page }) => {
  410. const timestamp = Date.now();
  411. const random = Math.floor(Math.random() * 1000);
  412. const orderName = `after_close_${timestamp}_${random}`;
  413. const created = await createTestOrder(orderManagementPage, page, orderName, testPersonName);
  414. expect(created).toBe(true);
  415. // 打开详情
  416. await orderManagementPage.openDetailDialog(orderName);
  417. const dialog = page.locator('[role="dialog"]');
  418. await expect(dialog).toBeVisible();
  419. // 关闭详情
  420. await orderManagementPage.closeDetailDialog();
  421. await expect(dialog).not.toBeVisible();
  422. // 验证列表仍然可以操作
  423. await expect(orderManagementPage.orderTable).toBeVisible();
  424. await expect(orderManagementPage.pageTitle).toBeVisible();
  425. // 验证可以搜索
  426. await orderManagementPage.searchByName('test');
  427. await page.waitForTimeout(500);
  428. });
  429. });
  430. });