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",