258 lines
12 KiB
JavaScript
258 lines
12 KiB
JavaScript
import { writeFileSync } from "node:fs";
|
||
|
||
const cdpPort = process.env.CDP_PORT ?? "9231";
|
||
const baseUrl = process.env.SITE_URL ?? "http://127.0.0.1:4330";
|
||
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.exception?.description ?? 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.exception?.description ?? result.exceptionDetails.text);
|
||
return result.result.value;
|
||
};
|
||
const navigate = async (path) => {
|
||
await command("Page.navigate", { url: `${baseUrl}${path}` });
|
||
for (let attempt = 0; attempt < 100; 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("/k3/");
|
||
|
||
const desktop = await evaluate(`(() => {
|
||
const root = document.querySelector("[data-forward-lab]");
|
||
root.scrollIntoView({ block: "start", behavior: "instant" });
|
||
window.scrollBy(0, -78);
|
||
const text = (selector) => root.querySelector(selector)?.textContent.replace(/\\s+/g, " ").trim();
|
||
const panel = () => root.querySelector("[data-forward-panel]:not([hidden])")?.dataset.forwardPanel;
|
||
const setSelect = (selector, value) => {
|
||
const node = root.querySelector(selector);
|
||
node.value = value;
|
||
node.dispatchEvent(new Event("change", { bubbles: true }));
|
||
};
|
||
|
||
const initial = {
|
||
panel: panel(),
|
||
tabs: root.querySelectorAll("[data-forward-tab]").length,
|
||
panels: root.querySelectorAll("[data-forward-panel]").length,
|
||
ledger: root.querySelectorAll(".forward-ledger article").length,
|
||
targetGroups: root.querySelectorAll(".group-map article.target").length,
|
||
spikeLayers: root.querySelectorAll(".group-map i.spike").length,
|
||
variants: root.querySelectorAll(".variant-grid article").length,
|
||
boundary: root.textContent.includes("K3 的训练尖峰已被定位") &&
|
||
root.textContent.includes("Figure 5(c)") &&
|
||
root.textContent.includes("训练期架构消融"),
|
||
};
|
||
|
||
root.querySelector('[data-forward-tab="trajectory"]').click();
|
||
const trajectoryInitial = {
|
||
panel: panel(),
|
||
state: text("[data-forward-trajectory-state]"),
|
||
points: root.querySelectorAll("[data-forward-trajectory-series] circle").length,
|
||
lines: root.querySelectorAll("[data-forward-trajectory-series] polyline").length,
|
||
readouts: [...root.querySelectorAll("[data-forward-trajectory-readout] article")]
|
||
.map((node) => node.textContent.replace(/\\s+/g, " ").trim()),
|
||
};
|
||
setSelect("[data-forward-trajectory-seed]", "2026073002");
|
||
root.querySelector('[data-forward-trajectory-metric="peak_normalized"]').click();
|
||
const trajectoryChanged = {
|
||
state: text("[data-forward-trajectory-state]"),
|
||
readouts: [...root.querySelectorAll("[data-forward-trajectory-readout] article")]
|
||
.map((node) => node.textContent.replace(/\\s+/g, " ").trim()),
|
||
};
|
||
|
||
root.querySelector('[data-forward-tab="gate"]').click();
|
||
const gate = {
|
||
panel: panel(),
|
||
status: text(".status-banner"),
|
||
rows: root.querySelectorAll(".gate-table tbody tr").length,
|
||
passedRows: root.querySelectorAll(".gate-table tbody td.good:last-child").length,
|
||
quality: text(".gate-layout aside"),
|
||
claims: root.querySelectorAll(".claim-pair article").length,
|
||
};
|
||
|
||
root.querySelector('[data-forward-tab="interaction"]').click();
|
||
const interactionInitial = {
|
||
panel: panel(),
|
||
state: text("[data-forward-interaction-state]"),
|
||
cells: root.querySelectorAll("[data-forward-interaction-cells] article").length,
|
||
summary: text("[data-forward-interaction-summary]"),
|
||
};
|
||
root.querySelector('[data-forward-interaction-metric="peak_normalized"]').click();
|
||
const interactionChanged = {
|
||
state: text("[data-forward-interaction-state]"),
|
||
summary: text("[data-forward-interaction-summary]"),
|
||
};
|
||
|
||
root.querySelector('[data-forward-tab="spectrum"]').click();
|
||
const spectrumInitial = {
|
||
panel: panel(),
|
||
state: text("[data-forward-spectrum-state]"),
|
||
points: root.querySelectorAll("[data-forward-spectrum-points] circle").length,
|
||
contrast: text("[data-forward-spectrum-contrast]"),
|
||
peak: text("[data-forward-spectrum-peak]"),
|
||
layer: text("[data-forward-spectrum-layer]"),
|
||
audits: root.querySelectorAll(".audit-grid article").length,
|
||
replay: root.textContent.includes("scientific exact"),
|
||
};
|
||
setSelect("[data-forward-spectrum-seed]", "2026073002");
|
||
setSelect("[data-forward-spectrum-variant]", "uniform_groups_6_7_forward");
|
||
const spectrumChanged = {
|
||
state: text("[data-forward-spectrum-state]"),
|
||
points: root.querySelectorAll("[data-forward-spectrum-points] circle").length,
|
||
contrast: text("[data-forward-spectrum-contrast]"),
|
||
peak: text("[data-forward-spectrum-peak]"),
|
||
layer: text("[data-forward-spectrum-layer]"),
|
||
};
|
||
|
||
const first = root.querySelector('[data-forward-tab="contract"]');
|
||
first.focus();
|
||
first.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true }));
|
||
const keyboard = {
|
||
selected: root.querySelector('[data-forward-tab][aria-selected="true"]').dataset.forwardTab,
|
||
panel: panel(),
|
||
};
|
||
|
||
return {
|
||
initial, trajectoryInitial, trajectoryChanged, gate,
|
||
interactionInitial, interactionChanged, spectrumInitial, spectrumChanged, keyboard,
|
||
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
||
rootOverflow: root.scrollWidth - root.clientWidth,
|
||
};
|
||
})()`);
|
||
await pause(180);
|
||
await screenshot("/tmp/llm-atlas-k3-attnres-forward-desktop.png");
|
||
|
||
await command("Emulation.setDeviceMetricsOverride", {
|
||
width: 390,
|
||
height: 844,
|
||
deviceScaleFactor: 1,
|
||
mobile: true,
|
||
});
|
||
await navigate("/k3/");
|
||
const mobile = await evaluate(`(() => {
|
||
const root = document.querySelector("[data-forward-lab]");
|
||
root.scrollIntoView({ block: "start", behavior: "instant" });
|
||
window.scrollBy(0, -64);
|
||
root.querySelector('[data-forward-tab="gate"]').click();
|
||
const table = root.querySelector(".gate-table-wrap");
|
||
const gateTableScrolls = table.scrollWidth > table.clientWidth;
|
||
root.querySelector('[data-forward-tab="spectrum"]').click();
|
||
return {
|
||
tabs: root.querySelectorAll("[data-forward-tab]").length,
|
||
visiblePanel: root.querySelector("[data-forward-panel]:not([hidden])")?.dataset.forwardPanel,
|
||
points: root.querySelectorAll("[data-forward-spectrum-points] circle").length,
|
||
audits: root.querySelectorAll(".audit-grid article").length,
|
||
gateTableScrolls,
|
||
documentOverflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
||
rootOverflow: root.scrollWidth - root.clientWidth,
|
||
};
|
||
})()`);
|
||
await pause(180);
|
||
await screenshot("/tmp/llm-atlas-k3-attnres-forward-mobile.png");
|
||
|
||
const failures = [];
|
||
if (desktop.initial.panel !== "contract" || desktop.initial.tabs !== 5 || desktop.initial.panels !== 5 ||
|
||
desktop.initial.ledger !== 6 || desktop.initial.targetGroups !== 2 ||
|
||
desktop.initial.spikeLayers !== 5 || desktop.initial.variants !== 4) {
|
||
failures.push("五视图、账本、group 或 selector map 结构异常");
|
||
}
|
||
if (!desktop.initial.boundary) failures.push("reduced-model / K3 / Figure 5(c) claim boundary 缺失");
|
||
if (desktop.trajectoryInitial.panel !== "trajectory" || desktop.trajectoryInitial.points !== 24 ||
|
||
desktop.trajectoryInitial.lines !== 4 || desktop.trajectoryInitial.readouts.length !== 4 ||
|
||
!desktop.trajectoryInitial.state.includes("2026073001") ||
|
||
!desktop.trajectoryInitial.readouts.some((value) => value.includes("+73.5%"))) {
|
||
failures.push("seed 1 contrast 训练轨迹异常");
|
||
}
|
||
if (!desktop.trajectoryChanged.state.includes("2026073002") ||
|
||
!desktop.trajectoryChanged.state.includes("PEAK / MEAN") ||
|
||
!desktop.trajectoryChanged.readouts.some((value) => value.includes("+62.0%"))) {
|
||
failures.push("trajectory seed / metric 切换异常");
|
||
}
|
||
if (desktop.gate.panel !== "gate" || desktop.gate.rows !== 6 || desktop.gate.passedRows !== 6 ||
|
||
desktop.gate.claims !== 2 || !desktop.gate.status.includes("ATTENUATION ESTABLISHED") ||
|
||
!desktop.gate.status.includes("6 / 6") || !desktop.gate.status.includes("4 / 4") ||
|
||
!desktop.gate.quality.includes("+0.0066")) {
|
||
failures.push("冻结主门或 BPC quality readout 异常");
|
||
}
|
||
if (desktop.interactionInitial.panel !== "interaction" || desktop.interactionInitial.cells !== 3 ||
|
||
!desktop.interactionInitial.state.includes("8,000") ||
|
||
!desktop.interactionInitial.summary.includes("-0.367") ||
|
||
!desktop.interactionChanged.state.includes("PEAK / MEAN") ||
|
||
!desktop.interactionChanged.summary.includes("-0.170")) {
|
||
failures.push("I67 描述性 residual 切换异常");
|
||
}
|
||
if (desktop.spectrumInitial.panel !== "spectrum" || desktop.spectrumInitial.points !== 32 ||
|
||
desktop.spectrumInitial.contrast !== "3.046×" || desktop.spectrumInitial.peak !== "3.219×" ||
|
||
desktop.spectrumInitial.layer !== "L21" || desktop.spectrumInitial.audits !== 5 ||
|
||
!desktop.spectrumInitial.replay) {
|
||
failures.push("reference 32 层谱或 replay 审计异常");
|
||
}
|
||
if (desktop.spectrumChanged.points !== 32 || !desktop.spectrumChanged.state.includes("2026073002") ||
|
||
!desktop.spectrumChanged.state.includes("GROUPS 6+7") ||
|
||
desktop.spectrumChanged.contrast !== "0.758×" || desktop.spectrumChanged.peak !== "1.375×" ||
|
||
desktop.spectrumChanged.layer !== "L9") {
|
||
failures.push("joint variant spectrum 切换异常");
|
||
}
|
||
if (desktop.keyboard.selected !== "trajectory" || desktop.keyboard.panel !== "trajectory") {
|
||
failures.push("键盘 tab 切换异常");
|
||
}
|
||
if (desktop.documentOverflow > 1 || mobile.documentOverflow > 1) failures.push("页面出现文档级横向溢出");
|
||
if (desktop.rootOverflow > 1 || mobile.rootOverflow > 1) failures.push("Round 08 实验室出现横向溢出");
|
||
if (mobile.tabs !== 5 || mobile.visiblePanel !== "spectrum" || mobile.points !== 32 ||
|
||
mobile.audits !== 5 || !mobile.gateTableScrolls) {
|
||
failures.push("390px 移动端布局或局部表格滚动异常");
|
||
}
|
||
if (exceptions.length) failures.push(`运行时异常:${exceptions.join(" | ")}`);
|
||
|
||
console.log(JSON.stringify({ desktop, mobile, exceptions }, null, 2));
|
||
socket.close();
|
||
if (failures.length) {
|
||
console.error(`FAIL K3 AttnRes forward-training browser\n- ${failures.join("\n- ")}`);
|
||
process.exit(1);
|
||
}
|
||
console.log("PASS K3 AttnRes forward-training browser interactions");
|