testing-tauri-apps

📁 beshkenadze/claude-code-tauri-skills 📅 14 days ago
1
总安装量
1
周安装量
#48162
全站排名
安装命令
npx skills add https://github.com/beshkenadze/claude-code-tauri-skills --skill testing-tauri-apps

Agent 安装分布

cursor 1
codex 1
claude-code 1
gemini-cli 1

Skill 文档

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:

  1. Unit/Integration Testing – Uses a mock runtime without executing native webview libraries
  2. 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

npm install -D vitest @tauri-apps/api

Mock IPC Commands

import { mockIPC, clearMocks } from '@tauri-apps/api/mocks';
import { invoke } from '@tauri-apps/api/core';
import { vi, describe, it, expect, afterEach } from 'vitest';

afterEach(() => {
  clearMocks();
});

describe('Tauri Commands', () => {
  it('should mock the add command', async () => {
    mockIPC((cmd, args) => {
      if (cmd === 'add') {
        return (args.a as number) + (args.b as number);
      }
    });

    const result = await invoke('add', { a: 12, b: 15 });
    expect(result).toBe(27);
  });

  it('should verify invoke was called', async () => {
    mockIPC((cmd) => {
      if (cmd === 'greet') return 'Hello!';
    });

    const spy = vi.spyOn(window.__TAURI_INTERNALS__, 'invoke');
    await invoke('greet', { name: 'World' });
    expect(spy).toHaveBeenCalled();
  });
});

Mock Sidecar and Shell Commands

import { mockIPC } from '@tauri-apps/api/mocks';

mockIPC(async (cmd, args) => {
  if (args.message.cmd === 'execute') {
    const eventCallbackId = `_${args.message.onEventFn}`;
    const eventEmitter = window[eventCallbackId];
    eventEmitter({ event: 'Stdout', payload: 'process output data' });
    eventEmitter({ event: 'Terminated', payload: { code: 0 } });
  }
});

Mock Events (v2.7.0+)

import { mockIPC } from '@tauri-apps/api/mocks';
import { emit, listen } from '@tauri-apps/api/event';

mockIPC(() => {}, { shouldMockEvents: true });

const eventHandler = vi.fn();
await listen('test-event', eventHandler);
await emit('test-event', { foo: 'bar' });
expect(eventHandler).toHaveBeenCalled();

Mock Windows

import { mockWindows } from '@tauri-apps/api/mocks';
import { getCurrent, getAll } from '@tauri-apps/api/webviewWindow';

mockWindows('main', 'second', 'third');

// First parameter is the "current" window
expect(getCurrent()).toHaveProperty('label', 'main');
expect(getAll().map((w) => w.label)).toEqual(['main', 'second', 'third']);

Vitest Configuration

// vitest.config.js
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    setupFiles: ['./test/setup.js'],
  },
});

// test/setup.js
window.__TAURI_INTERNALS__ = {
  invoke: vi.fn(),
  transformCallback: vi.fn(),
};

WebDriver End-to-End Testing

WebDriver testing uses tauri-driver to automate Tauri applications.

Platform Support

Platform Support Notes
Windows Full Requires Microsoft Edge Driver
Linux Full Requires WebKitWebDriver
macOS None WKWebView lacks WebDriver tooling

Install tauri-driver

cargo install tauri-driver --locked

Platform Dependencies

# Linux (Debian/Ubuntu)
sudo apt install webkit2gtk-driver xvfb
which WebKitWebDriver  # Verify installation

# Windows (PowerShell)
cargo install --git https://github.com/chippers/msedgedriver-tool
& "$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

{
  "name": "tauri-e2e-tests",
  "version": "1.0.0",
  "type": "module",
  "scripts": { "test": "wdio run wdio.conf.js" },
  "dependencies": { "@wdio/cli": "^9.19.0" },
  "devDependencies": {
    "@wdio/local-runner": "^9.19.0",
    "@wdio/mocha-framework": "^9.19.0",
    "@wdio/spec-reporter": "^9.19.0"
  }
}

WebdriverIO Configuration

// e2e-tests/wdio.conf.js
import { spawn, spawnSync } from 'child_process';

let tauriDriver;

export const config = {
  hostname: '127.0.0.1',
  port: 4444,
  specs: ['./specs/**/*.js'],
  maxInstances: 1,
  capabilities: [{
    browserName: 'wry',
    'tauri:options': {
      application: '../src-tauri/target/debug/my-tauri-app',
    },
  }],
  framework: 'mocha',
  reporters: ['spec'],
  mochaOpts: { ui: 'bdd', timeout: 60000 },

  onPrepare: () => {
    const result = spawnSync('cargo', ['build', '--manifest-path', '../src-tauri/Cargo.toml'], {
      stdio: 'inherit',
    });
    if (result.status !== 0) throw new Error('Failed to build Tauri app');
  },

  beforeSession: () => {
    tauriDriver = spawn('tauri-driver', [], { stdio: ['ignore', 'pipe', 'pipe'] });
    return new Promise((resolve) => {
      tauriDriver.stdout.on('data', (data) => {
        if (data.toString().includes('listening')) resolve();
      });
    });
  },

  afterSession: () => tauriDriver?.kill(),
};

WebdriverIO Test Example

// e2e-tests/specs/app.spec.js
describe('My Tauri App', () => {
  it('should display the header', async () => {
    const header = await $('body > h1');
    expect(await header.getText()).toMatch(/^[hH]ello/);
  });

  it('should interact with a button', async () => {
    const button = await $('#greet-button');
    await button.click();
    const output = await $('#greet-output');
    await output.waitForExist({ timeout: 5000 });
    expect(await output.getText()).toContain('Hello');
  });
});

Selenium Setup

Package Configuration

{
  "name": "tauri-selenium-tests",
  "version": "1.0.0",
  "scripts": { "test": "mocha" },
  "dependencies": {
    "chai": "^5.2.1",
    "mocha": "^11.7.1",
    "selenium-webdriver": "^4.34.0"
  }
}

Selenium Test Example

// e2e-tests/test/test.js
import { spawn, spawnSync } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
import { Builder, By } from 'selenium-webdriver';
import { expect } from 'chai';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
let driver, tauriDriver;
const application = path.resolve(__dirname, '../../src-tauri/target/debug/my-tauri-app');

describe('Tauri App Tests', function () {
  this.timeout(60000);

  before(async function () {
    spawnSync('cargo', ['build', '--manifest-path', '../../src-tauri/Cargo.toml'], {
      cwd: __dirname, stdio: 'inherit',
    });

    tauriDriver = spawn('tauri-driver', [], { stdio: ['ignore', 'pipe', 'pipe'] });
    await new Promise((resolve) => {
      tauriDriver.stdout.on('data', (data) => {
        if (data.toString().includes('listening')) resolve();
      });
    });

    driver = await new Builder()
      .usingServer('http://127.0.0.1:4444/')
      .withCapabilities({ browserName: 'wry', 'tauri:options': { application } })
      .build();
  });

  after(async function () {
    await driver?.quit();
    tauriDriver?.kill();
  });

  it('should display greeting', async function () {
    const header = await driver.findElement(By.css('body > h1'));
    expect(await header.getText()).to.match(/^[hH]ello/);
  });

  it('should click button and show output', async function () {
    const button = await driver.findElement(By.id('greet-button'));
    await button.click();
    const output = await driver.findElement(By.id('greet-output'));
    expect(await output.getText()).to.include('Hello');
  });
});

CI Integration with GitHub Actions

# .github/workflows/e2e-tests.yml
name: E2E Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - name: Install Linux dependencies
        if: matrix.os == 'ubuntu-latest'
        run: |
          sudo apt-get update
          sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential \
            curl wget file libxdo-dev libssl-dev \
            libayatana-appindicator3-dev librsvg2-dev \
            webkit2gtk-driver xvfb

      - uses: dtolnay/rust-action@stable
      - run: cargo install tauri-driver --locked

      - name: Setup Windows WebDriver
        if: matrix.os == 'windows-latest'
        shell: pwsh
        run: |
          cargo install --git https://github.com/chippers/msedgedriver-tool
          & "$HOME/.cargo/bin/msedgedriver-tool.exe"

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm install
      - run: npm run build
      - run: cargo build --manifest-path src-tauri/Cargo.toml

      - name: Run E2E tests (Linux)
        if: matrix.os == 'ubuntu-latest'
        working-directory: e2e-tests
        run: npm install && xvfb-run npm test

      - name: Run E2E tests (Windows)
        if: matrix.os == 'windows-latest'
        working-directory: e2e-tests
        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