You can test a Tauri app end-to-end without WebDriver by describing each step in plain language. TestDriver matches each description against screenshots of the installed app, so:
- you test the same
.debyour users install, with nothing compiled into it - you don't write or maintain selectors
- your test reads like the steps you'd give a teammate
TestDriver generates and runs black-box end-to-end tests. It starts your app in a fresh virtual machine, looks at the screen and uses a real mouse and keyboard, so it never needs selectors or access to your code. Each test is a normal Vitest file, and every run is recorded as a replay.
The example installs Yaak, an open-source API client built with Tauri, sends a request and checks the response. The full project is on GitHub: testdriverai/example-tauri-app.
Why not WebDriver
Tauri's testing docs recommend WebDriver: WebdriverIO's @wdio/tauri-service, which by default needs tauri-plugin-wdio-webdriver compiled into your app, or tauri-driver directly on Windows and Linux. Both find elements with selectors into the webview's DOM:
// WebdriverIO-style selector (illustrative, not run)
await $('[aria-label="Send request"]').click();
TestDriver describes the same element the way a user sees it:
await testdriver.find("Send button (paper airplane icon) at the right end of the URL bar").click();
The selector breaks when the markup changes. The description only changes when the UI does.
Watch the full replay
Open the full replay to step through every action with its screenshot and log.
The test
import { describe, expect, it } from "vitest";
import { TestDriver } from "testdriverai/vitest/hooks";
const YAAK_DEB =
"https://github.com/mountain-loop/yaak/releases/download/v2026.9.0/yaak_2026.9.0_amd64.deb";
describe("Yaak (Tauri)", () => {
it("sends a GET request and shows the JSON response", async (context) => {
const testdriver = TestDriver(context);
// Download and install the .deb. We start the app ourselves below.
await testdriver.provision.installer({ url: YAAK_DEB, launch: false });
// Pull in Tauri's Linux runtime dependencies (WebKitGTK, libsoup) and start Yaak.
await testdriver.exec(
"sh",
"sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -f -y -qq",
240000,
);
await testdriver.exec("sh", "nohup yaak-app-client >/tmp/yaak.log 2>&1 &", 10000);
// Start with an empty workspace. The first find waits for the window to appear.
await testdriver.find('"Start fresh" card on the Yaak welcome screen', { timeout: 60000 }).click();
// Create a new HTTP request.
await testdriver.find('"New Request" button in the middle of the empty workspace').click();
await testdriver.find('"HTTP" option in the New Request dropdown menu').click();
// Enter a URL and send it.
await testdriver.find('URL input with the placeholder "https://example.com" next to the GET method selector').click();
await testdriver.type("https://httpbin.org/json");
await testdriver.find("Send button (paper airplane icon) at the right end of the URL bar").click();
// Wait for the response to arrive, then check what the app rendered.
await testdriver.find("green 200 OK status label at the top of the response panel", { timeout: 30000 });
const responseShown = await testdriver.assert(
'the response panel shows a green 200 OK status and a JSON body containing "Sample Slide Show"',
);
expect(responseShown).toBeTruthy();
const savedInSidebar = await testdriver.assert(
'the sidebar lists the request as "httpbin.org/json" with a 200 status badge',
);
expect(savedInSidebar).toBeTruthy();
});
});
How it works
Install and launch the release build
provision.installer() downloads the .deb and installs it on a fresh Linux sandbox. The two exec() calls install Tauri's Linux runtime (WebKitGTK and libsoup) and start the app.
Describe elements by what users see
Each find() takes a description that names the label, the look and the position, such as "Send button (paper airplane icon) at the right end of the URL bar". Add a landmark when a label alone could match more than one element. See locating elements.
Wait for results, then assert
find() with a timeout keeps looking until the element appears, so the test waits for the app to start and for the 200 status without a fixed sleep. assert() then checks a plain-language statement against the screen and returns true or false. See making assertions.
Run it yourself
Clone the example repo, or set up TestDriver in your own project and run the test:
npx testdriverai init
npx vitest run tauri-app.test.mjs
To test your own Tauri app:
- Point
provision.installer()at your release.deb(tauri buildwrites it tosrc-tauri/target/release/bundle/). - Start your binary in the second
exec()call.dpkg -L <package>shows its name. - On Windows, pass
os: "windows"toTestDriver()and point the installer at your.msior.exe.
FAQ
Do I need tauri-driver, WebdriverIO or a plugin?
No. TestDriver drives the installed app from the screen, so nothing is added to your Tauri project.
Do I need test IDs?
No. Steps are matched on screenshots, so there are no data-testid attributes to add or keep in sync.
Can I run it in CI?
Yes. It's a normal Vitest file and the app runs in TestDriver's cloud sandbox. See CI/CD.
Originally published on the TestDriver blog.

Top comments (0)