Native Tauri automation

tauri-wd

Run complex end-to-end tests against the real WKWebView, WebView2, and WebKitGTK surfaces in your desktop app—with standard W3C clients and deterministic app lifecycle management.

  • macOS
  • Windows
  • Linux
Terminal
$ cargo add tauri-wd \
  --optional

added tauri-wd to dependencies

Built for real applications

One test surface, three native webviews.

Keep your test code portable while the embedded engine handles each operating system’s native webview API.

01

Native, not simulated

Drive the same window, DOM, dialogs, frames, shadow roots, and input surfaces your users interact with.

02

Client independent

Use WebdriverIO, Selenium, Fantoccini, or any current client that speaks the W3C WebDriver protocol.

03

Lifecycle owned

Each session launches one app process, isolates its test directory, forwards logs, and cleans up the full process tree.

Command coverage

Enough surface area for complex workflows.

  • Elements, nested references, and active element
  • Pointer, keyboard, wheel, and release actions
  • Frames, windows, shadow DOM, and alerts
  • Sync and async scripts with W3C JSON cloning
  • Cookies, source, screenshots, and PDF
  • Navigation waits and configurable timeouts

Architecture

A small boundary around native automation.

The public driver owns sessions and process safety. A test-only plugin translates W3C commands inside the app.

Your runner

WebdriverIO · Selenium · Fantoccini

Sends ordinary W3C commands to a stable loopback endpoint.

tauri-wd

Sessions, app processes, policy

Merges capabilities, launches the binary, queues commands, bounds data, and reaps failures.

Test-only plugin

Native webview bridge

Executes commands through WKWebView, WebView2, or WebKitGTK.

Setup

Keep automation out of production builds.

Gate the plugin behind a Cargo feature, build the test binary, then point your preferred client at the driver.

  1. 1

    Add the optional plugin

    Use a dedicated feature so release binaries never contain an automation server.

    Cargo.toml
    [features]
    e2e = ["dep:tauri-wd"]
    
    [dependencies]
    tauri-wd = {
      version = "0.1", optional = true
    }
  2. 2

    Register it in test builds

    The plugin remains inert unless tauri-wd launches the process with a private automation token.

    main.rs
    let builder = tauri::Builder::default();
    
    #[cfg(feature = "e2e")]
    let builder = builder.plugin(
      tauri_wd::init(),
    );
  3. 3

    Connect your test client

    Pass the current test binary through the namespaced tauri:options capability.

    wdio.conf.js
    export const config = {
      hostname: "127.0.0.1",
      port: 4444,
      capabilities: [{
        "tauri:options": {
          application: "./target/debug/my-app",
        },
      }],
    };

Current protocol only

Strict W3C capability processing.

The driver merges alwaysMatch with one compatible firstMatch candidate. desiredCapabilities and launcher aliases are rejected.

Supported tauri:options fields
Field Type Purpose
application string · required Path to the current test-enabled executable.
args string[] Arguments passed directly to the app process.
env object String environment values scoped to this session.
cwd string Working directory for the app process.
startupTimeout integer · ms Maximum time to bind the plugin and verify JavaScript responsiveness.
headless boolean Run without a visible window. On macOS the window stays on screen but transparent, click-through and never focused, so the webview keeps rendering; elsewhere it is hidden.
macOS

WKWebView

Native JavaScript evaluation, snapshots, PDF output, dialogs, and window control.

Windows

WebView2

Serialized script execution, native message callbacks, actions, and process-tree cleanup.

Linux

WebKitGTK 4.1

WebKit automation under a real display or Xvfb, with native screenshots and PDF.

Secure by construction

Local automation is still privileged automation.

The driver can launch an executable and supply environment variables, so the network boundary and build boundary are intentionally narrow.

Loopback only
The public driver and private plugin never bind to remote interfaces.
Per-session token
The embedded endpoint rejects requests without its unguessable bearer token.
Ephemeral port
The app binds port zero and publishes the actual listener only after it is ready.
Inert by default
Without the automation launch environment, the plugin registers no webview handlers and opens no server.

Reliability model

Failures become bounded, observable outcomes.

0 reserved-port race windows

The plugin owns the listener before it announces readiness.

1 command at a time per session

Concurrent client calls cannot corrupt native script state.

100 ms crash reaper interval

Dead apps release session capacity without waiting for another command.

Questions

Operational details.

Can I use this with an existing WebDriver client?

Yes. The public endpoint uses the current W3C protocol. WebdriverIO, Selenium, and Fantoccini can all create sessions with tauri:options.

Does it ship in my production app?

It should not. Keep the plugin optional and enable it only in a dedicated test build. It also remains inert unless the driver supplies the automation environment and token.

Can tests run concurrently?

Yes. Each session has its own app process, private plugin endpoint, token, command queue, and isolated automation directory. Set --max-sessions according to runner capacity.

Can it control native operating-system dialogs?

WebDriver controls the webview, not OS-owned surfaces such as a native file picker. In an e2e build, use automation_enabled() to expose a deterministic test-only command or fixture path for that boundary.

What happens when the app crashes?

The driver notices the process exit in the background, removes the session, releases capacity, and reports an invalid or failed session instead of poisoning future tests.

Ready to test the whole app?

Use the tools your team already knows.

Read the source