[eric] electron+e2e: close the init-script race - main reads OPENSWARM_E2E env on startup and appends an --openswarm-e2e chromium switch, preload reads the switch BEFORE any page script parses and contextBridge-exposes __OPENSWARM_E2E__=true so the production store-on-window gate fires deterministically rather than racing addInitScript against bundle parse

This commit is contained in:
Eric
2026-05-28 09:38:29 -07:00
parent d8c4aeb137
commit 6353bbddda
3 changed files with 31 additions and 6 deletions
+11 -6
View File
@@ -79,12 +79,17 @@ export function hasAnyProviderKey(): boolean {
export async function launchApp(): Promise<ElectronApplication> {
seedTestUserIfClean();
const app = await electron.launch({ executablePath: packagedAppPath(), args: [] });
// The frontend bundle gates `__OPENSWARM_STORE__` exposure on
// `__OPENSWARM_E2E__` being truthy at module-load time. Adding an init
// script BEFORE the main page navigates guarantees the flag is set before
// bundle.js parses, so specs that read the Redux store directly work
// against the production build.
// OPENSWARM_E2E=1 is read by electron/main.js BEFORE the renderer launches; it
// appends a Chromium switch the preload reads to set window.__OPENSWARM_E2E__
// before bundle.js parses, so the production store-on-window gate fires
// deterministically (no addInitScript race).
const app = await electron.launch({
executablePath: packagedAppPath(),
args: [],
env: { ...process.env, OPENSWARM_E2E: '1' },
});
// Belt-and-braces: addInitScript ALSO sets the flag in case a future Electron
// changes the cmdline propagation. If either path works, the spec succeeds.
try { await app.context().addInitScript({ content: '(window).__OPENSWARM_E2E__ = true;' }); } catch { /* best effort */ }
return app;
}
+9
View File
@@ -1,5 +1,14 @@
const { app, components, BrowserWindow, ipcMain, shell, session, dialog, crashReporter } = require('electron');
// E2E flag: when OPENSWARM_E2E=1, append a Chromium command-line switch the
// renderer reads at startup to set window.__OPENSWARM_E2E__ = true BEFORE any
// page script parses, so the production-build store-on-window gate fires
// deterministically. Normal user launches never set the env var so this is a
// no-op for them; only Playwright's electron.launch({env}) flips it on.
if (process.env.OPENSWARM_E2E === '1') {
try { app.commandLine.appendSwitch('openswarm-e2e', '1'); } catch {}
}
// Local-only crash reporter. Captures native renderer crashes that escape JS-level error handlers and don't otherwise surface in Crashpad. uploadToServer=false keeps minidumps on disk under %APPDATA%/OpenSwarm/Crashpad so we can inspect them post-mortem without sending anywhere.
try {
crashReporter.start({
+11
View File
@@ -3,6 +3,17 @@ const { contextBridge, ipcRenderer } = require('electron');
// eslint-disable-next-line no-console
console.log('[diag][preload] start, ua=', navigator.userAgent);
// E2E gate: set the renderer flag BEFORE any page script parses so the
// production-build store-on-window expose fires deterministically when
// Playwright launches with OPENSWARM_E2E=1. Read from the Chromium switch
// the main process appended; no-op for normal user launches.
try {
const args = (typeof process !== 'undefined' && process.argv) ? process.argv : [];
if (args.some((a) => /--openswarm-e2e(=1)?$/.test(a))) {
contextBridge.exposeInMainWorld('__OPENSWARM_E2E__', true);
}
} catch (e) { console.log('[diag][preload] e2e-flag setup failed:', e && e.message); }
// Synchronous exposure. The previous async IIFE (await ipcRenderer.invoke) raced React mount: any code reading window.openswarm during the gap (BrowserCard's Electron-detection falling back to iframe mode, AgentChat's auth-token call throwing) saw undefined. sendSync blocks the renderer for one IPC round-trip during preload before any user-visible paint, so window.openswarm is guaranteed to exist before the first frontend bundle evaluates.
const port = ipcRenderer.sendSync('get-backend-port-sync');
const webviewPreloadPath = ipcRenderer.sendSync('get-webview-preload-path-sync');