Skip to content

Tauri Service: pre-load IPC mock injection to capture startup invoke() calls in browser mode #259

@goosewobbler

Description

@goosewobbler

Problem

In browser mode (devServerUrl set), the IPC interceptor is injected after browser.url() resolves — i.e. after readyState === 'complete'. Any invoke() calls the app makes during module initialisation, DOMContentLoaded, or onload handlers run against the real (unpatched) __TAURI_INTERNALS__ and are invisible to mocks.

// service.ts – current flow
await browser.url(this.devServerUrl);   // waits for readyState=complete
await browser.execute(injectionScript); // too late for startup invoke() calls

Affected scope

Mode Affected?
Tauri browser mode (devServerUrl) ✅ Yes – confirmed
Tauri native mode (real binary) ⚠️ Potentially – mock registration happens after before() hook; any invoke() during app launch before the WebDriver session is fully established may also be missed
Electron ❌ No – Electron's IPC is intercepted at the Node.js layer via ipcMain, not injected into the renderer

Proposed solutions

1. Vite plugin (browser mode only)

Ship an optional @wdio/tauri-vite-plugin that imports the injection script as a top-level module in the dev build entry point. This guarantees the mock infrastructure is available before any app code runs.

// vite.config.ts
import { wdioTauriPlugin } from '@wdio/tauri-vite-plugin';
export default { plugins: [wdioTauriPlugin()] };

2. WebDriver BiDi script.addPreloadScript (browser mode)

WebDriver BiDi (supported in ChromeDriver 115+, Firefox 128+) provides script.addPreloadScript which runs before any page script. This would work without a Vite plugin and would not require @wdio/devtools-service.

await browser.scriptAddPreloadScript({ functionDeclaration: injectionScript });

3. Investigate Tauri native mode

Audit whether the native-mode mock lifecycle (registered in the before() hook) can miss startup invoke() calls when the app is already running before the WebDriver session attaches.

Current workaround

Structure tests so that all mocks are created before the first navigation:

before(async () => {
  await browser.url('/');          // navigate first
  const greetMock = await tauri.mock('greet');  // then mock
  // startup invoke() calls already happened — this mock only captures future calls
});

Or defer navigation until after mock setup:

before(async () => {
  const greetMock = await tauri.mock('greet');  // mock first
  await browser.url('/');                        // then navigate
  // still too late — inject is post-load
});

Neither workaround is reliable for true startup calls. The Vite plugin approach is the recommended interim solution.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions