* feat: add e2e tests for API * fix pnpm * ci * test on push for now * workflow_dispatch * fix lint * fix CI build on mac * fix audit * fix tests * fix windows CI * fix tests * install driver fix * fix windows test * test ci * test * test * fix ci * actually fix windows ci test * test * pin * fix tests * remove api env override * bump actions, fix workaround * code review * bump deps
6.9 KiB
@tauri-apps/api end-to-end tests
WebdriverIO suite that exercises every @tauri-apps/api module against a
real Tauri app — the examples/api validation app — rather than
a mocked backend. Each module has its own spec file, and adding coverage for a new API is
normally just dropping in one more spec.
How it works
- The example app is built with
withGlobalTauri: true, so the entire API surface is reachable onwindow.__TAURI__inside the webview. - WebdriverIO drives the app through
@crabnebula/tauri-driver, which bridges the WebDriver protocol to each platform's webview:- macOS — the CrabNebula Webdriver, which needs
tauri-plugin-automation(registered inexamples/apibehind its off-by-defaultautomationCargo feature, which the suite's build enables) and a locally-running@crabnebula/test-runner-backend, authenticated withCN_API_KEY. - Linux —
webkit2gtk-driver(WebKitWebDriveronPATH). - Windows —
msedgedriver.exeonPATH. It hands the app the--remote-debugging-portit attaches to throughWEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS, which WebView2 ignores in an elevated process (wry#1782), so the suite has to run unelevated.
- macOS — the CrabNebula Webdriver, which needs
- Specs never
evalin the page. They pass a function to thetauri()helper, which serializes it and runs it via the driver's own (CSP-exempt) script injection, handing itwindow.__TAURI__as the first argument and returning its JSON result. - A fresh
tauri-driver(and therefore a fresh app instance) is started per spec file, so each module's suite runs in isolation.
Prerequisites
# from the repo root
pnpm install
pnpm build:api # examples/api resolves @tauri-apps/api from packages/api/dist
pnpm build:cli # examples/api's `tauri` script uses the local native CLI
Platform driver dependencies:
| Platform | Requirement |
|---|---|
| macOS | CN_API_KEY env var (CrabNebula Cloud). The automation plugin and test-runner-backend are wired up already. |
| Linux | webkit2gtk-driver package (provides WebKitWebDriver). |
| Windows | msedgedriver.exe matching your Edge version, on PATH. Run the suite unelevated. |
Running
# from the repo root
pnpm test:api-e2e
# or from this package
pnpm e2e
# iterate without rebuilding the app every run
E2E_SKIP_BUILD=1 pnpm e2e
# run a single module's spec
pnpm exec wdio run ./wdio.conf.ts --spec test/specs/window.spec.ts
The first run builds the app with tauri.e2e.conf.json as a config
override, which enables the example's automation feature; afterwards use E2E_SKIP_BUILD=1
to reuse the existing binary. A binary supplied through E2E_SKIP_BUILD or E2E_APP_PATH
must have been built with that feature whenever the CrabNebula Webdriver is in use (always
on macOS).
Environment variables
| Variable | Purpose |
|---|---|
CN_API_KEY |
CrabNebula Cloud key. Required on macOS (and whenever E2E_CN_WEBDRIVER=1). |
E2E_SKIP_BUILD |
Skip the tauri build step and reuse the existing binary. |
E2E_APP_PATH |
Absolute path to a prebuilt app/binary to test (also implies skip-build). |
E2E_SKIP |
Comma-separated module names to skip, e.g. E2E_SKIP=tray,menu. |
E2E_SKIP_WM |
Skip window-manager-dependent tests (minimize/maximize/fullscreen/position/hide). |
E2E_SPEC_RETRIES |
Retry count for flaky spec files (default 0). |
E2E_CN_WEBDRIVER |
Use the CrabNebula Webdriver on Linux/Windows too (instead of the native driver). |
E2E_NATIVE_DRIVER |
Path passed to tauri-driver --native-driver (e.g. a specific chromedriver). |
CARGO_TARGET_DIR |
Override the target dir the app binary is looked up in. |
Adding tests for a new API
-
Add a spec. Create
test/specs/<module>.spec.tsand usedescribeApi('<module>', …)with thetauri()helper. It is picked up automatically by thetest/specs/**/*.spec.tsglob. Minimal example:import { expect } from '@wdio/globals' import { tauri, describeApi } from '../helpers/index.js' describeApi('app', () => { it('reports the product name', async () => { expect(await tauri((api) => api.app.getName())).toBe('Tauri API') }) }) -
Grant permissions if needed. If the API calls a command that is not allowed by default, add the permission to
examples/api/src-tauri/capabilities/run-app.json. The default permission sets are documented undercrates/tauri/permissions/*/autogenerated/reference.md. -
Add a Rust command if needed. If the API needs a custom backend command, add it to
examples/api/src-tauri/src/cmd.rs, register it in thegenerate_handler!list insrc/lib.rs, add its permission, and grant it inrun-app.json. -
Handle environment-sensitive cases. Use
itWm(instead ofit) for assertions that depend on a real window manager, andeventually()to poll for state a WM applies asynchronously. Branch onprocess.platform(Node side) for platform-specific behavior.
Rules for tauri() page functions
The function you pass to tauri() runs inside the webview, serialized as a string:
- It cannot close over anything from the spec module — pass every value it needs through
the trailing
tauri(fn, ...args)arguments. - It may only reference
api(thewindow.__TAURI__object), those args, and browser globals (window,document,setTimeout,Promise, …). - Its return value must be JSON-serializable — return plain objects/primitives, not class instances (call methods and return their results instead).
- Restore any app state you mutate (title, size, theme, …); tests within a spec file share the same app instance.
- For in-page waiting, wrap logic in a
Promisewith an explicitsetTimeoutrejection so a failure surfaces as a message rather than an opaque driver timeout.
Use tauriError(fn, ...args) to assert that a call rejects; it returns the rejection message.