214 lines
7.1 KiB
JavaScript
214 lines
7.1 KiB
JavaScript
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);
|
|
});
|