From 28bd0d990abebd3a96cdac4937342bf29dfe32d1 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 11 Oct 2026 08:30:09 +0800 Subject: [PATCH] test(electron): verify aliased APIs against a real IPC round trip Extend the existing real Electron public-tool fixture with assignment aliases for BrowserWindow, contextBridge, ipcMain, and ipcRenderer. Assert exact static recovery counts and observe the handler invocation after a real click. Verified the public CLI/stdio MCP fixture, IPC invocation, cancellation, and owned-process cleanup against checksum-verified Electron 43.3.0 on macOS ARM64. Build and check:fast pass. The separate readiness reload/crash scenario still fails on a destroyed execution context; this commit does not claim the full verify:electron lane passes. --- scripts/verify/electron/tool-contracts.mjs | 64 ++++++++++++++++++++-- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/scripts/verify/electron/tool-contracts.mjs b/scripts/verify/electron/tool-contracts.mjs index 0de31cb7b..5269a285a 100644 --- a/scripts/verify/electron/tool-contracts.mjs +++ b/scripts/verify/electron/tool-contracts.mjs @@ -26,22 +26,49 @@ export async function verifyElectronToolContracts({ await mkdtemp(join(tmpdir(), "rea-electron-e2e-")), ); const main = join(root, "main.cjs"); + const preload = join(root, "preload.cjs"); const ready = join(root, "ready"); const entrypoint = join(repositoryRoot, "scripts", "rea.mjs"); const runId = randomUUID(); await writeFile( main, - `const {app,BrowserWindow}=require("electron"); -app.whenReady().then(()=>{const window=new BrowserWindow(); -window.loadFile("renderer.html");require("node:fs").writeFileSync(${JSON.stringify(ready)},"ready");});`, + `const electron = require("electron"); +const path = require("node:path"); +const { app, BrowserWindow, ipcMain } = electron; +const Window = BrowserWindow; +const mainIpc = ipcMain; +app.whenReady().then(() => { + const window = new Window({ + webPreferences: { preload: path.join(__dirname, "preload.cjs") }, + }); + window.loadFile("renderer.html"); + require("node:fs").writeFileSync(${JSON.stringify(ready)}, "ready"); +}); +mainIpc.handle("fixture:echo", (_event, value) => ({ value }));`, ); await writeFile( join(root, "renderer.html"), - '', + ` + + +`, + ); + await writeFile( + preload, + `const electron = require("electron"); +const { contextBridge, ipcRenderer } = electron; +const bridge = contextBridge; +const rendererIpc = ipcRenderer; +bridge.exposeInMainWorld("fixture", { + echo: (value) => rendererIpc.invoke("fixture:echo", value), +});`, ); await writeFile( join(root, "renderer.js"), - 'document.querySelector("#run").addEventListener("click",()=>console.log("fixture-click"));', + `document.querySelector("#run").addEventListener("click", async () => { + const result = await window.fixture.echo("renderer"); + document.querySelector("#result").textContent = result.value; +});`, ); const transport = new StdioClientTransport({ command: process.execPath, @@ -135,6 +162,24 @@ window.loadFile("renderer.html");require("node:fs").writeFileSync(${JSON.stringi const staticAnalysis = await call("analyze_javascript_application", { input_path: root, }); + assert.equal(staticAnalysis.normalized_result.summary.browser_windows, 1); + assert.equal( + staticAnalysis.normalized_result.summary.preload_entrypoints, + 1, + ); + assert.equal( + staticAnalysis.normalized_result.summary.context_bridge_apis, + 1, + ); + assert.equal(staticAnalysis.normalized_result.summary.ipc.main_handlers, 1); + assert.equal( + staticAnalysis.normalized_result.summary.ipc.renderer_transmissions, + 1, + ); + assert.equal( + staticAnalysis.normalized_result.summary.ipc.paired_renderer_transmissions, + 1, + ); const reconciled = await call("reconcile_javascript_runtime", { static_layers: [ { @@ -169,7 +214,7 @@ window.loadFile("renderer.html");require("node:fs").writeFileSync(${JSON.stringi executable_path: executable, application_path: main, application_root: root, - actions: [{ step_id: "settle", kind: "wait", duration_ms: 50 }], + actions: [{ step_id: "ipc", kind: "click", selector: "#run" }], }; const active = await client.callTool( { name: "capture_electron_scenario", arguments: input }, @@ -192,6 +237,13 @@ window.loadFile("renderer.html");require("node:fs").writeFileSync(${JSON.stringi assert.equal(capture.application.cleanup, "terminated-owned-process"); } assert.equal(capture.actions[0].status, "completed"); + assert.ok( + capture.ipc.events.some( + ({ kind, channel }) => + kind === "main-handler-invocation" && channel === "fixture:echo", + ), + JSON.stringify(capture.ipc), + ); assert.equal( capture.coverage.status, "partial_attach",