Integration Tests for Tauri
CrabNebula provides the @crabnebula/tauri-driver
NPM package for end-to-end testing for Tauri apps via Webdriver.
The tauri-driver package is a fork of the official tauri-driver crate. It is compatible with the webdriver setup described in the tauri documentation, but it includes support for running macOS tests.
CrabNebula’s Tauri Driver acts as a bridge between the Webdriver client defining the integration tests and the target platform’s Webdriver implementation.
Currently supported platforms:
- macOS via CrabNebula Webdriver for Tauri
- Linux via webkit2gtk-driver
- Windows via msedgedriver
Prerequisites
- Linux: webkit2gtk-driver must be installed in your system and available in the PATH
- macOS: Your Tauri app must have the tauri-plugin-automation plugin installed in order to run integration tests on macOS
- Windows: msedgedriver.exe must be installed in your system and available in the PATH
Installation
Install with your favorite package manager:
npm install --save-dev @crabnebula/tauri-driver
yarn install -D @crabnebula/tauri-driver
pnpm install -D @crabnebula/tauri-driver
Additionally you should install @crabnebula/test-runner-backend
if you wish to run macOS tests locally, and @crabnebula/webdriverio-cloud-reporter
to upload test results to CrabNebula Cloud.
npm install --save-dev @crabnebula/test-runner-backend @crabnebula/webdriverio-cloud-reporter
yarn install -D @crabnebula/test-runner-backend @crabnebula/webdriverio-cloud-reporter
pnpm install -D @crabnebula/test-runner-backend @crabnebula/webdriverio-cloud-reporter
macOS support
The macOS webdriver currently requires a subscription. Contact us to get access.
To run integration tests on macOS, you must have the tauri-plugin-automation plugin installed in your Tauri app.
- Install the plugin:
cd src-tauricargo add tauri-plugin-automation
- Register the plugin:
Make sure you register the plugin as early as possible using tauri::Builder::plugin
instead of AppHandle#plugin
so it is ready when your app starts.
ALWAYS use a conditional compilation check to make sure the automation plugin is not added for production builds.
let mut builder = tauri::Builder::default();#[cfg(debug_assertions)] // alternatively: #[cfg(feature = "automation")], and tweak wdio.conf.js to enable this feature flag{ builder = builder.plugin(tauri_plugin_automation::init());}
builder .run(tauri::generate_context!()) .expect("error while running tauri application");
Usage
WebdriverIO
Let’s define a WebdriverIO configuration that starts tauri-driver so it can run your integration tests.
- Import packages
import path from "path";import { spawn, spawnSync } from "child_process";import { CrabNebulaCloudReporter } from "@crabnebula/webdriverio-cloud-reporter";import { waitTauriDriverReady } from "@crabnebula/tauri-driver";import { waitTestRunnerBackendReady } from "@crabnebula/test-runner-backend";
- Set your application path
For macOS you can optionally use the .app
bundle instead of just your app executable.
// NOTE: use actual path to the Tauri app directoryconst applicationPath = "path/to/src-tauri/target/debug/<app-name>"
Note that you must use the proper target
folder path relative to your WebdriverIO configuration file
and replace <app-name>
with the correct values.
- Create global variables
// keep track of the `tauri-driver` child processlet tauriDriver;let killedTauriDriver = false;// keep track of the `test-runner-backend` child processlet testRunnerBackend;let killedTestRunnerBackend = false;
- Define the WebdriverIO configuration
Note that this example assumes you are using pnpm
, please adjust the scripts if you are using a different package manager.
exports.config = { host: "127.0.0.1", port: 4444, specs: ["./test/specs/**/*.js"], maxInstances: 1,
capabilities: [ { maxInstances: 1, "tauri:options": { application: applicationPath, }, }, ], reporters: [CrabNebulaCloudReporter], framework: "mocha", mochaOpts: { ui: "bdd", timeout: 60000, }, connectionRetryCount: 0,
onPrepare: async () => { // ensure the Tauri app is built since we expect this binary to exist for the webdriver sessions spawnSync("pnpm", ["tauri", "build", "--debug", "--no-bundle"], { cwd: path.resolve(__dirname, "path/to/src-tauri"), // NOTE: use actual path to the Tauri app directory stdio: "inherit", shell: true, });
if (process.platform === "darwin") { // CN_API_KEY is required to run macOS tests via CrabNebula Webdriver for Tauri if (!process.env.CN_API_KEY) { console.error( "CN_API_KEY is not set, required for CrabNebula Webdriver" ); process.exit(1); }
testRunnerBackend = spawn("pnpm", ["test-runner-backend"], { stdio: "inherit", shell: true, });
testRunnerBackend.on("error", (error) => { console.error("test-runner-backend error:", error); process.exit(1); }); testRunnerBackend.on("exit", (code) => { if (!killedTestRunnerBackend) { console.error("test-runner-backend exited with code:", code); process.exit(1); } });
await waitTestRunnerBackendReady();
// instruct tauri-driver to connect to the test-runner-backend process.env.REMOTE_WEBDRIVER_URL = `http://127.0.0.1:3000`; } },
// ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests beforeSession: async () => { tauriDriver = spawn("pnpm", ["tauri-driver"], { stdio: [null, process.stdout, process.stderr], shell: true, }); tauriDriver.on("error", (error) => { console.error("tauri-driver error:", error); process.exit(1); }); tauriDriver.on("exit", (code) => { if (!killedTauriDriver) { console.error("tauri-driver exited with code:", code); process.exit(1); } });
// wait for tauri-driver to initialize its proxy server await waitTauriDriverReady(); },
// clean up the `tauri-driver` process we spawned at the start of the session afterSession: () => { closeTauriDriver(); },
onComplete: () => { killedTestRunnerBackend = true; testRunnerBackend?.kill(); },};
function closeTauriDriver() { killedTauriDriver = true; tauriDriver?.kill(); killedTestRunnerBackend = true; testRunnerBackend?.kill();}
export function onShutdown(fn) { const cleanup = () => { try { fn(); } finally { process.exit(); } };
process.on("exit", cleanup); process.on("SIGINT", cleanup); process.on("SIGTERM", cleanup); process.on("SIGHUP", cleanup); process.on("SIGBREAK", cleanup);}
onShutdown(closeTauriDriver);