Can You Use Playwright with Tauri? What Works Instead

· TestDriver Team

Playwright and WebDriver test Tauri apps through selectors. Here's a Tauri test written in plain language and run on screenshots of the real app instead.

Automate and scale manual testing with AI ->

If you’re searching “Playwright Tauri”, you probably want your existing selector-based tests to run against the desktop app. Playwright can only attach to Tauri’s webview on Windows, through WebView2 and connectOverCDP(). Tauri’s own recommendation is WebDriver, via WebdriverIO or tauri-driver. Either way, every step is a selector into your DOM, and every refactor is a chance to break one.

There’s another way to write the test. Describe each step the way you’d explain it to a teammate, “click the paper airplane at the end of the URL bar”, and let TestDriver find it on a screenshot of the real app.

Watch the test run

Sped up: idle time removed. The full replay below has every step and its logs.

The test installs Yaak, an open-source API client built with Tauri, sends a request, checks the response, then switches the app to dark mode from its Settings dialog. The full project is on GitHub: testdriverai/example-tauri-playwright-alternative.

Playwright-style selectors vs. plain language

Here’s sending a request in Yaak, written the selector way:

// WebdriverIO-style selectors (illustrative, not run)
await $('[data-testid="new-request"]').click();
await $("button=HTTP").click();
await $('input[placeholder="https://example.com"]').setValue("https://httpbin.org/json");
await $('[aria-label="Send request"]').click();

And the same steps from the test that ran:

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();
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();

The selector version depends on test IDs, attribute names and markup you have to add and keep stable. The plain-language version depends on what a user sees: the label, the icon and where it sits. Rename a component or restructure the layout and it keeps working. It also reads like a test plan, so anyone on the team can review it.

The test

/**
 * Installs Yaak (an open-source Tauri API client) on Linux, sends an HTTP request,
 * checks the response, then switches the app to dark mode from its Settings dialog.
 */
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 request, then switches the app to dark mode", 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, enter a URL and send it.
    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();
    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, 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();

    // Open Settings from the gear in Yaak's custom title bar.
    await testdriver.find("settings gear icon in the top-right title bar, just left of the window minimize button").click();
    await testdriver.find('"Settings" item at the top of the main menu dropdown').click();

    // Switch the appearance from Automatic to Dark.
    await testdriver.find('"Theme" tab in the left sidebar of the Settings dialog, just below "General"').click();
    await testdriver.find('"Automatic" dropdown on the right of the Appearance row in the Theme settings').click();
    await testdriver.find('"Dark" option at the bottom of the Appearance dropdown list').click();

    // The dropdown updates before the window repaints, so wait for a dark-only detail first.
    await testdriver.find('moon icon next to "Yaak (preview)" on a dark navy background in the Preview section', { timeout: 30000 });
    const darkMode = await testdriver.assert(
      'the Appearance dropdown in the Theme settings shows "Dark" and the window, title bar and settings dialog all use a dark background',
    );
    expect(darkMode).toBeTruthy();
  });
});

How it works

Setup

provision.installer() downloads Yaak’s release .deb into a fresh Linux sandbox, the same file users install. Two exec() calls install Tauri’s Linux runtime libraries and start the app.

Descriptions that hold up

Good descriptions name the label, the look and the position: “Send button (paper airplane icon) at the right end of the URL bar.” That’s specific enough to pick one element, and none of it changes when the code underneath does. Landmarks help in busy areas: the settings gear is “just left of the window minimize button.” See locating elements.

Waiting without sleeps

The first find() after launch has a 60-second timeout, and the response check waits up to 30 seconds for the green 200 label. After switching to Dark, the test waits for a detail that only exists once the window has repainted (the moon icon on a dark preview) before asserting. find() keeps looking until the element appears, so there’s no fixed sleep to tune.

Assertions about what you can see

assert() checks a plain-language statement against the screen. The dark-mode check is the clearest example: “the window, title bar and settings dialog all use a dark background.” With selectors you’d assert on a class name or CSS variable and trust that it painted. Here the test checks the result itself, including Yaak’s custom title bar. More in making assertions.

Run it yourself

Clone the example repo, or:

npx testdriverai init
npx vitest run tauri-playwright-alternative.test.mjs

See the quickstart for setup and CI/CD for GitHub Actions. To test your own Tauri app:

  • Point YAAK_DEB at your release .deb (tauri build writes it to src-tauri/target/release/bundle/) and swap in your binary name.
  • Rewrite the steps in your UI’s words. If a description matches more than one thing, add where it is.
  • For Windows, set os: "windows" and point the installer at your .msi or .exe.

FAQ

Can Playwright test a Tauri app? On Windows, by attaching to WebView2 with connectOverCDP(). Playwright doesn’t document a way to attach to Tauri’s webview on Linux or macOS.

Do I need test IDs or selectors? No. Steps are plain-language descriptions matched on screenshots, so you don’t add data-testid attributes or keep selectors in sync with your markup.

What happens when the UI changes? Layout and code changes don’t matter as long as the element still looks and reads the same to a user. If the label or icon changes, update the sentence.

Automate and scale manual testing with AI

TestDriver uses computer-use AI to test any app - write tests in plain English and run them anywhere.