mcpField
T
testing-tauri-apps

testing-tauri-apps

github.com/dchukテスト・品質36233 回インストール40 週間前に更新
リポジトリ

testing-tauri-apps について

Tauri v2 アプリをテストする開発者向けスキルです。フロントエンドの単体・統合テストから WebDriver による E2E テストまで、設定と CI ワークフローを扱います。

主な機能

  1. 01Tauri API と IPC のモック
  2. 02イベントとウィンドウのテスト
  3. 03WebDriver による E2E テスト
  4. 04WebdriverIO または Selenium の設定
  5. 05GitHub Actions へのテスト統合

活用シーン

  1. 01ネイティブ WebView ライブラリなしでフロントエンド動作をテスト
  2. 02Windows または Linux でアプリ操作を自動化
  3. 03GitHub Actions で Tauri テストを実行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dchuk/claude-code-tauri-skills --skill testing-tauri-apps

インストール後、Claude Code で/testing-tauri-appsを入力して読み込まれたか確認します。

詳細

Tauri アプリケーションのテスト

このスキルでは、Tauri v2 アプリケーションのテスト戦略について説明します。モックを使ったユニットテスト、WebDriver を使ったエンドツーエンドテスト、CI との統合を扱います。

テスト方法の概要

Tauri では、主に次の 2 つのテスト方法がサポートされています。

  1. ユニット/統合テスト - ネイティブの WebView ライブラリを実行せずに、モックランタイムを使用します
  2. エンドツーエンドテスト - ブラウザーの自動化に WebDriver プロトコルを使用します

Tauri API のモック

@tauri-apps/api/mocks モジュールを使うと、フロントエンドのテスト中に Tauri 環境をシミュレートできます。

モックの依存関係をインストールする
npm install -D vitest @tauri-apps/api
IPC コマンドをモックする
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();
  });
});
Sidecar と Shell のコマンドをモックする
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 } });
  }
});
イベントをモックする(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();
ウィンドウをモックする
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 の設定
// 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 によるエンドツーエンドテスト

WebDriver テストでは、Tauri アプリケーションの自動化に tauri-driver を使用します。

プラットフォームのサポート
プラットフォームサポート備考
Windows完全Microsoft Edge Driver が必要です
Linux完全WebKitWebDriver が必要です
macOSなしWKWebView には WebDriver ツールがありません
tauri-driver をインストールする
cargo install tauri-driver --locked
プラットフォームの依存関係
# 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 の設定

プロジェクト構成
my-tauri-app/
├── src-tauri/
├── src/
└── e2e-tests/
    ├── package.json
    ├── wdio.conf.js
    └── specs/
        └── app.spec.js
パッケージの設定
{
  "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 の設定
// 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 のテスト例
// 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 の設定

パッケージの設定
{
  "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 のテスト例
// 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');
  });
});

GitHub Actions との CI 統合

# .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

ベストプラクティス

モックテスト
  • 状態の漏えいを防ぐため、afterEach で必ず clearMocks() を呼び出します
  • スパイを使って IPC 呼び出しが正しく行われたことを確認します
  • 適切なレベルでモックします。コマンドには IPC、複数ウィンドウのロジックにはウィンドウを使います
WebDriver テスト
  • 開発中の反復を速めるため、デバッグビルドを使用します
  • Tauri アプリの初期化には時間がかかる場合があるため、適切なタイムアウトを設定します
  • 暗黙的な待機を使わず、要素を明示的に待ちます
  • 各テストを単独で実行できるよう、テスト間の依存関係をなくします
CI 統合
  • Linux でヘッドレスの WebDriver テストを行う場合は、xvfb-run を使用します
  • 接続の問題を避けるため、Windows では Edge Driver のバージョンを一致させます
  • WebDriver テストの実行前にアプリをビルドします
  • 問題を早期に発見するため、e2e テストの前にユニットテストを実行します

トラブルシューティング

WebDriver の接続タイムアウト
  • Windows:Edge Driver のバージョンがインストール済みの Edge と一致していることを確認します
  • Linux:webkit2gtk-driver がインストールされていることを確認します
  • tauri-driver が起動しており、ポート 4444 でリッスンしていることを確認します
モックが動作しない
  • テスト対象のコードより先に @tauri-apps/api/mocks をインポートします
  • 状態をリセットするため、afterEach で clearMocks() を呼び出します
  • セットアップで window.__TAURI_INTERNALS__ が適切にモックされていることを確認します
CI の失敗
  • Linux:テストコマンドの先頭に xvfb-run を追加します
  • Windows:msedgedriver-tool を使って Edge Driver をインストールします
  • 実行速度の遅い CI ランナーでは、タイムアウトを延長します

参考資料

コメント