mcpField
T
testing-tauri-apps

testing-tauri-apps

github.com/dchuk测试与质量36233 次安装40 周前更新
代码仓库

关于 testing-tauri-apps

面向 Tauri v2 应用开发者的测试技能,涵盖前端单元与集成测试,以及 WebDriver 端到端测试,并介绍测试配置和 CI 工作流。

主要功能

  1. 01模拟 Tauri API 和 IPC
  2. 02测试事件与窗口
  3. 03运行 WebDriver 端到端测试
  4. 04配置 WebdriverIO 或 Selenium
  5. 05将测试接入 GitHub Actions

适用场景

  1. 01不依赖原生 WebView 库测试前端行为
  2. 02在 Windows 或 Linux 上自动化应用交互
  3. 03在 GitHub 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 支持两种主要的测试方法:

  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-driver 自动化 Tauri 应用。

平台支持
平台支持情况备注
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 上使用 xvfb-run 进行无头 WebDriver 测试
  • 在 Windows 上确保 Edge Driver 版本匹配,以避免连接问题
  • 运行 WebDriver 测试前先构建应用
  • 先运行单元测试,再运行端到端测试,以便及早发现问题

故障排查

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 运行器增加超时时间

参考资料

评论

支持 Markdown