a2-ui-batch1.spec.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { test, expect } from '../fixtures/auth';
  2. import type { Page, Request, Response } from '@playwright/test';
  3. /**
  4. * A2 UI 回归 第一批:Location / Department
  5. * 验证:
  6. * 1. 删除按钮点击 → 确认弹窗 → 后端 409 → 红色 toast 显示业务文案
  7. * 2. 列表行不消失(未误删)
  8. * 3. 不出现 success toast(无假成功)
  9. */
  10. const TARGETS = [
  11. {
  12. name: 'Department D-PROD',
  13. url: '/#/aidop/s0/warehouse/department',
  14. apiList: /\/api\/s0\/warehouse\/departments\?/,
  15. apiDelete: /\/api\/s0\/warehouse\/departments\/\d+/,
  16. code: 'D-PROD',
  17. expectMsg: /引用该部门|EmployeeMaster/,
  18. },
  19. {
  20. name: 'Location 1001',
  21. url: '/#/aidop/s0/warehouse/location',
  22. apiList: /\/api\/s0\/warehouse\/locations\?/,
  23. apiDelete: /\/api\/s0\/warehouse\/locations\/\d+/,
  24. code: '1001',
  25. expectMsg: /引用该库位|ItemMaster|LocationShelf/,
  26. },
  27. ] as const;
  28. async function findRowByCode(page: Page, code: string) {
  29. const row = page.locator('tr', { hasText: code }).first();
  30. await expect(row).toBeVisible({ timeout: 15_000 });
  31. return row;
  32. }
  33. for (const t of TARGETS) {
  34. test(`${t.name} 删除被引用 → 409 + 红色 toast + 行未消失`, async ({ authedPage }) => {
  35. // 探测可用路由:尝试硬编码 url,若 404 则尝试常见替代名
  36. const candidates = [
  37. t.url,
  38. t.url.replace(/s$/, '-list'),
  39. t.url.replace(/s$/, ''),
  40. t.url.replace('/aidop/s0', '/s0'),
  41. ];
  42. let landed = false;
  43. for (const u of candidates) {
  44. await authedPage.goto(u, { waitUntil: 'domcontentloaded' });
  45. await authedPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => {});
  46. const has404 = await authedPage.locator('text=/404|没有找到|未找到页面/').first().isVisible().catch(() => false);
  47. const hasTable = await authedPage.locator('table tbody tr').first().isVisible({ timeout: 8_000 }).catch(() => false);
  48. if (!has404 && hasTable) { landed = true; break; }
  49. }
  50. test.skip(!landed, `未能定位 ${t.name} 列表页`);
  51. const row = await findRowByCode(authedPage, t.code);
  52. const rowCount = await authedPage.locator('table tbody tr').count();
  53. // 监听删除请求
  54. let deleteResp: Response | null = null;
  55. authedPage.on('response', (r) => {
  56. if (t.apiDelete.test(r.url()) && r.request().method() === 'DELETE') deleteResp = r;
  57. });
  58. await row.getByRole('button', { name: /删除/ }).click();
  59. const confirm = authedPage.locator('.el-message-box').getByRole('button', { name: /确\s*定/ });
  60. await expect(confirm).toBeVisible({ timeout: 5_000 });
  61. await confirm.click();
  62. // 等到删除接口回包
  63. await expect.poll(() => deleteResp?.status() ?? 0, { timeout: 10_000 }).toBe(409);
  64. const body = await deleteResp!.json().catch(() => null);
  65. test.info().annotations.push({
  66. type: 'api',
  67. description: `DELETE ${deleteResp!.url()} → ${deleteResp!.status()} body=${JSON.stringify(body).slice(0, 200)}`,
  68. });
  69. expect(body?.code, '业务码应为 S01006').toBe('S01006');
  70. expect(String(body?.message ?? '')).toMatch(t.expectMsg);
  71. // 关键 UI 证据:错误 toast 出现,业务文案可见
  72. const errToast = authedPage.locator('.el-message--error, .el-message.is-error').first();
  73. await expect(errToast, '应出现红色 ElMessage error toast').toBeVisible({ timeout: 5_000 });
  74. const toastText = (await errToast.innerText()).replace(/\s+/g, '');
  75. test.info().annotations.push({ type: 'toast', description: toastText });
  76. expect(toastText).toMatch(t.expectMsg);
  77. // 不应出现 success toast(无假成功)
  78. const okToast = authedPage.locator('.el-message--success').first();
  79. expect(await okToast.isVisible().catch(() => false), '不应出现 success toast').toBeFalsy();
  80. // 列表行未消失
  81. await authedPage.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => {});
  82. const stillThere = authedPage.locator('tr', { hasText: t.code }).first();
  83. await expect(stillThere, '行不应被删除').toBeVisible({ timeout: 3_000 });
  84. const rowCount2 = await authedPage.locator('table tbody tr').count();
  85. expect(rowCount2, '行数不应减少').toBe(rowCount);
  86. await authedPage.screenshot({ path: `test-results/a2-ui-${t.code.replace(/[^\w-]/g, '_')}.png`, fullPage: true });
  87. });
  88. }