Files

171 lines
6.6 KiB
JavaScript
Raw Permalink Normal View History

2026-06-07 10:35:21 +08:00
#!/usr/bin/env node
"use strict";
const assert = require("node:assert");
const fs = require("node:fs");
const path = require("node:path");
const { chromium } = require("playwright");
const ROOT = process.env.MNOTE_REPO_ROOT || "/mnt/Data1T/mnote";
const { BASE_URL, UI_TIMEOUT_MS } = require(path.join(ROOT, "scripts", "tree-shell-smoke-helpers"));
const OUTPUT_DIR = path.join(ROOT, "tmp", "task531-lightrag-dashboard-ui-smoke");
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");
const CHROME = process.env.MNOTE_PAGE_AI_CHROME || "/usr/bin/google-chrome";
async function fetchDashboardStatus(context) {
const response = await context.request.get(`${BASE_URL}/api/knowledge-rag/status`, {
headers: {
"x-mnote-actor-id": "mnote-e2e",
"x-mnote-actor-type": "user",
"x-mnote-workspace-id": "local-ws:mnote-e2e:my-space",
},
timeout: UI_TIMEOUT_MS,
});
const text = await response.text();
assert(response.ok(), `/api/knowledge-rag/status 失败: ${response.status()} ${text.slice(0, 500)}`);
const payload = JSON.parse(text);
assert(payload.dashboardUrl, `status 缺少 dashboardUrl: ${text.slice(0, 500)}`);
assert(payload.health?.healthy !== false, `LightRAG health 非 healthy: ${text.slice(0, 500)}`);
return payload;
}
async function bodyText(page) {
return await page.evaluate(() => document.body.innerText || "");
}
async function screenshot(page, name) {
const screenshotPath = path.join(OUTPUT_DIR, name);
await page.screenshot({ path: screenshotPath, fullPage: true });
return screenshotPath;
}
async function main() {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
const browser = await chromium.launch({
headless: process.env.MNOTE_DASHBOARD_SMOKE_HEADED !== "1",
executablePath: fs.existsSync(CHROME) ? CHROME : undefined,
});
const context = await browser.newContext({ viewport: { width: 1440, height: 960 } });
const page = await context.newPage();
const pageErrors = [];
const consoleErrors = [];
page.on("pageerror", (error) => pageErrors.push(String(error?.message || error)));
page.on("console", (message) => {
if (message.type() === "error") consoleErrors.push(message.text());
});
try {
const statusPayload = await fetchDashboardStatus(context);
const dashboardUrl = statusPayload.dashboardUrl;
const documentSummaries = Array.isArray(statusPayload.documents?.documents)
? statusPayload.documents.documents
: [];
const rawStatusGroups = statusPayload.documents?.rawStatusGroups || {};
const processedCount = Number(rawStatusGroups.processed || 0);
const webuiUrl = dashboardUrl.endsWith("/webui/") ? dashboardUrl : `${dashboardUrl.replace(/\/+$/, "")}/webui/`;
await page.goto(webuiUrl, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
await page.waitForLoadState("networkidle", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
await page.waitForFunction(
() => (document.body.innerText || "").includes("Document Management"),
null,
{ timeout: UI_TIMEOUT_MS },
);
const documentsText = await bodyText(page);
assert(documentsText.includes("Uploaded Documents"), "Documents 页缺少 Uploaded Documents");
if (processedCount > 0) {
assert(
documentsText.includes(`Completed (${processedCount})`),
`Documents 页未显示当前 completed 计数 ${processedCount}: ${documentsText.slice(0, 1000)}`,
);
}
const visibleDocument = documentSummaries.find((doc) => {
const id = String(doc.id || "");
const filePath = String(doc.filePath || "");
return (id && documentsText.includes(id)) || (filePath && documentsText.includes(filePath));
});
assert(
visibleDocument,
`Documents 页没有显示 status API 返回的任一当前文档: ${JSON.stringify(documentSummaries.slice(0, 5), null, 2)}\n${documentsText.slice(0, 1000)}`,
);
const documentsScreenshot = await screenshot(page, "documents.png");
await page.getByRole("tab", { name: /Knowledge Graph/i }).click({ timeout: UI_TIMEOUT_MS });
await page.waitForFunction(
() => {
const text = document.body.innerText || "";
return text.includes("Connected") && text.includes("D:");
},
null,
{ timeout: UI_TIMEOUT_MS },
);
const graphText = await bodyText(page);
assert(graphText.includes("Connected"), `Graph 页缺少连接状态: ${graphText.slice(0, 1000)}`);
const graphScreenshot = await screenshot(page, "graph.png");
await page.getByRole("tab", { name: /Retrieval/i }).click({ timeout: UI_TIMEOUT_MS });
await page.waitForFunction(
() => (document.body.innerText || "").includes("Start a retrieval by typing your query below"),
null,
{ timeout: UI_TIMEOUT_MS },
);
const retrievalText = await bodyText(page);
assert(retrievalText.includes("Query Mode"), "Retrieval 页缺少 Query Mode 参数");
assert(retrievalText.includes("KG Top K"), "Retrieval 页缺少 KG Top K 参数");
assert(retrievalText.includes("Connected"), "Retrieval 页缺少连接状态");
const retrievalScreenshot = await screenshot(page, "retrieval.png");
const result = {
ok: true,
baseUrl: BASE_URL,
dashboardUrl,
webuiUrl: page.url(),
documents: {
processedCount,
completedVisible: processedCount > 0 ? documentsText.includes(`Completed (${processedCount})`) : null,
visibleDocumentId: visibleDocument.id || null,
visibleDocumentFilePath: visibleDocument.filePath || null,
screenshot: documentsScreenshot,
},
graph: {
connectedVisible: graphText.includes("Connected"),
screenshot: graphScreenshot,
},
retrieval: {
queryModeVisible: retrievalText.includes("Query Mode"),
connectedVisible: retrievalText.includes("Connected"),
screenshot: retrievalScreenshot,
},
pageErrors,
consoleErrors,
};
fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8");
console.log(JSON.stringify(result, null, 2));
} catch (error) {
await page.screenshot({ path: path.join(OUTPUT_DIR, "failure.png"), fullPage: true }).catch(() => undefined);
fs.writeFileSync(
path.join(OUTPUT_DIR, "failure.json"),
`${JSON.stringify(
{
ok: false,
error: error instanceof Error ? error.stack || error.message : String(error),
pageErrors,
consoleErrors,
},
null,
2,
)}\n`,
"utf8",
);
throw error;
} finally {
await browser.close().catch(() => undefined);
}
}
main().catch((error) => {
console.error(error.stack || error.message || String(error));
process.exit(1);
});