import { test, expect, type Page, type ConsoleMessage } from "@playwright/test"; import * as fs from "node:fs"; import * as path from "node:path"; import { execSync } from "node:child_process"; type MailItem = { key: string; id: string; status: string; mail_type: string; container_no: string | null; shipments: number; table_rows: number; url: string; }; type DetailResult = { key: string; id: string; url: string; open_ms: number; ok: boolean; antd_warnings: string[]; page_errors: string[]; visible_checks: string[]; note?: string; }; function btn(page: Page, label: string) { const re = new RegExp(`^${label.split("").map(escapeRe).join("\\s*")}$`); return page.getByRole("button", { name: re }); } function escapeRe(s: string) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } async function login(page: Page) { await page.goto("/login"); const user = page.getByLabel("用户名"); await user.click(); await user.fill(""); await user.fill("admin"); const pwd = page.getByLabel("密码"); await pwd.click(); await pwd.fill(""); await pwd.fill("admin123"); await btn(page, "登录").click(); await expect(page).toHaveURL(/\/mails/, { timeout: 20_000 }); } function loadSampleItems(): MailItem[] { execSync("pnpm exec tsx scripts/ingest-sample-mails.ts", { cwd: process.cwd(), encoding: "utf8", env: process.env, stdio: ["ignore", "pipe", "pipe"], }); const report = path.join(process.cwd(), "data", "logs", "sample-mails-ingest.json"); const json = JSON.parse(fs.readFileSync(report, "utf8")) as { items: MailItem[] }; return json.items; } test.describe("sample mails M1-M4 detail console", () => { test("open four details + catch antd warnings", async ({ page }) => { test.setTimeout(240_000); const items = loadSampleItems(); expect(items.length).toBe(4); await login(page); const results: DetailResult[] = []; for (const item of items) { const antd_warnings: string[] = []; const page_errors: string[] = []; const onConsole = (msg: ConsoleMessage) => { const text = msg.text(); if ( msg.type() === "error" || msg.type() === "warning" || text.includes("[antd:") ) { if (text.includes("[antd:")) antd_warnings.push(text.slice(0, 300)); else if (msg.type() === "error") page_errors.push(text.slice(0, 300)); } }; const onPageError = (err: Error) => { page_errors.push(err.message.slice(0, 300)); }; page.on("console", onConsole); page.on("pageerror", onPageError); const t0 = Date.now(); let ok = true; const visible_checks: string[] = []; let note: string | undefined; try { await page.goto(`/mails/${item.id}`, { waitUntil: "domcontentloaded" }); await expect(page.getByRole("button", { name: /回列表/ })).toBeVisible({ timeout: 30_000, }); visible_checks.push("回列表"); await expect(page.locator(".mail-biz-units")).toBeVisible({ timeout: 15_000, }); visible_checks.push("指令区"); // 若有标准化表,确认 Table 渲染且无 rowKey/span 告警 const tableTitle = page.getByText(/工单标准化列表|货件标准表/); if (await tableTitle.first().isVisible().catch(() => false)) { visible_checks.push("标准表"); await expect(page.locator(".ant-table-tbody").first()).toBeVisible(); } // 等一拍收集异步 warning await page.waitForTimeout(800); } catch (e) { ok = false; note = e instanceof Error ? e.message.slice(0, 240) : String(e); } finally { page.off("console", onConsole); page.off("pageerror", onPageError); } // 过滤已知无关噪声,只关心 antd Descriptions/Table const critical = antd_warnings.filter( (w) => w.includes("[antd: Descriptions]") || w.includes("[antd: Table]") || w.includes("rowKey") || w.includes("span"), ); results.push({ key: item.key, id: item.id, url: item.url, open_ms: Date.now() - t0, ok: ok && critical.length === 0 && page_errors.length === 0, antd_warnings: critical, page_errors, visible_checks, note: note || `type=${item.mail_type} status=${item.status} cn=${item.container_no} rows=${item.table_rows}`, }); } const outDir = path.join(process.cwd(), "data", "logs"); fs.mkdirSync(outDir, { recursive: true }); const summary = { at: new Date().toISOString(), fail: results.filter((r) => !r.ok).length, results, }; fs.writeFileSync( path.join(outDir, "sample-mails-detail-smoke.json"), JSON.stringify(summary, null, 2), "utf8", ); const md = [ "# Sample Mails Detail Smoke (M1–M4)", "", `| mail | id | open_ms | ok | antd | checks | note |`, `|---|---|---:|:---:|---|---|---|`, ...results.map( (r) => `| M${r.key} | ${r.id} | ${r.open_ms} | ${r.ok ? "Y" : "N"} | ${r.antd_warnings.length} | ${r.visible_checks.join("/") || "-"} | ${(r.note ?? "").replace(/\|/g, "/")} |`, ), "", ...results .filter((r) => r.antd_warnings.length || r.page_errors.length) .flatMap((r) => [ `## M${r.key} issues`, ...r.antd_warnings.map((w) => `- antd: ${w}`), ...r.page_errors.map((w) => `- error: ${w}`), "", ]), ].join("\n"); fs.writeFileSync(path.join(outDir, "sample-mails-detail-smoke.md"), md, "utf8"); expect(results.every((r) => r.ok)).toBeTruthy(); }); });