mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-21 20:06:06 +00:00
187 lines
6.2 KiB
JavaScript
187 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { join } from "node:path";
|
|
import { env } from "node:process";
|
|
import {
|
|
apiJson,
|
|
createBrowser,
|
|
ensureAuthenticatedBrowser,
|
|
ensureEvidence,
|
|
evidencePaths,
|
|
exitCode,
|
|
loadEnvFiles,
|
|
localIsoWithOffset,
|
|
safeScreenshot,
|
|
scanBrowserDiagnostics,
|
|
writeResult,
|
|
} from "./lib/langbot-e2e.mjs";
|
|
|
|
await loadEnvFiles();
|
|
const caseId = env.LBS_CASE_ID || "workspace-plugin-pages-smoke";
|
|
const paths = evidencePaths(caseId);
|
|
await ensureEvidence(paths);
|
|
const startedAt = new Date();
|
|
const frontendUrl = env.LANGBOT_FRONTEND_URL || "";
|
|
const backendUrl = env.LANGBOT_BACKEND_URL || "";
|
|
|
|
const targets = [
|
|
{
|
|
id: "control-plane",
|
|
author: "langbot",
|
|
plugin: "agent-control-plane",
|
|
page: "control-plane",
|
|
signal: "Agent Control Plane",
|
|
endpoint: "/health",
|
|
method: "GET",
|
|
},
|
|
{
|
|
id: "memory-console",
|
|
author: "langbot-team",
|
|
plugin: "LongTermMemory",
|
|
page: "memory_console",
|
|
signal: "Memory Console",
|
|
endpoint: "/summary",
|
|
method: "GET",
|
|
},
|
|
{
|
|
id: "langrag-observability",
|
|
author: "langbot-team",
|
|
plugin: "LangRAG",
|
|
page: "observability",
|
|
signal: "LangRAG Observability",
|
|
endpoint: "/snapshot",
|
|
method: "GET",
|
|
},
|
|
{
|
|
id: "skill-authoring",
|
|
author: "huanghuoguoguo",
|
|
plugin: "skill-authoring",
|
|
page: "authoring",
|
|
signal: "Skill Authoring",
|
|
endpoint: "/health",
|
|
method: "GET",
|
|
},
|
|
];
|
|
|
|
const result = {
|
|
source: "automation",
|
|
case_id: caseId,
|
|
run_id: paths.runId,
|
|
started_at: startedAt.toISOString(),
|
|
started_at_local: localIsoWithOffset(startedAt),
|
|
finished_at: "",
|
|
finished_at_local: "",
|
|
status: "fail",
|
|
reason: "",
|
|
frontend_url: frontendUrl,
|
|
backend_url: backendUrl,
|
|
pages: [],
|
|
browser_diagnostics: null,
|
|
evidence: {
|
|
console_log: paths.consoleLog,
|
|
network_log: paths.networkLog,
|
|
screenshot: paths.screenshot,
|
|
automation_result_json: paths.automationResultJson,
|
|
result_json: paths.resultJson,
|
|
},
|
|
evidence_collected: ["ui", "screenshot", "console", "network", "api_diagnostic"],
|
|
};
|
|
|
|
let browser;
|
|
try {
|
|
if (!frontendUrl || !backendUrl) throw new Error("LANGBOT_FRONTEND_URL and LANGBOT_BACKEND_URL must be configured.");
|
|
browser = await createBrowser(paths);
|
|
const { page } = browser;
|
|
await page.goto(frontendUrl, { waitUntil: "domcontentloaded" });
|
|
const auth = await ensureAuthenticatedBrowser(page, { frontendUrl, backendUrl });
|
|
if (auth.status !== "pass") {
|
|
result.status = auth.status;
|
|
throw new Error(auth.reason);
|
|
}
|
|
const token = await page.evaluate(() => localStorage.getItem("token") || "");
|
|
const pluginsResponse = await apiJson(backendUrl, "/api/v1/plugins", { token });
|
|
if (pluginsResponse.status >= 400 || pluginsResponse.json.code !== 0) {
|
|
throw new Error(pluginsResponse.json.msg || "Failed to list installed plugins.");
|
|
}
|
|
const installed = new Map((pluginsResponse.json.data?.plugins || []).map((item) => {
|
|
const metadata = item.manifest?.manifest?.metadata || item.manifest?.metadata || {};
|
|
return [`${metadata.author || ""}/${metadata.name || ""}`, item];
|
|
}));
|
|
const missing = targets.filter((target) => {
|
|
const plugin = installed.get(`${target.author}/${target.plugin}`);
|
|
if (!plugin) return true;
|
|
const pages = plugin.manifest?.manifest?.spec?.pages || plugin.manifest?.spec?.pages || [];
|
|
return !pages.some((pageSpec) => pageSpec.id === target.page);
|
|
});
|
|
if (missing.length) {
|
|
result.status = "blocked";
|
|
throw new Error(`Required plugin page fixture(s) are not installed or do not register the expected Page ID: ${missing.map((item) => item.id).join(", ")}`);
|
|
}
|
|
|
|
for (const target of targets) {
|
|
const routeId = `${target.author}/${target.plugin}/${target.page}`;
|
|
const route = `${frontendUrl.replace(/\/$/, "")}/home/plugin-pages?id=${encodeURIComponent(routeId)}`;
|
|
const pageResult = {
|
|
id: target.id,
|
|
route,
|
|
visible_signal: target.signal,
|
|
ui_status: "fail",
|
|
api_status: "fail",
|
|
screenshot: join(paths.evidenceDir, `${target.id}.png`),
|
|
page_api: null,
|
|
};
|
|
result.pages.push(pageResult);
|
|
|
|
await page.goto(route, { waitUntil: "domcontentloaded" });
|
|
await page.waitForLoadState("networkidle", { timeout: 15_000 }).catch(() => {});
|
|
const frame = page.frameLocator("iframe").first();
|
|
await frame.getByText(target.signal, { exact: true }).first().waitFor({ state: "visible", timeout: 30_000 });
|
|
pageResult.ui_status = "pass";
|
|
await safeScreenshot(page, pageResult.screenshot);
|
|
|
|
const apiResponse = await apiJson(
|
|
backendUrl,
|
|
`/api/v1/plugins/${encodeURIComponent(target.author)}/${encodeURIComponent(target.plugin)}/page-api`,
|
|
{
|
|
method: "POST",
|
|
token,
|
|
body: { page_id: target.page, endpoint: target.endpoint, method: target.method, body: null },
|
|
},
|
|
);
|
|
pageResult.page_api = {
|
|
endpoint: target.endpoint,
|
|
method: target.method,
|
|
http_status: apiResponse.status,
|
|
code: apiResponse.json.code ?? null,
|
|
has_data: apiResponse.json.data !== undefined && apiResponse.json.data !== null,
|
|
};
|
|
if (apiResponse.status >= 400 || apiResponse.json.code !== 0 || apiResponse.json.data == null) {
|
|
throw new Error(`${target.id} Page API ${target.method} ${target.endpoint} failed.`);
|
|
}
|
|
pageResult.api_status = "pass";
|
|
}
|
|
|
|
await safeScreenshot(page, paths.screenshot);
|
|
result.browser_diagnostics = await scanBrowserDiagnostics(paths);
|
|
if (result.browser_diagnostics.status !== "pass") {
|
|
throw new Error(result.browser_diagnostics.reason);
|
|
}
|
|
result.status = "pass";
|
|
result.reason = "All workspace plugin pages rendered in the LangBot WebUI and their read-only Page APIs passed.";
|
|
} catch (error) {
|
|
if (result.status === "fail" && /configured|Playwright is not installed|ECONNREFUSED|ERR_CONNECTION/i.test(error.message)) {
|
|
result.status = "env_issue";
|
|
}
|
|
result.reason = error.message;
|
|
} finally {
|
|
if (browser?.page) await safeScreenshot(browser.page, paths.screenshot);
|
|
if (browser) await browser.close().catch(() => {});
|
|
const finishedAt = new Date();
|
|
result.finished_at = finishedAt.toISOString();
|
|
result.finished_at_local = localIsoWithOffset(finishedAt);
|
|
await writeResult(paths, result);
|
|
console.log(JSON.stringify(result, null, 2));
|
|
}
|
|
|
|
process.exit(exitCode(result.status));
|