Files
llm-atlas/scripts/check-moe-browser.mjs
T
2026-07-28 23:32:14 +08:00

188 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { writeFileSync } from "node:fs";
const cdpPort = process.env.CDP_PORT ?? "9223";
const baseUrl = process.env.SITE_URL ?? "http://127.0.0.1:4322";
const pages = await fetch(`http://127.0.0.1:${cdpPort}/json/list`).then((response) => response.json());
const page = pages.find((entry) => entry.type === "page");
if (!page) throw new Error(`CDP ${cdpPort} 没有可用页面`);
const socket = new WebSocket(page.webSocketDebuggerUrl);
await new Promise((resolve, reject) => {
socket.addEventListener("open", resolve, { once: true });
socket.addEventListener("error", reject, { once: true });
});
let nextId = 0;
const pending = new Map();
const exceptions = [];
socket.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
if (message.id && pending.has(message.id)) {
const { resolve, reject } = pending.get(message.id);
pending.delete(message.id);
if (message.error) reject(new Error(message.error.message));
else resolve(message.result);
}
if (message.method === "Runtime.exceptionThrown") {
exceptions.push(message.params.exceptionDetails.text);
}
});
const command = (method, params = {}) => new Promise((resolve, reject) => {
const id = ++nextId;
pending.set(id, { resolve, reject });
socket.send(JSON.stringify({ id, method, params }));
});
const pause = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
const evaluate = async (expression) => {
const result = await command("Runtime.evaluate", {
expression,
returnByValue: true,
awaitPromise: true,
});
if (result.exceptionDetails) throw new Error(result.exceptionDetails.text);
return result.result.value;
};
const navigate = async (path) => {
await command("Page.navigate", { url: `${baseUrl}${path}` });
for (let attempt = 0; attempt < 30; attempt += 1) {
await pause(100);
if (await evaluate("document.readyState === 'complete'")) return;
}
throw new Error(`${path} 加载超时`);
};
const screenshot = async (path) => {
const result = await command("Page.captureScreenshot", { format: "png", captureBeyondViewport: false });
writeFileSync(path, Buffer.from(result.data, "base64"));
};
await command("Page.enable");
await command("Runtime.enable");
await command("Emulation.setDeviceMetricsOverride", {
width: 1440,
height: 1100,
deviceScaleFactor: 1,
mobile: false,
});
await navigate("/moe/");
const k3 = await evaluate(`(() => {
const root = document.querySelector("[data-moe-lab]");
const started = performance.now();
root.querySelector('[data-preset-button="k3"]').click();
const duration = performance.now() - started;
const skew = root.querySelector("[data-skew]");
const capacity = root.querySelector("[data-capacity]");
skew.value = "100";
capacity.value = "0.75";
skew.dispatchEvent(new Event("input", { bubbles: true }));
capacity.dispatchEvent(new Event("input", { bubbles: true }));
root.querySelector('[data-balance="none"]').click();
const none = {
ratio: root.querySelector("[data-load-ratio]").textContent,
overflow: root.querySelector("[data-drop-rate]").textContent,
};
root.querySelector('[data-balance="quantile"]').click();
return {
duration: Number(duration.toFixed(2)),
title: root.querySelector("[data-route-title]").textContent,
fraction: root.querySelector("[data-active-fraction]").textContent,
communication: root.querySelector("[data-comm-value]").textContent,
communicationNote: root.querySelector("[data-comm-note]").textContent,
combination: root.querySelector("[data-combination-value]").textContent,
none,
quantile: {
ratio: root.querySelector("[data-load-ratio]").textContent,
overflow: root.querySelector("[data-drop-rate]").textContent,
},
metrics: root.querySelectorAll(".metric-grid > article").length,
};
})()`);
const latent = await evaluate(`(() => {
const root = document.querySelector("[data-moe-lab]");
const started = performance.now();
root.querySelector('[data-preset-button="latent"]').click();
return {
duration: Number((performance.now() - started).toFixed(2)),
fraction: root.querySelector("[data-active-fraction]").textContent,
communication: root.querySelector("[data-comm-value]").textContent,
note: root.querySelector("[data-comm-note]").textContent,
};
})()`);
const layout = await evaluate(`(() => {
const header = document.querySelector(".site-header");
const nav = document.querySelector(".top-nav");
const meta = document.querySelector(".header-meta");
return {
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
navGap: Number((meta.getBoundingClientRect().left - nav.getBoundingClientRect().right).toFixed(1)),
articleSections: document.querySelectorAll(".article-section").length,
};
})()`);
await evaluate(`(() => {
document.documentElement.style.scrollBehavior = "auto";
document.querySelector("[data-moe-lab]").scrollIntoView({ block: "start", behavior: "instant" });
return { scrollY, top: document.querySelector("[data-moe-lab]").getBoundingClientRect().top };
})()`);
await pause(200);
await screenshot("/tmp/llm-atlas-moe-lab-desktop.png");
await command("Emulation.setDeviceMetricsOverride", {
width: 390,
height: 844,
deviceScaleFactor: 1,
mobile: true,
});
await navigate("/moe/");
const mobile = await evaluate(`({
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
menuVisible: getComputedStyle(document.querySelector("#menu-toggle")).display !== "none",
title: document.querySelector("h1").innerText,
})`);
await screenshot("/tmp/llm-atlas-moe-mobile.png");
await command("Emulation.setDeviceMetricsOverride", {
width: 1440,
height: 1100,
deviceScaleFactor: 1,
mobile: false,
});
await navigate("/");
const home = await evaluate(`({
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
releaseCards: document.querySelectorAll(".release-card").length,
navLinks: document.querySelectorAll(".top-nav a").length,
})`);
await screenshot("/tmp/llm-atlas-home-desktop.png");
await evaluate(`(() => {
document.documentElement.style.scrollBehavior = "auto";
document.querySelector("#new-chapters").scrollIntoView({ block: "start", behavior: "instant" });
})()`);
await pause(100);
await screenshot("/tmp/llm-atlas-home-releases.png");
const report = { k3, latent, layout, mobile, home, exceptions };
console.log(JSON.stringify(report, null, 2));
const failures = [];
if (!k3.title.includes("896 选 16")) failures.push("K3 预设未生效");
if (k3.metrics !== 6) failures.push(`指标卡数量异常:${k3.metrics}`);
if (!k3.communicationNote.includes("2× latent")) failures.push("K3 latent 通信说明缺失");
if (!latent.note.includes("4× latent")) failures.push("LatentMoE 4× 通信说明缺失");
if (layout.documentOverflow > 0 || mobile.documentOverflow > 0 || home.documentOverflow > 0) {
failures.push("页面存在横向溢出");
}
if (layout.navGap < 0) failures.push(`桌面导航碰撞:${layout.navGap}px`);
if (!mobile.menuVisible) failures.push("移动端菜单按钮未显示");
if (home.releaseCards !== 2) failures.push(`首页新章卡数量异常:${home.releaseCards}`);
if (exceptions.length) failures.push(`浏览器脚本异常:${exceptions.join("; ")}`);
socket.close();
if (failures.length) {
failures.forEach((failure) => console.error(`- ${failure}`));
process.exit(1);
}