const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const { chromium } = require("../apps/node_modules/@playwright/test");
async function main() {
const [base, folder] = process.argv.slice(2);
const endpoint = new URL(base);
assert.equal(endpoint.hostname, "127.0.0.1", "fixture must be loopback only");
const report = { scope: "local Chromium + real Web/Service; fixture identities", checks: [], result: "FAIL" };
const browser = await chromium.launch({ headless: true });
report.browser_version = browser.version();
const context = await browser.newContext({ viewport: { width: 1365, height: 900 } });
const page = await context.newPage();
const pageErrors = [];
const rpcStatuses = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
page.on("response", (response) => {
if (new URL(response.url()).pathname === "/api/rpc") rpcStatuses.push(response.status());
});
try {
await page.goto(base);
await page.waitForURL("**/__login");
await page.locator('input[name="username"]').waitFor({ state: "visible" });
report.checks.push("anonymous_ui_redirects_to_login");
await page.locator('input[name="username"]').fill("fixture-admin");
await page.locator('input[name="password"]').fill("fixture-admin-password");
await page.locator('button[type="submit"]').click();
await page.waitForURL(base + "/");
await page.getByRole("button", { name: "本次关闭", exact: true }).click();
report.checks.push("first_launch_guide_dismissed_through_ui");
const logout = page.getByRole("button", { name: /退出登录|Sign out|Log out/i });
await logout.waitFor({ state: "visible" });
report.checks.push("login_bootstrap_executes_and_react_shell_renders");
const cookies = await context.cookies(base);
assert(cookies.some((cookie) => cookie.httpOnly && cookie.sameSite === "Lax"));
const state = await page.evaluate(async () => {
const response = await fetch("/__auth_status");
const auth = await response.json();
return { role: auth.role, sessionStorageEntries: sessionStorage.length };
});
assert.equal(state.role, "admin");
assert(state.sessionStorageEntries > 0);
report.checks.push("browser_cookie_and_tab_session_resolve_admin");
await page.locator('a[href="/apikeys/"]').first().click();
await page.waitForURL(base + "/apikeys/");
await logout.waitFor({ state: "visible" });
await page.getByText("Migration fixture", { exact: true }).first().waitFor({ state: "visible" });
assert(rpcStatuses.includes(200), "rendered UI must call real RPC transport");
report.checks.push("api_key_page_reads_fixture_through_real_rpc");
await page.screenshot({ path: path.join(folder, "browser-api-keys.png"), fullPage: true });
await logout.click();
await page.waitForURL("**/__login?force=1");
const status = await page.evaluate(async () => (await fetch("/api/rpc", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: "browser-logout", method: "accountManager/status" }),
})).status);
assert.equal(status, 401);
report.checks.push("ui_logout_revokes_browser_rpc_session");
assert.deepEqual(pageErrors, [], "uncaught browser exceptions");
report.checks.push("no_uncaught_browser_exceptions");
report.result = "PASS";
} catch (error) {
report.failure = error.message;
await page.screenshot({ path: path.join(folder, "browser-failure.png"), fullPage: true }).catch(() => {});
} finally {
report.page_errors = pageErrors;
report.rpc_status_counts = Object.fromEntries([...new Set(rpcStatuses)].map((status) =>
[status, rpcStatuses.filter((value) => value === status).length]));
await browser.close();
fs.writeFileSync(path.join(folder, "browser-report.json"), JSON.stringify(report, null, 2) + "\n");
}
process.exitCode = report.result === "PASS" ? 0 : 1;
}
main().catch((error) => { console.error(error.message); process.exitCode = 1; });