Summary
Run Tauri and Electron frontend tests in plain Chrome against a dev server, with native IPC intercepted at the JS boundary. No binary, no native driver, no display server — fast feedback loop for UI-focused tests on CI.
Today every test requires a built native binary, a platform-specific driver, and a real OS window. Many tests only exercise the web UI and mock the backend anyway. Browser-only mode collapses the inner/outer mock boundary into a single window context and eliminates the entire driver/binary chain.
Phase 1 — Tauri
@wdio/native-spy
TauriAdapter.buildBrowserIpcInjectionScript() — one-shot IIFE that sets up window.__wdio_spy__, window.__wdio_mocks__, and patches window.__TAURI_INTERNALS__.invoke to route through __wdio_mocks__[cmd] or throw 'unmocked Tauri command in browser mode: <cmd>'
IpcInterceptor interface gains buildBrowserIpcInjectionScript()
@wdio/tauri-service — types
TauriServiceOptions.mode?: 'native' | 'browser'
TauriServiceOptions.devServerUrl?: string (required when mode === 'browser')
Launcher — short-circuit driver/binary setup
onPrepare early-returns in browser mode: sets browserName: 'chrome', removes tauri:options, skips ensureTauriDriver, ensureMsEdgeDriver, embedded server spawn, version detection
onWorkerStart / onWorkerEnd skip per-worker driver lifecycle
Worker — inject IPC layer
before() branches on mode === 'browser': navigates to devServerUrl, executes injection script, exposes browser.tauri with the single-context mock factory
browser.tauri.execute() throws a clear error; switchWindow/listWindows throw
browser.url() is patched post-init to re-inject the IPC script after every navigation (page reload wipes window state)
Single-context mock factory
createMock in browser mode skips the outer/inner dual-mock path; all call data lives in window.__wdio_mocks__[cmd] and is read back via parseCallData over a browser.execute round-trip
mock.update() remains callable as a no-op for users migrating from native mode
Phase 2 — Electron (follow-up PR)
Mirrors Phase 1 with:
ElectronAdapter.buildBrowserIpcInjectionScript() wraps window.electron.ipcRenderer.invoke (and .send/.sendSync) — the contextBridge-exposed surface
ElectronServiceOptions.mode / devServerUrl additions
- Launcher skips
resolveAppPaths, getAppBuildInfo, Chromium version detection, --inspect allocation, AppArmor patches
- Worker skips
initCdpBridge; browser.electron.execute() throws
contextBridge-only support (no nodeIntegration: true). Documented with a link to Electron's security guidance.
Design decisions
| Decision |
Rationale |
| Dev server is user-managed |
Matches tauri-playwright; simplest contract |
execute() throws |
No silent semantic change between modes |
| Tauri ships first |
Proven end-to-end slice; Electron follows the same template |
IPC re-injection on browser.url() |
Page reload wipes window state; patching url keeps the infrastructure alive |
| Multiremote: patch each instance |
Root browser url() and per-instance url() both need re-injection |
Out of scope (v1)
- Auto-start of dev server (
devServerCommand option deferred)
- Multi-window (
switchWindow throws with a clear message)
- Tauri events (
listen/emit)
- Non-
contextBridge Electron preloads
Acceptance criteria
Functional
- Browser-mode E2E suites pass on Linux CI with no display server
- All existing native-mode unit, integration, package, and E2E suites pass unchanged
- No
tauri-driver, msedgedriver, or Electron app process appears in ps
- Misconfiguration surfaces a clear, actionable error (missing
devServerUrl, dev server unreachable, mode: 'browser' + non-chrome browserName)
Coverage — 80%+ on all touched packages; each test tier owns only the bugs it can catch:
- Unit: logic, error paths, every branch of
mode === 'browser'
- Integration: launcher→worker handshake, capability flow, no-driver assertion
- Package: ESM/CJS import surface via
wdio.browser.conf.ts added to existing fixtures
- E2E: happy-path real-Chrome +
ps-based process-presence regression
Performance
- Browser-mode E2E suite (~5 specs) completes in under 30 seconds on CI Linux (warm)
- Per-test browser-mode runtime is at least 5× faster than equivalent native-mode on the same hardware
Related
Summary
Run Tauri and Electron frontend tests in plain Chrome against a dev server, with native IPC intercepted at the JS boundary. No binary, no native driver, no display server — fast feedback loop for UI-focused tests on CI.
Today every test requires a built native binary, a platform-specific driver, and a real OS window. Many tests only exercise the web UI and mock the backend anyway. Browser-only mode collapses the inner/outer mock boundary into a single window context and eliminates the entire driver/binary chain.
Phase 1 — Tauri
@wdio/native-spyTauriAdapter.buildBrowserIpcInjectionScript()— one-shot IIFE that sets upwindow.__wdio_spy__,window.__wdio_mocks__, and patcheswindow.__TAURI_INTERNALS__.invoketo route through__wdio_mocks__[cmd]or throw'unmocked Tauri command in browser mode: <cmd>'IpcInterceptorinterface gainsbuildBrowserIpcInjectionScript()@wdio/tauri-service— typesTauriServiceOptions.mode?: 'native' | 'browser'TauriServiceOptions.devServerUrl?: string(required whenmode === 'browser')Launcher — short-circuit driver/binary setup
onPrepareearly-returns in browser mode: setsbrowserName: 'chrome', removestauri:options, skipsensureTauriDriver,ensureMsEdgeDriver, embedded server spawn, version detectiononWorkerStart/onWorkerEndskip per-worker driver lifecycleWorker — inject IPC layer
before()branches onmode === 'browser': navigates todevServerUrl, executes injection script, exposesbrowser.tauriwith the single-context mock factorybrowser.tauri.execute()throws a clear error;switchWindow/listWindowsthrowbrowser.url()is patched post-init to re-inject the IPC script after every navigation (page reload wipeswindowstate)Single-context mock factory
createMockin browser mode skips the outer/inner dual-mock path; all call data lives inwindow.__wdio_mocks__[cmd]and is read back viaparseCallDataover abrowser.executeround-tripmock.update()remains callable as a no-op for users migrating from native modePhase 2 — Electron (follow-up PR)
Mirrors Phase 1 with:
ElectronAdapter.buildBrowserIpcInjectionScript()wrapswindow.electron.ipcRenderer.invoke(and.send/.sendSync) — thecontextBridge-exposed surfaceElectronServiceOptions.mode/devServerUrladditionsresolveAppPaths,getAppBuildInfo, Chromium version detection,--inspectallocation, AppArmor patchesinitCdpBridge;browser.electron.execute()throwscontextBridge-only support (nonodeIntegration: true). Documented with a link to Electron's security guidance.Design decisions
execute()throwsbrowser.url()windowstate; patchingurlkeeps the infrastructure aliveurl()and per-instanceurl()both need re-injectionOut of scope (v1)
devServerCommandoption deferred)switchWindowthrows with a clear message)listen/emit)contextBridgeElectron preloadsAcceptance criteria
Functional
tauri-driver,msedgedriver, or Electron app process appears inpsdevServerUrl, dev server unreachable,mode: 'browser'+ non-chromebrowserName)Coverage — 80%+ on all touched packages; each test tier owns only the bugs it can catch:
mode === 'browser'wdio.browser.conf.tsadded to existing fixturesps-based process-presence regressionPerformance
Related
feat/ipc-mock—IpcInterceptorframework in@wdio/native-spy(already landed / in PR)invoke()calls (Vite plugin approach)