import { chromium, request } from "playwright"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const stamp = new Date().toISOString().replace(/[:.]/g, "-"); const runId = `kb-failed-ui-${Date.now()}`; const webBase = process.env.KB_FAILED_UI_WEB_URL || "http://127.0.0.1:3005"; const apiBase = process.env.KB_FAILED_UI_API_URL || "http://127.0.0.1:8000/api/v1"; const healthUrl = process.env.KB_FAILED_UI_HEALTH_URL || "http://127.0.0.1:8000/health"; const adminPassword = process.env.AI_CONTRACT_ADMIN_PASSWORD || "123456"; const artifactDir = process.env.KB_FAILED_UI_ARTIFACT_DIR || path.resolve(__dirname, "..", "..", "..", "project-docs", "qa-artifacts", "kb-failed-document-ui", stamp); fs.mkdirSync(artifactDir, { recursive: true }); const result = { run_id: runId, web_base: webBase, api_base: apiBase, health_url: healthUrl, artifact_dir: artifactDir, checks: [], }; function apiUrl(route) { return `${apiBase.replace(/\/$/, "")}${route}`; } function screenshotPath(name) { return path.join(artifactDir, `${name}.png`); } function assert(condition, message, details = undefined) { if (!condition) { const error = new Error(message); error.details = details; throw error; } } async function apiRequest(api, method, route, options = {}) { const headers = { ...(options.authToken ? { Authorization: `Bearer ${options.authToken}` } : {}), ...(options.headers || {}), }; const response = await api[method](apiUrl(route), { ...options, headers }); const text = await response.text(); let body = null; if (text) { try { body = JSON.parse(text); } catch { body = text; } } return { response, body, text }; } function expectOk(payload, label) { assert(payload.response.status() >= 200 && payload.response.status() < 300, `${label} HTTP ${payload.response.status()}`, { body: payload.body, }); if (payload.body && typeof payload.body === "object" && "code" in payload.body) { assert(payload.body.code === 0, `${label} API code ${payload.body.code}`, { body: payload.body }); } } async function login(api) { const payload = await apiRequest(api, "post", "/auth/login", { data: { account: "admin@example.com", password: adminPassword }, }); expectOk(payload, "admin login"); return payload.body.data; } async function main() { const api = await request.newContext(); const browser = await chromium.launch({ headless: true }); try { const frontend = await api.get(`${webBase}/login`); const health = await api.get(healthUrl); assert(frontend.status() === 200, `frontend /login status ${frontend.status()}`); assert(health.status() === 200, `backend /health status ${health.status()}`); const adminSession = await login(api); const kbName = `${runId} KB`; const create = await apiRequest(api, "post", "/knowledge-bases", { authToken: adminSession.access_token, multipart: { name: kbName, description: "KB failed document ui regression" }, }); expectOk(create, "create knowledge base"); const kbId = create.body.data.id; const goodDoc = await apiRequest(api, "post", `/knowledge-bases/${kbId}/documents`, { authToken: adminSession.access_token, multipart: { file: { name: `${runId}-good.txt`, mimeType: "text/plain", buffer: Buffer.from("合同审查依据:付款、保密、违约责任。", "utf8"), }, }, }); expectOk(goodDoc, "upload good kb document"); const failedDoc = await apiRequest(api, "post", `/knowledge-bases/${kbId}/documents`, { authToken: adminSession.access_token, multipart: { file: { name: `${runId}-failed.txt`, mimeType: "text/plain", buffer: Buffer.from(""), }, }, }); expectOk(failedDoc, "upload failed kb document"); assert(failedDoc.body.data.status === "failed", "failed kb document should stay failed", failedDoc.body.data); const context = await browser.newContext({ viewport: { width: 1440, height: 1100 }, }); await context.addInitScript( ({ token, user }) => { window.localStorage.setItem("access_token", token); window.localStorage.setItem("auth-store", JSON.stringify({ state: { accessToken: token, currentUser: user }, version: 0 })); }, { token: adminSession.access_token, user: adminSession.user }, ); const page = await context.newPage(); await page.goto(`${webBase}/knowledge-bases`, { waitUntil: "domcontentloaded" }); await page.waitForTimeout(2200); await page.getByRole("button", { name: kbName }).click(); await page.waitForTimeout(1200); await page.screenshot({ path: screenshotPath("knowledge-base-failed-ui"), fullPage: true }); const body = await page.locator("body").innerText(); assert(body.includes("异常文档恢复"), "failed document recovery panel should be visible", body.slice(0, 1800)); assert(body.includes("重试处理"), "retry button should be visible for failed document", body.slice(-1600)); const disabledLinks = await page.locator("a.disabled-link[aria-disabled='true']").count(); assert(disabledLinks >= 2, "failed document chat actions should be disabled", { disabledLinks }); const failedFileNameVisible = body.includes(`${runId}-failed.txt`); assert(failedFileNameVisible, "failed document filename should be visible in recovery panel", body.slice(-1800)); const summary = { ...result, total: 1, passed: 1, failed: 0, checks: [ { id: "KB-FAILED-UI-001", title: "knowledge base failed document UI", status: "PASS", details: { kb_id: kbId, disabled_links: disabledLinks, screenshot: "knowledge-base-failed-ui.png", }, }, ], }; fs.writeFileSync(path.join(artifactDir, "kb-failed-document-ui-summary.json"), JSON.stringify(summary, null, 2), "utf8"); fs.writeFileSync( path.join(artifactDir, "kb-failed-document-ui-summary.md"), [ "# KB Failed Document UI E2E", "", `- Run ID: \`${runId}\``, `- Web: \`${webBase}\``, `- API: \`${apiBase}\``, "- Result: PASS", "", "| Check | Status |", "|---|---|", "| KB failed document UI | PASS |", ].join("\n"), "utf8", ); console.log(JSON.stringify(summary, null, 2)); } finally { await browser.close().catch(() => undefined); await api.dispose().catch(() => undefined); } } main().catch((error) => { const summary = { ...result, total: 1, passed: 0, failed: 1, checks: [ { id: "KB-FAILED-UI-001", title: "knowledge base failed document UI", status: "FAIL", error: error.message, details: error.details || {}, }, ], }; fs.writeFileSync(path.join(artifactDir, "kb-failed-document-ui-summary.json"), JSON.stringify(summary, null, 2), "utf8"); console.error(JSON.stringify(summary, null, 2)); process.exit(1); });