import { test, expect } from '../fixtures/auth'; import type { Page, Request, Response } from '@playwright/test'; /** * A2 UI 回归 第一批:Location / Department * 验证: * 1. 删除按钮点击 → 确认弹窗 → 后端 409 → 红色 toast 显示业务文案 * 2. 列表行不消失(未误删) * 3. 不出现 success toast(无假成功) */ const TARGETS = [ { name: 'Department D-PROD', url: '/#/aidop/s0/warehouse/department', apiList: /\/api\/s0\/warehouse\/departments\?/, apiDelete: /\/api\/s0\/warehouse\/departments\/\d+/, code: 'D-PROD', expectMsg: /引用该部门|EmployeeMaster/, }, { name: 'Location 1001', url: '/#/aidop/s0/warehouse/location', apiList: /\/api\/s0\/warehouse\/locations\?/, apiDelete: /\/api\/s0\/warehouse\/locations\/\d+/, code: '1001', expectMsg: /引用该库位|ItemMaster|LocationShelf/, }, ] as const; async function findRowByCode(page: Page, code: string) { const row = page.locator('tr', { hasText: code }).first(); await expect(row).toBeVisible({ timeout: 15_000 }); return row; } for (const t of TARGETS) { test(`${t.name} 删除被引用 → 409 + 红色 toast + 行未消失`, async ({ authedPage }) => { // 探测可用路由:尝试硬编码 url,若 404 则尝试常见替代名 const candidates = [ t.url, t.url.replace(/s$/, '-list'), t.url.replace(/s$/, ''), t.url.replace('/aidop/s0', '/s0'), ]; let landed = false; for (const u of candidates) { await authedPage.goto(u, { waitUntil: 'domcontentloaded' }); await authedPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => {}); const has404 = await authedPage.locator('text=/404|没有找到|未找到页面/').first().isVisible().catch(() => false); const hasTable = await authedPage.locator('table tbody tr').first().isVisible({ timeout: 8_000 }).catch(() => false); if (!has404 && hasTable) { landed = true; break; } } test.skip(!landed, `未能定位 ${t.name} 列表页`); const row = await findRowByCode(authedPage, t.code); const rowCount = await authedPage.locator('table tbody tr').count(); // 监听删除请求 let deleteResp: Response | null = null; authedPage.on('response', (r) => { if (t.apiDelete.test(r.url()) && r.request().method() === 'DELETE') deleteResp = r; }); await row.getByRole('button', { name: /删除/ }).click(); const confirm = authedPage.locator('.el-message-box').getByRole('button', { name: /确\s*定/ }); await expect(confirm).toBeVisible({ timeout: 5_000 }); await confirm.click(); // 等到删除接口回包 await expect.poll(() => deleteResp?.status() ?? 0, { timeout: 10_000 }).toBe(409); const body = await deleteResp!.json().catch(() => null); test.info().annotations.push({ type: 'api', description: `DELETE ${deleteResp!.url()} → ${deleteResp!.status()} body=${JSON.stringify(body).slice(0, 200)}`, }); expect(body?.code, '业务码应为 S01006').toBe('S01006'); expect(String(body?.message ?? '')).toMatch(t.expectMsg); // 关键 UI 证据:错误 toast 出现,业务文案可见 const errToast = authedPage.locator('.el-message--error, .el-message.is-error').first(); await expect(errToast, '应出现红色 ElMessage error toast').toBeVisible({ timeout: 5_000 }); const toastText = (await errToast.innerText()).replace(/\s+/g, ''); test.info().annotations.push({ type: 'toast', description: toastText }); expect(toastText).toMatch(t.expectMsg); // 不应出现 success toast(无假成功) const okToast = authedPage.locator('.el-message--success').first(); expect(await okToast.isVisible().catch(() => false), '不应出现 success toast').toBeFalsy(); // 列表行未消失 await authedPage.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => {}); const stillThere = authedPage.locator('tr', { hasText: t.code }).first(); await expect(stillThere, '行不应被删除').toBeVisible({ timeout: 3_000 }); const rowCount2 = await authedPage.locator('table tbody tr').count(); expect(rowCount2, '行数不应减少').toBe(rowCount); await authedPage.screenshot({ path: `test-results/a2-ui-${t.code.replace(/[^\w-]/g, '_')}.png`, fullPage: true }); }); }