/** * 浏览器实测(第二轮):切链路后**实际节拍**是否真的跟着变。 * 前一轮只验了输入框/state;这里数真实的 fetchMotorState 调用频率 —— * 若 applyTransportRefresh 忘了重排定时器,state 是新值但节拍还是旧的。 */ const CDP_BASE = "http://127.0.0.1:9333"; const list = await (await fetch(CDP_BASE + "/json/list")).json(); const page = list.find((t) => t.type === "page"); const sock = new WebSocket(page.webSocketDebuggerUrl); 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); } }; const send = (method, params = {}) => { const myId = ++id; return new Promise((res) => { pending.set(myId, res); sock.send(JSON.stringify({ id: myId, method, params })); }); }; async function ev(expr) { const r = await send("Runtime.evaluate", { expression: expr, awaitPromise: true, returnByValue: true }); 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 = []; const check = (n, c, d) => { if (c) { pass++; out.push(" \u2713 " + n); } else { fail++; out.push(" \u2717 " + n + (d ? " \u2014 " + d : "")); } }; await send("Runtime.enable"); await send("Page.enable"); /* 页面服务端口可传参:node scripts/xxx.mjs [port](默认 8080) */ const PORT = Number(process.argv[2] || 8080); const PAGE = `http://127.0.0.1:${PORT}/pages/motor.html`; /* 干净起点,直接进 ECAT 链路(本机有 ecat 服务,能真连上跑起轮询) */ await send("Page.navigate", { url: PAGE }); await sleep(2000); await ev(`localStorage.setItem("dcu_motor_transport","ecat"); localStorage.removeItem("dcu_motor_ui_v2"); "ok"`); await send("Page.navigate", { url: PAGE }); await sleep(3000); /* 装一个计数探针:统计 /ecat/api/motor/state 的实际请求次数 */ await ev(`(() => { window.__cnt = 0; if (!window.__origFetch) window.__origFetch = window.fetch; window.fetch = function (u, i) { if (String(u).includes("motor/state")) window.__cnt++; return window.__origFetch.apply(this, arguments); }; return "ok"; })()`); const state0 = await ev(`JSON.stringify({tp: motorDbg.state.tpId, conn: motorDbg.state.connected, eff: motorDbg.state.refreshIntervalMs})`); out.push("\n[前置] 当前链路状态: " + state0); const st0 = JSON.parse(state0); check("已在 ecat 链路", st0.tp === "ecat", state0); check("已连接(否则数不到轮询)", st0.conn === true, state0); check("生效间隔 50ms", st0.eff === 50, state0); async function measure(seconds) { await ev(`window.__cnt = 0; "ok"`); await sleep(seconds * 1000); return await ev(`window.__cnt`); } out.push("\n[A] ECAT 默认 50ms 的实际节拍"); let n = await measure(3); let hz = n / 3; out.push(` 3 秒内 ${n} 次 ≈ ${hz.toFixed(1)} Hz(理论 20 Hz)`); check("接近 20Hz(>12Hz,留足浏览器调度余量)", hz > 12, `实测 ${hz.toFixed(1)}Hz`); out.push("\n[B] 改成 500ms → 节拍应明显变慢"); await ev(`(() => { const i=document.querySelector("#refreshRate"); i.value="500"; i.dispatchEvent(new Event("change")); return "ok"; })()`); await sleep(800); n = await measure(4); hz = n / 4; out.push(` 4 秒内 ${n} 次 ≈ ${hz.toFixed(1)} Hz(理论 2 Hz)`); check("落到 2Hz 附近(0.8~4Hz)", hz > 0.8 && hz < 4, `实测 ${hz.toFixed(1)}Hz`); out.push("\n[C] 切到 WiFi 再切回 ECAT:节拍应自动回到 ecat 那一份(500ms)"); await ev(`(() => { const s=document.querySelector("#transportSelect"); s.value="wifi"; s.dispatchEvent(new Event("change")); return "ok"; })()`); await sleep(1500); const wifiState = await ev(`JSON.stringify({input: document.querySelector("#refreshRate").value, eff: motorDbg.state.refreshIntervalMs})`); check("切 WiFi 后生效值变 250(不是带着 ECAT 的 500)", JSON.parse(wifiState).eff === 250, wifiState); await ev(`(() => { const s=document.querySelector("#transportSelect"); s.value="ecat"; s.dispatchEvent(new Event("change")); return "ok"; })()`); await sleep(2500); const backState = await ev(`JSON.stringify({input: document.querySelector("#refreshRate").value, eff: motorDbg.state.refreshIntervalMs, conn: motorDbg.state.connected})`); check("切回 ECAT 生效值回到 500(自己那一份)", JSON.parse(backState).eff === 500, backState); n = await measure(4); hz = n / 4; out.push(` 4 秒内 ${n} 次 ≈ ${hz.toFixed(1)} Hz(理论 2 Hz)`); check("实际节拍也回到 2Hz 附近(不是 20Hz 的旧节拍)", hz > 0.8 && hz < 4, `实测 ${hz.toFixed(1)}Hz`); out.push("\n[D] 改回 50ms,节拍应立刻提上去(验证重排真的生效)"); await ev(`(() => { const i=document.querySelector("#refreshRate"); i.value="50"; i.dispatchEvent(new Event("change")); return "ok"; })()`); await sleep(600); n = await measure(3); hz = n / 3; out.push(` 3 秒内 ${n} 次 ≈ ${hz.toFixed(1)} Hz(理论 20 Hz)`); check("回到 20Hz 附近(>12Hz)", hz > 12, `实测 ${hz.toFixed(1)}Hz`); console.log(out.join("\n")); console.log("\n" + "=".repeat(56)); console.log(`通过 ${pass} 项,失败 ${fail} 项`); sock.close(); process.exit(fail === 0 ? 0 : 1);