2026-09-04 12:18:06 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 浏览器实测:刷新速度按链路各存一份(DOM + localStorage 往返)。
|
|
|
|
|
|
* 单测覆盖不到的部分:输入框回填、切链路自动换、刷新页面后仍各自独立。
|
|
|
|
|
|
*/
|
|
|
|
|
|
const CDP_BASE = "http://127.0.0.1:9333";
|
|
|
|
|
|
|
|
|
|
|
|
async function ws() {
|
|
|
|
|
|
const list = await (await fetch(CDP_BASE + "/json/list")).json();
|
|
|
|
|
|
const page = list.find((t) => t.type === "page");
|
|
|
|
|
|
return page.webSocketDebuggerUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
const { WebSocket } = await import("node:http").then(() => import("ws")).catch(() => ({}));
|
|
|
|
|
|
|
|
|
|
|
|
// 没有 ws 依赖时用原生 WebSocket(Node 22+)
|
|
|
|
|
|
const WSImpl = globalThis.WebSocket || WebSocket;
|
|
|
|
|
|
|
|
|
|
|
|
const url = await ws();
|
|
|
|
|
|
const sock = new WSImpl(url);
|
|
|
|
|
|
let id = 0;
|
|
|
|
|
|
const pending = new Map();
|
|
|
|
|
|
await new Promise((r, j) => { sock.onopen = r; sock.onerror = j; });
|
|
|
|
|
|
sock.onmessage = (ev) => {
|
|
|
|
|
|
const m = JSON.parse(ev.data);
|
|
|
|
|
|
if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); }
|
|
|
|
|
|
};
|
|
|
|
|
|
function send(method, params = {}) {
|
|
|
|
|
|
const myId = ++id;
|
|
|
|
|
|
return new Promise((res) => { pending.set(myId, res); sock.send(JSON.stringify({ id: myId, method, params })); });
|
|
|
|
|
|
}
|
|
|
|
|
|
async function evaluate(expr) {
|
|
|
|
|
|
const r = await send("Runtime.evaluate", { expression: expr, awaitPromise: true, returnByValue: true });
|
|
|
|
|
|
if (r.result && r.result.exceptionDetails) throw new Error(JSON.stringify(r.result.exceptionDetails));
|
|
|
|
|
|
return r.result && r.result.result && r.result.result.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
|
|
|
|
|
|
|
|
let pass = 0, fail = 0;
|
|
|
|
|
|
const out = [];
|
|
|
|
|
|
function check(name, cond, detail) {
|
|
|
|
|
|
if (cond) { pass++; out.push(" \u2713 " + name); }
|
|
|
|
|
|
else { fail++; out.push(" \u2717 " + name + (detail ? " \u2014 " + detail : "")); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await send("Runtime.enable");
|
|
|
|
|
|
await send("Page.enable");
|
|
|
|
|
|
|
2026-09-07 12:19:26 +08:00
|
|
|
|
/* 页面服务端口可传参:node scripts/xxx.mjs [port](默认 2625) */
|
|
|
|
|
|
const PORT = Number(process.argv[2] || 2625);
|
2026-09-04 12:18:06 +08:00
|
|
|
|
const PAGE = `http://127.0.0.1:${PORT}/pages/motor.html`;
|
|
|
|
|
|
|
|
|
|
|
|
async function goto(u) {
|
|
|
|
|
|
await send("Page.navigate", { url: u });
|
|
|
|
|
|
await sleep(2500);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 干净起点:清掉本页 localStorage 再载入 */
|
|
|
|
|
|
await goto(PAGE);
|
|
|
|
|
|
await evaluate(`localStorage.removeItem("dcu_motor_ui_v2"); localStorage.removeItem("dcu_motor_transport"); "ok"`);
|
|
|
|
|
|
await goto(PAGE);
|
|
|
|
|
|
|
|
|
|
|
|
out.push("\n[A] 首次进入(无历史数据)");
|
|
|
|
|
|
let s = await evaluate(`JSON.stringify({tp: motorDbg.state.tpId, input: document.querySelector("#refreshRate").value, eff: motorDbg.state.refreshIntervalMs, map: motorDbg.state.refreshByTp})`);
|
|
|
|
|
|
let o = JSON.parse(s);
|
|
|
|
|
|
check("默认链路 wifi", o.tp === "wifi", s);
|
|
|
|
|
|
check("输入框显示 250", o.input === "250", s);
|
|
|
|
|
|
check("生效值 250", o.eff === 250, s);
|
|
|
|
|
|
check("两份都在(wifi250/ecat50)", o.map.wifi === 250 && o.map.ecat === 50, s);
|
|
|
|
|
|
|
|
|
|
|
|
out.push("\n[B] 切到 EtherCAT → 自动换成 50ms");
|
|
|
|
|
|
await evaluate(`(() => { const s=document.querySelector("#transportSelect"); s.value="ecat"; s.dispatchEvent(new Event("change")); return "ok"; })()`);
|
|
|
|
|
|
await sleep(1200);
|
|
|
|
|
|
s = await evaluate(`JSON.stringify({tp: motorDbg.state.tpId, input: document.querySelector("#refreshRate").value, eff: motorDbg.state.refreshIntervalMs})`);
|
|
|
|
|
|
o = JSON.parse(s);
|
|
|
|
|
|
check("链路已切到 ecat", o.tp === "ecat", s);
|
|
|
|
|
|
check("输入框自动变 50(不是留在 250)", o.input === "50", s);
|
|
|
|
|
|
check("生效值 50", o.eff === 50, s);
|
|
|
|
|
|
|
|
|
|
|
|
out.push("\n[C] 在 ECAT 下改成 120ms,只改这一份");
|
|
|
|
|
|
await evaluate(`(() => { const i=document.querySelector("#refreshRate"); i.value="120"; i.dispatchEvent(new Event("change")); return "ok"; })()`);
|
|
|
|
|
|
await sleep(600);
|
|
|
|
|
|
s = await evaluate(`JSON.stringify({eff: motorDbg.state.refreshIntervalMs, map: motorDbg.state.refreshByTp, ls: JSON.parse(localStorage.getItem("dcu_motor_ui_v2")||"{}").refreshByTp})`);
|
|
|
|
|
|
o = JSON.parse(s);
|
|
|
|
|
|
check("ecat 生效 120", o.eff === 120, s);
|
|
|
|
|
|
check("wifi 那一份仍是 250(没被污染)", o.map.wifi === 250, s);
|
|
|
|
|
|
check("已写入 localStorage", o.ls && o.ls.ecat === 120 && o.ls.wifi === 250, s);
|
|
|
|
|
|
|
|
|
|
|
|
out.push("\n[D] 切回 WiFi → 自动换回 250ms");
|
|
|
|
|
|
await evaluate(`(() => { const s=document.querySelector("#transportSelect"); s.value="wifi"; s.dispatchEvent(new Event("change")); return "ok"; })()`);
|
|
|
|
|
|
await sleep(1200);
|
|
|
|
|
|
s = await evaluate(`JSON.stringify({tp: motorDbg.state.tpId, input: document.querySelector("#refreshRate").value, eff: motorDbg.state.refreshIntervalMs, map: motorDbg.state.refreshByTp})`);
|
|
|
|
|
|
o = JSON.parse(s);
|
|
|
|
|
|
check("链路回到 wifi", o.tp === "wifi", s);
|
|
|
|
|
|
check("输入框换回 250(没带着 ECAT 的 120)", o.input === "250", s);
|
|
|
|
|
|
check("生效值 250", o.eff === 250, s);
|
|
|
|
|
|
check("ecat 那一份仍记着 120", o.map.ecat === 120, s);
|
|
|
|
|
|
|
|
|
|
|
|
out.push("\n[E] 在 WiFi 下改成 800ms,再刷新页面");
|
|
|
|
|
|
await evaluate(`(() => { const i=document.querySelector("#refreshRate"); i.value="800"; i.dispatchEvent(new Event("change")); return "ok"; })()`);
|
|
|
|
|
|
await sleep(600);
|
|
|
|
|
|
await goto(PAGE);
|
|
|
|
|
|
s = await evaluate(`JSON.stringify({tp: motorDbg.state.tpId, input: document.querySelector("#refreshRate").value, eff: motorDbg.state.refreshIntervalMs, map: motorDbg.state.refreshByTp})`);
|
|
|
|
|
|
o = JSON.parse(s);
|
|
|
|
|
|
check("刷新后仍是 wifi 链路", o.tp === "wifi", s);
|
|
|
|
|
|
check("刷新后输入框恢复 800", o.input === "800", s);
|
|
|
|
|
|
check("刷新后生效值 800", o.eff === 800, s);
|
|
|
|
|
|
check("刷新后 ecat 那一份仍是 120", o.map.ecat === 120, s);
|
|
|
|
|
|
|
|
|
|
|
|
out.push("\n[F] 刷新后切到 ECAT,仍取回自己的 120");
|
|
|
|
|
|
await evaluate(`(() => { const s=document.querySelector("#transportSelect"); s.value="ecat"; s.dispatchEvent(new Event("change")); return "ok"; })()`);
|
|
|
|
|
|
await sleep(1200);
|
|
|
|
|
|
s = await evaluate(`JSON.stringify({input: document.querySelector("#refreshRate").value, eff: motorDbg.state.refreshIntervalMs})`);
|
|
|
|
|
|
o = JSON.parse(s);
|
|
|
|
|
|
check("输入框 120(跨刷新持久化 + 切换自动换都成立)", o.input === "120", s);
|
|
|
|
|
|
check("生效值 120", o.eff === 120, s);
|
|
|
|
|
|
|
|
|
|
|
|
out.push("\n[G] 旧数据迁移(模拟老版本只存单个 refreshRate)");
|
|
|
|
|
|
await evaluate(`(() => { localStorage.setItem("dcu_motor_ui_v2", JSON.stringify({refreshRate:"400"})); localStorage.setItem("dcu_motor_transport","wifi"); return "ok"; })()`);
|
|
|
|
|
|
await goto(PAGE);
|
|
|
|
|
|
s = await evaluate(`JSON.stringify({input: document.querySelector("#refreshRate").value, map: motorDbg.state.refreshByTp})`);
|
|
|
|
|
|
o = JSON.parse(s);
|
|
|
|
|
|
check("旧单值 400 迁到 wifi 并生效", o.input === "400", s);
|
|
|
|
|
|
check("ecat 不继承旧值,仍是默认 50", o.map.ecat === 50, s);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(out.join("\n"));
|
|
|
|
|
|
console.log("\n" + "=".repeat(56));
|
|
|
|
|
|
console.log(`通过 ${pass} 项,失败 ${fail} 项`);
|
|
|
|
|
|
sock.close();
|
|
|
|
|
|
process.exit(fail === 0 ? 0 : 1);
|