b02-legacy-scenes.spec.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { test, expect } from '../fixtures/auth';
  2. import type { Page } from '@playwright/test';
  3. /**
  4. * B-02 遗留现场单页断言
  5. * 目标:验证 aidopdev 测试库中两条半提交现场依旧保留,详情页可加载,
  6. * 且核心不变量(ActiveFlowInstanceId、状态非已关闭)仍然成立。
  7. *
  8. * 不变量来源:memory/project_s8_b02_status.md(2026-04-20 快照)
  9. * - id=4:阶段 2 Approve 半提交 → ActiveFlowInstanceId=796353842065477
  10. * - id=29:阶段 3 Reject 半提交 → ActiveFlowInstanceId=796458502344773(EX-20260414-0003-RJ)
  11. */
  12. const SCENES = [
  13. { id: 4, expectedFlow: '796353842065477', label: 'B-02 阶段 2 Approve 半提交' },
  14. { id: 29, expectedFlow: '796458502344773', expectedCode: 'EX-20260414-0003-RJ', label: 'B-02 阶段 3 Reject 半提交' },
  15. ] as const;
  16. async function getToken(page: Page): Promise<string | null> {
  17. return page.evaluate(() => {
  18. for (let i = 0; i < localStorage.length; i++) {
  19. const k = localStorage.key(i)!;
  20. if (/access-token$/i.test(k) && !/x-access-token$/i.test(k)) {
  21. const raw = localStorage.getItem(k)!;
  22. try {
  23. const v = JSON.parse(raw);
  24. return typeof v === 'string' ? v : v?.value ?? raw;
  25. } catch {
  26. return raw;
  27. }
  28. }
  29. }
  30. return null;
  31. });
  32. }
  33. async function fetchDetail(page: Page, id: number) {
  34. const token = await getToken(page);
  35. expect(token, '未取到 access-token').toBeTruthy();
  36. const baseURL = new URL(page.url()).origin;
  37. const resp = await page.request.get(
  38. `${baseURL}/api/aidop/s8/exceptions/${id}?tenantId=1&factoryId=1`,
  39. { headers: { Authorization: `Bearer ${token}` } },
  40. );
  41. return { status: resp.status(), body: resp.ok() ? await resp.json() : null };
  42. }
  43. test.describe('S8-B02-LEGACY 遗留现场单页断言', () => {
  44. for (const scene of SCENES) {
  45. test(`id=${scene.id} ${scene.label}`, async ({ authedPage }) => {
  46. // 1) API 层:记录依然存在 + 关键不变量
  47. await authedPage.goto('/#/aidop/s8/exceptions', { waitUntil: 'domcontentloaded' });
  48. await authedPage.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => {});
  49. const { status, body } = await fetchDetail(authedPage, scene.id);
  50. test.info().annotations.push({
  51. type: 'api',
  52. description: `GET /exceptions/${scene.id} → HTTP ${status}`,
  53. });
  54. expect(status, `详情接口非 200,记录可能已被清理`).toBe(200);
  55. const data = body?.data ?? body;
  56. expect(data, '详情响应缺少数据').toBeTruthy();
  57. const activeFlow =
  58. data.activeFlowInstanceId ?? data.ActiveFlowInstanceId ?? data.flowInstanceId;
  59. const code = data.code ?? data.exceptionCode;
  60. const status8 = data.status ?? data.statusName;
  61. test.info().annotations.push({
  62. type: 'snapshot',
  63. description: JSON.stringify({ code, status: status8, activeFlow }),
  64. });
  65. expect(String(activeFlow ?? ''), '半提交现场必须仍持有 ActiveFlowInstanceId').toBe(
  66. scene.expectedFlow,
  67. );
  68. expect(String(status8 ?? ''), '半提交现场不应处于已关闭状态').not.toMatch(/已关闭|已取消/);
  69. if ('expectedCode' in scene && scene.expectedCode) {
  70. expect(String(code ?? '')).toBe(scene.expectedCode);
  71. }
  72. // 2) UI 层:详情页可正常打开
  73. await authedPage.goto(`/#/aidop/s8/exceptions/${scene.id}`, {
  74. waitUntil: 'domcontentloaded',
  75. });
  76. await authedPage.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => {});
  77. const errorBoundary = authedPage.locator('text=/页面加载失败|Error|未找到/').first();
  78. const errorVisible = await errorBoundary.isVisible().catch(() => false);
  79. expect(errorVisible, '详情页出现错误提示').toBeFalsy();
  80. const titleArea = authedPage.locator(
  81. 'h1, h2, h3, .el-page-header, .el-card__header, .header',
  82. );
  83. await expect(titleArea.first(), '详情页应渲染标题区').toBeVisible({ timeout: 10_000 });
  84. await authedPage.screenshot({
  85. path: `test-results/s8-b02-id-${scene.id}.png`,
  86. fullPage: true,
  87. });
  88. });
  89. }
  90. });