b02-legacy-full-recheck.spec.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { test, expect } from '../fixtures/auth';
  2. import type { Page } from '@playwright/test';
  3. /**
  4. * B-02 遗留现场扩展复测(阶段 4 / N-2 前置):
  5. * 在已验证"详情 API + 详情页可加载"之上,补齐 列表 / 时间线 / 状态一致性 三项。
  6. */
  7. const SCENES = [
  8. { id: 4, flow: '796353842065477', code: 'EX-20260414-0002', label: '阶段2 Approve 半提交' },
  9. { id: 29, flow: '796458502344773', code: 'EX-20260414-0003-RJ', label: '阶段3 Reject 半提交' },
  10. ] as const;
  11. async function token(page: Page) {
  12. return page.evaluate(() => {
  13. for (let i = 0; i < localStorage.length; i++) {
  14. const k = localStorage.key(i)!;
  15. if (/access-token$/i.test(k) && !/x-access-token$/i.test(k)) {
  16. const raw = localStorage.getItem(k)!;
  17. try {
  18. const v = JSON.parse(raw);
  19. return typeof v === 'string' ? v : v?.value ?? raw;
  20. } catch {
  21. return raw;
  22. }
  23. }
  24. }
  25. return null;
  26. });
  27. }
  28. async function api(page: Page, url: string) {
  29. const t = await token(page);
  30. const base = new URL(page.url()).origin;
  31. const resp = await page.request.get(`${base}${url}`, {
  32. headers: { Authorization: `Bearer ${t!}` },
  33. });
  34. return { status: resp.status(), json: resp.ok() ? ((await resp.json()) as any) : null };
  35. }
  36. test.describe('B-02 遗留现场扩展复测', () => {
  37. for (const s of SCENES) {
  38. test(`id=${s.id} 详情/时间线/列表三侧一致`, async ({ authedPage }) => {
  39. await authedPage.goto('/#/aidop/s8/exceptions', { waitUntil: 'domcontentloaded' });
  40. await authedPage.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => {});
  41. // 1) 详情 API
  42. const detail = await api(
  43. authedPage,
  44. `/api/aidop/s8/exceptions/${s.id}?tenantId=1&factoryId=1`,
  45. );
  46. expect(detail.status).toBe(200);
  47. const d = detail.json?.data ?? detail.json;
  48. test.info().annotations.push({
  49. type: 'detail',
  50. description: JSON.stringify({
  51. code: d.code ?? d.exceptionCode,
  52. status: d.status,
  53. activeFlow: d.activeFlowInstanceId ?? d.ActiveFlowInstanceId,
  54. }),
  55. });
  56. expect(String(d.activeFlowInstanceId ?? d.ActiveFlowInstanceId ?? '')).toBe(s.flow);
  57. expect(String(d.status ?? '')).not.toMatch(/CLOSED|CANCELED|已关闭|已取消/);
  58. // 2) 时间线 API
  59. const tl = await api(authedPage, `/api/aidop/s8/exceptions/${s.id}/timeline`);
  60. expect(tl.status, '时间线接口应返回 200').toBe(200);
  61. const tlData = tl.json?.data ?? tl.json;
  62. const tlArr = Array.isArray(tlData) ? tlData : (tlData?.items ?? tlData?.list ?? []);
  63. test.info().annotations.push({
  64. type: 'timeline',
  65. description: `length=${tlArr.length}; first=${JSON.stringify(tlArr[0] ?? null).slice(0, 200)}`,
  66. });
  67. expect(tlArr.length, '时间线至少应有一条记录').toBeGreaterThan(0);
  68. // 3) 列表接口:按 code 过滤定位行
  69. const listResp = await api(
  70. authedPage,
  71. `/api/aidop/s8/exceptions?page=1&pageSize=50&tenantId=1&factoryId=1`,
  72. );
  73. expect(listResp.status).toBe(200);
  74. const listData = listResp.json?.data ?? listResp.json;
  75. const rows: any[] = listData?.list ?? listData?.items ?? listData?.rows ?? [];
  76. const hit = rows.find(
  77. (r) => String(r.id) === String(s.id) || r.code === s.code || r.exceptionCode === s.code,
  78. );
  79. test.info().annotations.push({
  80. type: 'list',
  81. description: hit
  82. ? `found code=${hit.code ?? hit.exceptionCode} status=${hit.status}`
  83. : `total=${rows.length}, not in first page`,
  84. });
  85. // 不强断言必须在第 1 页(数据量变化可能让它翻页),仅要求接口可达
  86. if (hit) {
  87. expect(String(hit.status ?? '')).toBe(String(d.status ?? ''));
  88. }
  89. // 4) 详情页 URL 可达
  90. await authedPage.goto(`/#/aidop/s8/exceptions/${s.id}`, {
  91. waitUntil: 'domcontentloaded',
  92. });
  93. await authedPage.waitForLoadState('networkidle', { timeout: 12_000 }).catch(() => {});
  94. const errBanner = authedPage.locator('text=/页面加载失败|未找到|Error:/').first();
  95. expect(await errBanner.isVisible().catch(() => false)).toBeFalsy();
  96. });
  97. }
  98. });