119 lines
10 KiB
JavaScript
119 lines
10 KiB
JavaScript
const path = require("path");
|
|
const fs = require("fs");
|
|
const { execFileSync } = require("child_process");
|
|
const root = process.env.PLAYWRIGHT_REQUIRE_FROM;
|
|
if (!root) throw new Error("PLAYWRIGHT_REQUIRE_FROM is required");
|
|
const { chromium } = require(path.join(root, "playwright"));
|
|
const chromePath = process.env.CHROME_PATH || "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
|
|
const outputDir = path.resolve(process.env.UI_SCREENSHOT_DIR || "test/e2e/screenshots/operations");
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
|
|
function psql(sql) {
|
|
return execFileSync("docker", ["compose", "exec", "-T", "postgres", "psql", "-U", "voice", "-d", "voice_platform", "-v", "ON_ERROR_STOP=1", "-q", "-c", sql], { cwd: path.resolve(__dirname, ".."), encoding: "utf8" });
|
|
}
|
|
|
|
(async () => {
|
|
let acceptanceKey;
|
|
let acceptanceID;
|
|
let pilotDailyKey;
|
|
let pilotDailyID;
|
|
let pilotWeeklyKey;
|
|
let pilotWeeklyID;
|
|
const browser = await chromium.launch({ executablePath: chromePath, headless: true });
|
|
try {
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 1100 } });
|
|
const failures = [];
|
|
page.on("pageerror", (error) => failures.push(`pageerror: ${error.message}`));
|
|
page.on("console", (message) => { if (message.type() === "error") failures.push(`console: ${message.text()}`); });
|
|
page.on("response", (response) => { if (response.status() >= 400) failures.push(`http ${response.status()}: ${response.url()}`); });
|
|
page.on("request", (request) => {
|
|
if (request.url().endsWith("/api/v1/ops/diagnostics/acceptance-snapshots")) acceptanceKey = request.headers()["idempotency-key"];
|
|
if (request.url().endsWith("/api/v1/ops/diagnostics/pilot-daily-snapshots")) pilotDailyKey = request.headers()["idempotency-key"];
|
|
if (request.url().endsWith("/api/v1/ops/diagnostics/pilot-weekly-snapshots")) pilotWeeklyKey = request.headers()["idempotency-key"];
|
|
});
|
|
await page.goto("http://localhost:3100", { waitUntil: "networkidle" });
|
|
const login = page.getByRole("button", { name: "登录研发环境" });
|
|
if (await login.isVisible()) {
|
|
await login.click();
|
|
await page.locator("#username").fill("owner");
|
|
await page.locator("#password").fill("LocalOnly123!");
|
|
await page.locator("#kc-login").click();
|
|
}
|
|
await page.waitForURL("http://localhost:3100/**", { timeout: 15000 });
|
|
await page.getByRole("button", { name: /^联调诊断/ }).click();
|
|
await page.getByRole("heading", { name: "设备与通话联调" }).waitFor({ state: "visible", timeout: 10000 });
|
|
await page.getByRole("heading", { name: "发布候选验收快照" }).waitFor({ state: "visible", timeout: 10000 });
|
|
await page.getByRole("heading", { name: "试点运行日报" }).waitFor({ state: "visible", timeout: 10000 });
|
|
await page.getByRole("heading", { name: "试点运行周报" }).waitFor({ state: "visible", timeout: 10000 });
|
|
await page.getByRole("heading", { name: "外部依赖预检" }).waitFor({ state: "visible", timeout: 10000 });
|
|
await page.getByRole("heading", { name: "联调问题清单" }).waitFor({ state: "visible", timeout: 10000 });
|
|
const oidcCard = page.locator(".external-dependency-grid .diagnostic-card").filter({ hasText: "OIDC签名密钥" });
|
|
if (await oidcCard.count() !== 1) failures.push("OIDC preflight card is missing or duplicated");
|
|
else {
|
|
if (!await oidcCard.getByText("API进程", { exact: false }).isVisible()) failures.push("OIDC preflight service ownership is missing");
|
|
if (!await oidcCard.getByText("可用", { exact: true }).isVisible()) failures.push("local OIDC preflight is not available");
|
|
if (await oidcCard.getByText("结果过期", { exact: true }).count()) failures.push("fresh OIDC preflight was marked stale");
|
|
}
|
|
const issueSection = page.locator(".integration-issues");
|
|
if (!await issueSection.getByText("高严重度门禁", { exact: true }).isVisible()) failures.push("integration issue release gate summary is missing");
|
|
if (!await issueSection.getByText("通过", { exact: true }).isVisible()) failures.push("empty local issue ledger did not show a satisfied high-severity gate");
|
|
if (!await issueSection.getByLabel("严重级别").isVisible()) failures.push("integration issue severity input is missing");
|
|
const acceptanceSection = page.locator(".acceptance-snapshot");
|
|
await acceptanceSection.getByLabel("候选版本").fill("0.51.0-rc.browser");
|
|
await acceptanceSection.getByLabel("构建摘要").fill(`sha256:${"b".repeat(64)}`);
|
|
await acceptanceSection.getByText("方舟模型服务TLS", { exact: true }).locator("input").uncheck();
|
|
await acceptanceSection.getByText("火山RTC服务TLS", { exact: true }).locator("input").uncheck();
|
|
const downloadPromise = page.waitForEvent("download");
|
|
await acceptanceSection.getByRole("button", { name: "生成并下载快照" }).click();
|
|
const download = await downloadPromise;
|
|
const downloadPath = await download.path();
|
|
const acceptance = JSON.parse(fs.readFileSync(downloadPath, "utf8"));
|
|
acceptanceID = acceptance.id;
|
|
if (!acceptanceID || acceptance.candidate_version !== "0.51.0-rc.browser" || acceptance.redaction_policy !== "ops-acceptance-minimal-v1") failures.push("downloaded acceptance snapshot metadata is invalid");
|
|
const failedAcceptanceGates = acceptance.gates?.filter((gate) => !gate.satisfied) || [];
|
|
if (acceptance.gates?.length !== 6 || acceptance.external_dependencies?.length !== 1 || acceptance.overall_gate_satisfied !== (failedAcceptanceGates.length === 0)) failures.push("downloaded acceptance snapshot gates are invalid");
|
|
const blockingItems = acceptance.alerts?.blocking_items || [];
|
|
if (blockingItems.length !== Math.min(acceptance.alerts?.blocking || 0, 100)) failures.push("downloaded blocking alert evidence count is invalid");
|
|
if (blockingItems.some((item) => !item.id || !item.rule_code || !["PRESENT", "MISSING", "AGGREGATE", "UNKNOWN"].includes(item.resource_state))) failures.push("downloaded blocking alert evidence is invalid");
|
|
if (blockingItems.length && !await acceptanceSection.getByRole("heading", { name: "阻断告警定位" }).isVisible()) failures.push("blocking alert evidence is not rendered");
|
|
if (!await page.getByText("技术验收快照已下载", { exact: false }).isVisible()) failures.push("acceptance download result message is missing");
|
|
const pilotSection = page.locator(".pilot-daily-snapshot");
|
|
const pilotDownloadPromise = page.waitForEvent("download");
|
|
await pilotSection.getByRole("button", { name: "生成并下载日报" }).click();
|
|
const pilotDownload = await pilotDownloadPromise;
|
|
const pilotDownloadPath = await pilotDownload.path();
|
|
const pilotDaily = JSON.parse(fs.readFileSync(pilotDownloadPath, "utf8"));
|
|
pilotDailyID = pilotDaily.id;
|
|
if (!pilotDailyID || pilotDaily.candidate_version !== "0.51.0-rc.browser" || pilotDaily.build_digest !== `sha256:${"b".repeat(64)}` || pilotDaily.redaction_policy !== "ops-pilot-daily-minimal-v1" || pilotDaily.conversations?.statuses?.length !== 8) failures.push("downloaded pilot daily snapshot metadata is invalid");
|
|
if ((pilotDaily.conversations?.statuses || []).reduce((sum, item) => sum + item.count, 0) !== pilotDaily.conversations?.total) failures.push("downloaded pilot daily conversation distribution is invalid");
|
|
if (!await page.getByText("试点运行日报已生成并下载", { exact: false }).isVisible()) failures.push("pilot daily download result message is missing");
|
|
const pilotWeeklySection = page.locator(".pilot-weekly-snapshot");
|
|
const pilotWeeklyDownloadPromise = page.waitForEvent("download");
|
|
await pilotWeeklySection.getByRole("button", { name: "生成并下载周报" }).click();
|
|
const pilotWeeklyDownload = await pilotWeeklyDownloadPromise;
|
|
const pilotWeeklyDownloadPath = await pilotWeeklyDownload.path();
|
|
const pilotWeekly = JSON.parse(fs.readFileSync(pilotWeeklyDownloadPath, "utf8"));
|
|
pilotWeeklyID = pilotWeekly.id;
|
|
if (!pilotWeeklyID || pilotWeekly.candidate_version !== "0.51.0-rc.browser" || pilotWeekly.build_digest !== `sha256:${"b".repeat(64)}` || pilotWeekly.redaction_policy !== "ops-pilot-weekly-minimal-v1" || pilotWeekly.privacy?.by_type?.length !== 4 || pilotWeekly.privacy?.by_status?.length !== 5) failures.push("downloaded pilot weekly snapshot metadata is invalid");
|
|
if ((pilotWeekly.config?.aligned || 0) + (pilotWeekly.config?.drifted || 0) + (pilotWeekly.config?.unreported || 0) !== pilotWeekly.config?.eligible_devices) failures.push("downloaded pilot weekly config distribution is invalid");
|
|
if (pilotWeekly.external_evidence?.[0]?.code !== "complaints" || pilotWeekly.external_evidence?.[0]?.status !== "REQUIRED") failures.push("pilot weekly complaint evidence gap is missing");
|
|
if (!await pilotWeeklySection.getByText("外部证据仍需补充:投诉", { exact: true }).isVisible()) failures.push("pilot weekly complaint evidence warning is missing");
|
|
const overflow = await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth + 1);
|
|
if (overflow) failures.push("diagnostics workspace has horizontal overflow");
|
|
await page.screenshot({ path: path.join(outputDir, "ops-diagnostics-workspace.png"), fullPage: true });
|
|
if (failures.length) throw new Error(failures.join("\n"));
|
|
process.stdout.write(`Operations diagnostics UI browser smoke passed. Screenshot: ${outputDir}\n`);
|
|
} finally {
|
|
await browser.close();
|
|
if (acceptanceKey || acceptanceID || pilotDailyKey || pilotDailyID || pilotWeeklyKey || pilotWeeklyID) {
|
|
try {
|
|
const predicates = [];
|
|
if (acceptanceID) predicates.push(`(resource_type='acceptance_snapshot' AND resource_id='${acceptanceID}'::uuid)`);
|
|
if (pilotDailyID) predicates.push(`(resource_type='pilot_daily_snapshot' AND resource_id='${pilotDailyID}'::uuid)`);
|
|
if (pilotWeeklyID) predicates.push(`(resource_type='pilot_weekly_snapshot' AND resource_id='${pilotWeeklyID}'::uuid)`);
|
|
psql(`BEGIN; DELETE FROM audit_event WHERE ${predicates.length ? predicates.join(" OR ") : "false"}; DELETE FROM idempotency_record WHERE idempotency_key IN ('${acceptanceKey || "missing"}','${pilotDailyKey || "missing"}','${pilotWeeklyKey || "missing"}'); COMMIT;`);
|
|
} catch (error) { process.stderr.write(`diagnostics evidence cleanup failed: ${error.message}\n`); }
|
|
}
|
|
}
|
|
})().catch((error) => { process.stderr.write(`${error.stack || error}\n`); process.exitCode = 1; });
|