Mock Electron APIs (shell, clipboard, app…)
Native dialogs have a typed helper (mockDialog()), but plenty of Electron behavior you want to keep out of a test run doesn't: opening the user's browser (shell.openExternal), writing the system clipboard (clipboard.writeText), resolving OS paths (app.getPath), and so on. browser.electron.mock() replaces any electron.<api>.<fn> main-process method with a scripted return and a call recorder, so you can drive the real UI and assert what it asked the OS to do — without the OS actually doing it.
Needs main-process access (electron: { mainProcess: true }), like executeMain and mockDialog.
Stop a "share" button from launching a browser
Application code — a normal renderer → preload → IPC → shell.openExternal flow:
// main.js
const { ipcMain, shell } = require('electron');
ipcMain.handle('share:open', (_event, url) => shell.openExternal(url));// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('share', { open: (url) => ipcRenderer.invoke('share:open', url) });Test — mock the final shell.openExternal, click, and assert the URL it received:
import { Browser, By } from 'craftdriver';
import { expect, test } from 'vitest';
test('share button opens the right URL without launching a browser', async () => {
const browser = await Browser.launch({
electron: { appBinaryPath: process.env.APP_PATH!, mainProcess: true },
});
try {
// shell.openExternal resolves to true on success — script that.
const openExternal = await browser.electron.mock('shell', 'openExternal', true);
await browser.click(By.testId('share-btn'));
expect(await openExternal.getCalls()).toEqual([{ args: ['https://example.com/report/42'] }]);
await openExternal.restore();
} finally {
await browser.quit();
}
});Each recorded call is { args: [...] } with the (JSON-safe) arguments the app passed. The rest of the handle mirrors mockDialog: getCallCount(), clearCalls(), mockReturnValue(value) to re-script mid-test, and restore() (also restored on browser.quit()).
Assert what the app copied to the clipboard
const writeText = await browser.electron.mock('clipboard', 'writeText');
await browser.click(By.testId('copy-link-btn'));
expect(await writeText.getCalls()).toEqual([{ args: ['https://example.com/report/42'] }]);
await writeText.restore();Here no return value is scripted — clipboard.writeText returns nothing, so the mock returns undefined and you assert purely on the recorded arguments.
Notes
- The scripted value is returned as-is (not wrapped in a Promise), so it works for synchronous methods (
app.getName(),app.getPath()) andawaited asynchronous ones (shell.openExternal). Pass the already-resolved value for an async method. - Arguments and the return value must be JSON-serializable; a non-serializable argument (a
BrowserWindow, aBuffer) is recorded as a descriptive placeholder rather than crossing the process boundary. mock()targets object-namespace methods (shell,clipboard,app,dialog, …). Class-based APIs such asNotificationorMenuare not covered by it today — mock the app code that constructs them instead, or drive them through your own IPC surface.- Reach for the typed
mockDialog()for file, save, and message dialogs; it validates the result shape for you.