import { createRequire } from "node:module"; const require = createRequire(process.env.PLAYWRIGHT_REQUIRE_FROM ?? import.meta.url); const { chromium } = require("playwright"); const webURL = process.env.VISUAL_WEB_URL ?? "http://localhost:3100"; const username = process.env.VISUAL_USERNAME ?? "operator"; const password = process.env.VISUAL_PASSWORD ?? "OpsLocalOnly123!"; const browser = await chromium.launch({ headless: true, executablePath: process.env.CHROME_PATH ?? "C:/Program Files/Google/Chrome/Application/chrome.exe", }); const page = await browser.newPage({ viewport: { width: 1440, height: 1000 }, deviceScaleFactor: 1 }); const consoleErrors = []; const failedRequests = []; const errorResponses = []; page.on("console", (message) => { if (message.type() === "error") consoleErrors.push(message.text()); }); page.on("pageerror", (error) => consoleErrors.push(`pageerror: ${error.message}`)); page.on("response", (response) => { if (response.status() >= 400) errorResponses.push(`${response.status()} ${response.url()}`); }); page.on("requestfailed", (request) => { const reason = request.failure()?.errorText ?? "failed"; const pathname = new URL(request.url()).pathname; if (request.method() === "GET" && pathname === "/api/v1/events" && reason.includes("ERR_ABORTED")) return; if (pathname.startsWith("/api/v1/") && reason.includes("ERR_ABORTED")) return; failedRequests.push(`${request.method()} ${request.url()} ${reason}`); }); const areas = [ ["总览", "维护总览", null], ["设备管理", "设备管理", "devices"], ["运行配置", "运行配置", "config"], ["大模型管理", "大模型管理", "models"], ["内容质量", "内容质量", "quality"], ["用量、费用与额度", "用量、费用与额度", "billing"], ["告警处置", "告警处置", "alerts"], ["联调与诊断", "联调与诊断", "diagnostics"], ]; try { await page.goto(`${webURL}/maintenance`, { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: "登录维护中心", exact: true }).click(); await page.waitForURL(/localhost:58180|127\.0\.0\.1:58180/); await page.locator("#username").fill(username); await page.locator("#password").fill(password); await page.locator("#kc-login").click(); await page.waitForURL(new RegExp(`^${new URL(webURL).origin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/maintenance(?:[?#]|$)`), { timeout: 15000 }); await page.getByRole("heading", { name: "维护总览", exact: true, level: 1 }).waitFor({ timeout: 15000 }); const navigation = page.getByRole("navigation", { name: "维护中心主导航" }); const areaResults = []; for (const [label, heading, area] of areas) { await navigation.getByRole("button", { name: label, exact: true }).click(); await page.getByRole("heading", { name: heading, exact: true, level: 1 }).waitFor(); areaResults.push({ label, area: new URL(page.url()).searchParams.get("area"), expectedArea: area }); } const search = page.getByPlaceholder("搜索设备、通话、作业", { exact: true }); await search.fill("告警"); await search.press("Enter"); await page.getByRole("heading", { name: "告警处置", exact: true, level: 1 }).waitFor(); const alertSearchWorks = new URL(page.url()).searchParams.get("area") === "alerts"; await search.fill("10000000-0000-0000-0000-000000000511"); await search.press("Enter"); await page.getByRole("heading", { name: "联调与诊断", exact: true, level: 1 }).waitFor(); const identifierSearchWorks = new URL(page.url()).searchParams.get("area") === "diagnostics"; await navigation.getByRole("button", { name: "大模型管理", exact: true }).click(); await page.getByRole("heading", { name: "大模型管理", exact: true, level: 1 }).waitFor(); await page.getByRole("button", { name: /添加大模型/ }).click(); const modelDialog = page.getByRole("dialog", { name: "添加总结大模型", exact: true }); await modelDialog.waitFor(); const providerButtons = modelDialog.locator(".model-provider-group button"); const modelPaletteWorks = await providerButtons.evaluateAll((controls) => controls.length > 0 && controls.every((item) => { const channels = getComputedStyle(item).backgroundColor.match(/\d+/g)?.slice(0, 3).map(Number) ?? []; return channels.length === 3 && channels.reduce((sum, value) => sum + value, 0) / 3 > 210; })); await modelDialog.getByRole("button", { name: "关闭", exact: true }).click(); await navigation.getByRole("button", { name: "设备管理", exact: true }).click(); await page.getByRole("button", { name: /新增设备/ }).click(); const createDialog = page.getByRole("dialog", { name: "新增设备", exact: true }); await createDialog.waitFor(); await createDialog.getByRole("button", { name: "关闭", exact: true }).click(); await page.getByRole("button", { name: /编辑/ }).first().click(); const editDialog = page.getByRole("dialog", { name: "编辑设备", exact: true }); await editDialog.waitFor(); await editDialog.getByRole("button", { name: "关闭", exact: true }).click(); await navigation.getByRole("button", { name: "联调与诊断", exact: true }).click(); await page.getByPlaceholder("输入完整编号", { exact: true }).fill("10000000-0000-0000-0000-000000000511"); await page.getByRole("button", { name: "查询", exact: true }).click(); await page.getByRole("button", { name: "导出脱敏诊断包", exact: true }).waitFor(); await page.getByRole("button", { name: "导出脱敏诊断包", exact: true }).click(); const exportForm = page.getByRole("form").last(); const exportSubmit = exportForm.getByRole("button", { name: "生成并下载", exact: true }); const exportInitiallyDisabled = await exportSubmit.isDisabled(); await exportForm.getByRole("textbox", { name: "导出原因", exact: true }).fill("页面功能测试,不下载"); const exportReady = await exportSubmit.isEnabled(); await exportForm.getByRole("button", { name: "取消", exact: true }).click(); const areaFailures = areaResults.filter((item) => item.area !== item.expectedArea); const relevantResponseErrors = [...new Set(errorResponses)].filter((item) => !item.includes("/api/v1/events") && !(item.startsWith("404 ") && item.endsWith("/api/v1/ops/diagnostics/platform-health"))); const relevantConsoleErrors = consoleErrors.filter((item) => !item.startsWith("Failed to load resource:")); const report = { areaResults, areaFailures, alertSearchWorks, identifierSearchWorks, modelPaletteWorks, exportInitiallyDisabled, exportReady, consoleErrors: relevantConsoleErrors, errorResponses: relevantResponseErrors, failedRequests }; console.log(JSON.stringify(report, null, 2)); if (areaFailures.length || !alertSearchWorks || !identifierSearchWorks || !modelPaletteWorks || !exportInitiallyDisabled || !exportReady || relevantConsoleErrors.length || relevantResponseErrors.length || failedRequests.length) { throw new Error("Maintenance navigation/function smoke failed"); } } finally { await browser.close(); }