Testing Tauri Applications
This skill covers testing strategies for Tauri v2 applications: unit testing with mocks, end-to-end testing with WebDriver, and CI integration.
Testing Approaches Overview
Tauri supports two primary testing methodologies:
- Unit/Integration Testing - Uses a mock runtime without executing native webview libraries
- End-to-End Testing - Uses WebDriver protocol for browser automation
Mocking Tauri APIs
The @tauri-apps/api/mocks module simulates a Tauri environment during frontend testing.
Install Mock Dependencies
bash
1npm install -D vitest @tauri-apps/api
Mock IPC Commands
javascript
1import { mockIPC, clearMocks } from '@tauri-apps/api/mocks';
2import { invoke } from '@tauri-apps/api/core';
3import { vi, describe, it, expect, afterEach } from 'vitest';
4
5afterEach(() => {
6 clearMocks();
7});
8
9describe('Tauri Commands', () => {
10 it('should mock the add command', async () => {
11 mockIPC((cmd, args) => {
12 if (cmd === 'add') {
13 return (args.a as number) + (args.b as number);
14 }
15 });
16
17 const result = await invoke('add', { a: 12, b: 15 });
18 expect(result).toBe(27);
19 });
20
21 it('should verify invoke was called', async () => {
22 mockIPC((cmd) => {
23 if (cmd === 'greet') return 'Hello!';
24 });
25
26 const spy = vi.spyOn(window.__TAURI_INTERNALS__, 'invoke');
27 await invoke('greet', { name: 'World' });
28 expect(spy).toHaveBeenCalled();
29 });
30});
Mock Sidecar and Shell Commands
javascript
1import { mockIPC } from '@tauri-apps/api/mocks';
2
3mockIPC(async (cmd, args) => {
4 if (args.message.cmd === 'execute') {
5 const eventCallbackId = `_${args.message.onEventFn}`;
6 const eventEmitter = window[eventCallbackId];
7 eventEmitter({ event: 'Stdout', payload: 'process output data' });
8 eventEmitter({ event: 'Terminated', payload: { code: 0 } });
9 }
10});
Mock Events (v2.7.0+)
javascript
1import { mockIPC } from '@tauri-apps/api/mocks';
2import { emit, listen } from '@tauri-apps/api/event';
3
4mockIPC(() => {}, { shouldMockEvents: true });
5
6const eventHandler = vi.fn();
7await listen('test-event', eventHandler);
8await emit('test-event', { foo: 'bar' });
9expect(eventHandler).toHaveBeenCalled();
Mock Windows
javascript
1import { mockWindows } from '@tauri-apps/api/mocks';
2import { getCurrent, getAll } from '@tauri-apps/api/webviewWindow';
3
4mockWindows('main', 'second', 'third');
5
6// First parameter is the "current" window
7expect(getCurrent()).toHaveProperty('label', 'main');
8expect(getAll().map((w) => w.label)).toEqual(['main', 'second', 'third']);
Vitest Configuration
javascript
1// vitest.config.js
2import { defineConfig } from 'vitest/config';
3
4export default defineConfig({
5 test: {
6 environment: 'jsdom',
7 setupFiles: ['./test/setup.js'],
8 },
9});
10
11// test/setup.js
12window.__TAURI_INTERNALS__ = {
13 invoke: vi.fn(),
14 transformCallback: vi.fn(),
15};
WebDriver End-to-End Testing
WebDriver testing uses tauri-driver to automate Tauri applications.
| Platform | Support | Notes |
|---|
| Windows | Full | Requires Microsoft Edge Driver |
| Linux | Full | Requires WebKitWebDriver |
| macOS | None | WKWebView lacks WebDriver tooling |
Install tauri-driver
bash
1cargo install tauri-driver --locked
bash
1# Linux (Debian/Ubuntu)
2sudo apt install webkit2gtk-driver xvfb
3which WebKitWebDriver # Verify installation
4
5# Windows (PowerShell)
6cargo install --git https://github.com/chippers/msedgedriver-tool
7& "$HOME/.cargo/bin/msedgedriver-tool.exe"
WebdriverIO Setup
Project Structure
my-tauri-app/
├── src-tauri/
├── src/
└── e2e-tests/
├── package.json
├── wdio.conf.js
└── specs/
└── app.spec.js
Package Configuration
json
1{
2 "name": "tauri-e2e-tests",
3 "version": "1.0.0",
4 "type": "module",
5 "scripts": { "test": "wdio run wdio.conf.js" },
6 "dependencies": { "@wdio/cli": "^9.19.0" },
7 "devDependencies": {
8 "@wdio/local-runner": "^9.19.0",
9 "@wdio/mocha-framework": "^9.19.0",
10 "@wdio/spec-reporter": "^9.19.0"
11 }
12}
WebdriverIO Configuration
javascript
1// e2e-tests/wdio.conf.js
2import { spawn, spawnSync } from 'child_process';
3
4let tauriDriver;
5
6export const config = {
7 hostname: '127.0.0.1',
8 port: 4444,
9 specs: ['./specs/**/*.js'],
10 maxInstances: 1,
11 capabilities: [{
12 browserName: 'wry',
13 'tauri:options': {
14 application: '../src-tauri/target/debug/my-tauri-app',
15 },
16 }],
17 framework: 'mocha',
18 reporters: ['spec'],
19 mochaOpts: { ui: 'bdd', timeout: 60000 },
20
21 onPrepare: () => {
22 const result = spawnSync('cargo', ['build', '--manifest-path', '../src-tauri/Cargo.toml'], {
23 stdio: 'inherit',
24 });
25 if (result.status !== 0) throw new Error('Failed to build Tauri app');
26 },
27
28 beforeSession: () => {
29 tauriDriver = spawn('tauri-driver', [], { stdio: ['ignore', 'pipe', 'pipe'] });
30 return new Promise((resolve) => {
31 tauriDriver.stdout.on('data', (data) => {
32 if (data.toString().includes('listening')) resolve();
33 });
34 });
35 },
36
37 afterSession: () => tauriDriver?.kill(),
38};
WebdriverIO Test Example
javascript
1// e2e-tests/specs/app.spec.js
2describe('My Tauri App', () => {
3 it('should display the header', async () => {
4 const header = await $('body > h1');
5 expect(await header.getText()).toMatch(/^[hH]ello/);
6 });
7
8 it('should interact with a button', async () => {
9 const button = await $('#greet-button');
10 await button.click();
11 const output = await $('#greet-output');
12 await output.waitForExist({ timeout: 5000 });
13 expect(await output.getText()).toContain('Hello');
14 });
15});
Selenium Setup
Package Configuration
json
1{
2 "name": "tauri-selenium-tests",
3 "version": "1.0.0",
4 "scripts": { "test": "mocha" },
5 "dependencies": {
6 "chai": "^5.2.1",
7 "mocha": "^11.7.1",
8 "selenium-webdriver": "^4.34.0"
9 }
10}
Selenium Test Example
javascript
1// e2e-tests/test/test.js
2import { spawn, spawnSync } from 'child_process';
3import path from 'path';
4import { fileURLToPath } from 'url';
5import { Builder, By } from 'selenium-webdriver';
6import { expect } from 'chai';
7
8const __dirname = path.dirname(fileURLToPath(import.meta.url));
9let driver, tauriDriver;
10const application = path.resolve(__dirname, '../../src-tauri/target/debug/my-tauri-app');
11
12describe('Tauri App Tests', function () {
13 this.timeout(60000);
14
15 before(async function () {
16 spawnSync('cargo', ['build', '--manifest-path', '../../src-tauri/Cargo.toml'], {
17 cwd: __dirname, stdio: 'inherit',
18 });
19
20 tauriDriver = spawn('tauri-driver', [], { stdio: ['ignore', 'pipe', 'pipe'] });
21 await new Promise((resolve) => {
22 tauriDriver.stdout.on('data', (data) => {
23 if (data.toString().includes('listening')) resolve();
24 });
25 });
26
27 driver = await new Builder()
28 .usingServer('http://127.0.0.1:4444/')
29 .withCapabilities({ browserName: 'wry', 'tauri:options': { application } })
30 .build();
31 });
32
33 after(async function () {
34 await driver?.quit();
35 tauriDriver?.kill();
36 });
37
38 it('should display greeting', async function () {
39 const header = await driver.findElement(By.css('body > h1'));
40 expect(await header.getText()).to.match(/^[hH]ello/);
41 });
42
43 it('should click button and show output', async function () {
44 const button = await driver.findElement(By.id('greet-button'));
45 await button.click();
46 const output = await driver.findElement(By.id('greet-output'));
47 expect(await output.getText()).to.include('Hello');
48 });
49});
CI Integration with GitHub Actions
yaml
1# .github/workflows/e2e-tests.yml
2name: E2E Tests
3
4on:
5 push:
6 branches: [main]
7 pull_request:
8 branches: [main]
9
10jobs:
11 test:
12 strategy:
13 fail-fast: false
14 matrix:
15 os: [ubuntu-latest, windows-latest]
16 runs-on: ${{ matrix.os }}
17
18 steps:
19 - uses: actions/checkout@v4
20
21 - name: Install Linux dependencies
22 if: matrix.os == 'ubuntu-latest'
23 run: |
24 sudo apt-get update
25 sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential \
26 curl wget file libxdo-dev libssl-dev \
27 libayatana-appindicator3-dev librsvg2-dev \
28 webkit2gtk-driver xvfb
29
30 - uses: dtolnay/rust-action@stable
31 - run: cargo install tauri-driver --locked
32
33 - name: Setup Windows WebDriver
34 if: matrix.os == 'windows-latest'
35 shell: pwsh
36 run: |
37 cargo install --git https://github.com/chippers/msedgedriver-tool
38 & "$HOME/.cargo/bin/msedgedriver-tool.exe"
39
40 - uses: actions/setup-node@v4
41 with:
42 node-version: '20'
43
44 - run: npm install
45 - run: npm run build
46 - run: cargo build --manifest-path src-tauri/Cargo.toml
47
48 - name: Run E2E tests (Linux)
49 if: matrix.os == 'ubuntu-latest'
50 working-directory: e2e-tests
51 run: npm install && xvfb-run npm test
52
53 - name: Run E2E tests (Windows)
54 if: matrix.os == 'windows-latest'
55 working-directory: e2e-tests
56 run: npm install && npm test
Best Practices
Mock Testing
- Always call
clearMocks() in afterEach to prevent state leakage
- Use spies to verify IPC calls were made correctly
- Mock at the right level: IPC for commands, windows for multi-window logic
WebDriver Testing
- Use debug builds for faster iteration during development
- Set appropriate timeouts as Tauri apps may need time to initialize
- Wait for elements explicitly rather than using implicit waits
- Keep tests independent so each test works in isolation
CI Integration
- Use
xvfb-run on Linux for headless WebDriver testing
- Match Edge Driver version on Windows to avoid connection issues
- Build the app before running WebDriver tests
- Run unit tests before e2e tests to catch issues early
Troubleshooting
WebDriver Connection Timeout
- Windows: Verify Edge Driver version matches installed Edge
- Linux: Ensure
webkit2gtk-driver is installed
- Check
tauri-driver is running and listening on port 4444
Mock Not Working
- Import
@tauri-apps/api/mocks before the code under test
- Call
clearMocks() in afterEach to reset state
- Ensure
window.__TAURI_INTERNALS__ is properly mocked in setup
CI Failures
- Linux: Add
xvfb-run prefix to test commands
- Windows: Install Edge Driver via
msedgedriver-tool
- Increase timeout for slower CI runners
References