Skip to content

4DA-Systems/Victauri

Victauri

Victauri

Verified Introspection & Control for Tauri Applications

Full-stack testing for Tauri apps. Click a button in the frontend, verify the Rust handler ran, confirm the database row was written — from one test.

CI crates.io docs.rs License: Apache-2.0 MSRV: 1.88+


Testing Tauri apps today means choosing between frontend mocks that lie about your backend, WebDriver setups that take a weekend, or paying for macOS CI runners. Victauri replaces all three: it embeds a lightweight server inside your Tauri process (debug builds only) that gives your test suite, curl, and CI direct access to the DOM, IPC layer, Rust backend state, the database, and native windows — from one test, on all three platforms. No WebDriver. No browser dependency. Works on macOS, Windows, and Linux.

That same server also speaks MCP, so any AI agent — Claude Code, Cursor, Windsurf — can drive and debug your app with the exact same full-stack access. Testing is the job; the agent integration is the bonus.

Tested against real-world Tauri apps. In a one-time deep evaluation (May 2026) across 5 open-source apps (Kanri, En Croissant, Surrealist, Duckling, Lettura), 867 / 895 checks passed (96.9%) with zero Victauri bugs and zero changes to the apps. A reproducible compat harness (scripts/compat) re-runs an app-agnostic smoke battery against the current code on demand (and weekly), pinned to three Tauri-2 apps (Kanri, En Croissant, Lettura). It is a best-effort net for upstream drift, not a release gate — third-party apps move on their own schedules and periodically need re-pinning. See the compat README for what it covers and current per-app status.

Note — browser mode removed (2026-06-09). The experimental victauri-browser extension + @4da/victauri-browser npm package (MCP for any website) have been removed so Victauri can focus on its strength: full-stack introspection inside Tauri apps. For browser automation, use Playwright or the Chrome DevTools Protocol. The Tauri plugin is unaffected. Details & migration: #15.

What You Get

For your test suite and CI:

  • Full-stack verification — click a button, verify the IPC call, query the database to confirm the write, check the UI updated — in one test
  • Direct backend access — query SQLite databases, browse app files, read config, inspect process memory — no webview proxy
  • Ghost command detection — find high-confidence orphaned frontend calls (confirmed_ghosts: invoked but only ever errored "not found"), and backend commands the frontend never calls
  • Cross-boundary state checking — compare DOM state against Rust backend state and catch the drift between them
  • Time-travel recording — record interactions, checkpoint state, replay sequences, generate test files
  • Cross-platform, no WebDriver — identical behavior on macOS, Windows, and Linux; runs headless in CI under xvfb
  • Zero runtime cost in release — the server is gated behind #[cfg(debug_assertions)], so init() is a no-op and nothing listens in release builds. (The crate still compiles in; add it as a dev-dependency if you want it absent from the release binary entirely.)

Bonus — for AI agents: the same server speaks MCP, so Claude Code, Cursor, Windsurf, and any MCP client get this full-stack access for interactive debugging — no extra setup.

Will Victauri work on your app?

Victauri is a build-time dev dependency you add to your own app's source and rebuild — not a tool you attach to an already-running or shipped app. It works when all four hold:

  1. Tauri 2. Tauri 1.x apps can't host it — their webkit2gtk-sys 0.18 and Victauri's 2.x both link the native web_kit2 library, an unresolvable cargo conflict. (Tauri 2 is the current major; Tauri 1 is legacy.)
  2. Built from source with the plugin wired in — one line in Cargo.toml, .plugin(victauri_plugin::init()), and a victauri:default capability. There is no inject-into-a-foreign-binary path.
  3. A debug build. The server is #[cfg(debug_assertions)]-gated, so init() is a no-op and nothing listens in release. It's a dev/test-time tool by design.
  4. Each window grants victauri:default. Tauri's per-window permission ACL silently blinds the bridge without it; the window introspectability tool detects and explains this.

Framework (React, Vue/Nuxt, Svelte, vanilla) and OS/webview engine (WebView2 / WKWebView / WebKitGTK) do not matter — all supported and cross-checked on Windows, macOS, and Linux.

✅ Works on ❌ Won't work on
Your own Tauri 2 app during development Tauri 1.x apps
Any Tauri 2 app you can build from source (debug) Release / production builds
Any frontend framework, any of the 3 OSes A binary you didn't build (no source)
Non-Tauri apps (Electron, native, plain web)

Full details, edge cases, and a tested-apps list: Compatibility.

Quick Start

Install the CLI

cargo install victauri-cli

Set up your project

From your Tauri project root:

victauri init

This will:

  • Add victauri-plugin and victauri-test to your Cargo.toml
  • Create starter smoke tests in your tests/ directory
  • Print the next steps to wire the plugin

Wire the plugin

Add one line to your Tauri builder:

// src-tauri/src/main.rs (or lib.rs)
tauri::Builder::default()
    .plugin(victauri_plugin::init())
    .invoke_handler(tauri::generate_handler![/* your commands */])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

In release builds, init() returns a no-op plugin — the server never starts, so there's zero runtime cost and no feature flags needed.

Run tests

Start your app, then run the smoke suite:

pnpm tauri dev                                   # start your app
VICTAURI_E2E=1 cargo test --test smoke           # run tests

Or use the CLI for instant validation:

victauri test                                    # 11 built-in smoke checks
victauri check                                   # server health + IPC diagnostics

Connect an AI agent

Add .mcp.json to your project root (created automatically by victauri init):

{
  "mcpServers": {
    "my-app": {
      "command": "victauri",
      "args": ["bridge", "--wait"]
    }
  }
}

The victauri bridge stdio proxy discovers the running app's port at connect time and re-discovers on restart, so the agent always reaches the right app — even across rebuilds, or when several Victauri apps are running (add "--app", "<your.bundle.identifier>" to pin one). Prefer it over a fixed "url": "http://127.0.0.1:7373/mcp", which hardcodes a port and can bind the wrong app.

Works with Claude Code, Cursor, Windsurf, and any MCP client. Your agent gets full-stack access: DOM snapshots, IPC monitoring, command invocation, screenshot capture, accessibility auditing, and more.

With Claude Code, start your app and Claude can immediately:

  • Inspect the DOM tree and click/type/fill any element
  • Invoke any #[tauri::command] and verify the response
  • Read app config, list files, query SQLite databases
  • Take screenshots and audit accessibility
  • Record interactions and replay them as tests

Writing Tests

The e2e_test! macro handles server detection and auto-connect:

use victauri_test::{e2e_test, VictauriClient};

e2e_test!(greet_flow, |client| async move {
    client.fill_by_id("name-input", "World").await.unwrap();
    client.click_by_id("greet-btn").await.unwrap();
    client.expect_text("Hello, World!").await.unwrap();
});

Locator API

For complex queries, composable locators with auto-waiting expectations:

use victauri_test::prelude::*;

e2e_test!(settings_flow, |client| async move {
    let save = Locator::role("button").and_text("Save");
    let email = Locator::label("Email address");

    email.fill(&mut client, "user@example.com").await.unwrap();
    save.click(&mut client).await.unwrap();

    Locator::test_id("toast-message")
        .expect(&mut client)
        .to_contain_text("Settings saved")
        .await
        .unwrap();
});

Locators support role, text, test_id, css, label, placeholder, alt_text, and title strategies with chainable refinement (.and_text(), .nth(), .and_tag()). See the Testing Guide for the full Locator API reference.

Direct Client Methods

Method What it does
click_by_text("Submit") Find element by visible text, click it
click_by_id("save-btn") Find element by HTML id, click it
fill_by_id("email", "a@b.com") Find input by id, fill value
type_by_id("search", "query") Find input by id, type char-by-char
select_by_id("theme", "dark") Find select by id, choose option
expect_text("Success!") Poll until text appears (5s timeout)
expect_no_text("Error") Poll until text disappears (3s timeout)
text_by_id("counter") Get text content of element by id

Full-Stack Verification

This is what sets Victauri apart — verifying that frontend actions actually trigger the right backend logic.

UI to IPC to Database

// Click "Save" in the UI
client.click_by_id("save-btn").await?;

// Verify the IPC command was called
let log = client.get_ipc_log(None).await?;
assert_ipc_called(&log, "save_settings");

// Verify the database was actually written
let result = client.query_db(
    "SELECT value FROM settings WHERE key = 'theme'",
    None, None,
).await?;
assert_eq!(result["rows"][0]["value"], "dark");

Fluent Verification

Check multiple conditions at once — DOM, IPC, accessibility, errors — with a single report:

client.verify()
    .has_text("Settings saved")
    .ipc_was_called("save_settings")
    .no_console_errors()
    .no_ghost_commands()
    .ipc_healthy()
    .coverage_above(80.0)
    .run()
    .await?
    .assert_all_passed();

Ghost Command Detection

Find orphaned commands — called in the frontend but missing from the backend:

let ghosts = client.detect_ghost_commands().await?;
assert!(ghosts["confirmed_ghosts"].as_array().unwrap().is_empty(),
    "Found ghost commands: {ghosts}");

See the Testing Guide for IPC checkpoints, visual regression testing, IPC coverage, accessibility auditing, performance monitoring, time-travel recording, CI integration, and more.


MCP Tools

35 tools across the full stack — backend, IPC, webview, and introspection:

Backend tools (direct Rust access, no webview needed)

Tool What it does
app_info App config, directory paths, env vars, discovered databases, process info
list_app_dir Browse files in app data/config/log/local_data directories
read_app_file Read files from app backend directories (UTF-8 or base64)
query_db Read-only SQLite queries with auto-discovery
invoke_command Call any Tauri command directly through IPC
app_state Read app-defined backend-state probes (pipeline/queue/cache internals) — no IPC round-trip
get_memory_stats Real-time OS process memory (working set, page faults)

IPC tools

Tool What it does
get_registry List all #[inspectable] command schemas
detect_ghost_commands Find orphaned frontend IPC calls (high-confidence confirmed_ghosts)
check_ipc_integrity Detect stuck/stale/errored IPC calls
verify_state Compare frontend DOM against backend state
resolve_command Natural language to matching Tauri command

Webview tools

Tool What it does
eval_js Execute JavaScript in the webview
dom_snapshot Full accessibility tree with ref handles
find_elements Search by text, role, test ID, CSS, label, placeholder, alt, title
screenshot Platform-native window capture (no Chromium)
wait_for Poll for conditions: text, selector, IPC settle, JS expression, or Tauri event — await async backend work without sleeps
assert_semantic Evaluate JS + assert against expected value

Compound tools (multiple actions per tool)

Tool Actions
interact click, double_click, hover, focus, scroll, select
input fill, type_text, press_key (keyboard combos supported)
window get_state, list, manage, resize, move, set_title
storage get, set, delete, cookies
navigate go_to, back, history, dialogs
recording start, stop, checkpoint, events, export, import
inspect styles, bounds, highlight, audit_accessibility, get_performance
logs console, network, ipc, navigation, dialogs, events, slow_ipc
css inject, remove
introspect command_timings, coverage, contract_record, contract_check, startup_timing, capabilities, db_health, plugin_state, processes, plugin_tasks, event_bus
fault inject (delay/error/drop/corrupt), list, clear, clear_all
explain summary, last_action, diff
get_plugin_info Plugin config: port, tools, privacy, version
get_diagnostics Shadow DOM, service workers, iframes, large DOM detection

All tools are also available via REST at POST /api/tools/{name} — no MCP client needed. See the Tools Reference.


How It Works

AI Agent / cargo test / curl
        |
        v
  HTTP on :7373
  ├── /mcp          (MCP protocol — for AI agents)
  ├── /api/tools    (REST API — for scripts and CI)
  └── /health       (health check — for monitoring)
        |
        v
  Victauri Plugin  (inside Tauri process)
     |       |       |
     v       v       v
  WebView  IPC    Backend
  - DOM    - log   - app config
  - click  - args  - file system
  - eval   - cmds  - SQLite DBs
  - a11y   - ghost - memory
  - perf   - verify- env vars

Victauri runs inside the Tauri process — same thread pool, same memory space. This isn't an implementation detail; it changes what's possible:

Embedded (Victauri) External process
Tool response <1ms (function call) 5-50ms (IPC + serialization)
State accuracy Zero drift (reads live state) Stale (snapshot + transfer)
Backend access Full (AppHandle, DB, state) Limited (webview only)
Setup One line in Cargo.toml Separate process + config
Release build init() is a no-op (zero runtime cost) Must be disabled manually

Port selection: Victauri tries port 7373 first, then falls back through 7374-7383 if taken. The actual port is written to a temp directory for automatic client discovery.


Architecture

victauri/
├── crates/
│   ├── victauri-plugin/     # Tauri plugin + MCP server + JS bridge (the main crate)
│   ├── victauri-core/       # Shared types (events, registry, snapshots, verification)
│   ├── victauri-macros/     # #[inspectable] proc macro for command schemas
│   ├── victauri-test/       # Test client + Locator API + assertion helpers
│   ├── victauri-cli/        # CLI: init, check, test, record, watch, coverage
│   └── victauri-watchdog/   # Health-check sidecar for crash recovery
├── docs/                    # mdbook documentation site
└── examples/
    └── demo-app/            # Multi-window Tauri app with 21 instrumented commands
Crate Purpose Tauri dependency?
victauri-plugin Embed in your app — MCP server + bridge Yes
victauri-test Use in your tests — client + assertions No
victauri-cli Install globally — scaffold + diagnose No
victauri-macros Use on commands — #[inspectable] No
victauri-core Shared types (usually not used directly) No
victauri-watchdog Run as sidecar for crash recovery No

Security

Victauri is designed for development, not production:

  • Debug-only: the server is #[cfg(debug_assertions)]-gated, so init() is a no-op and nothing listens in release builds
  • Localhost-only: binds to 127.0.0.1, DNS rebinding protection
  • Auth on by default: auto-generated Bearer token, auto-discovered by clients (.auth_disabled() to opt out)
  • Rate limited: 1000 req/sec, token-bucket algorithm
  • Privacy profiles: Observe (read-only), Test (interactions), FullControl (everything)
  • Output redaction: auto-scrub API keys, tokens, emails from tool responses

See the Security Guide for threat model, privacy configuration, and command filtering.


CI Integration

GitHub Actions

Use the built-in composite action to run Victauri smoke tests in CI:

- name: Build my app
  run: cargo build -p my-app

- name: Start app under xvfb
  run: |
    xvfb-run -a ./target/debug/my-app &
    sleep 3

- uses: 4DA-Systems/victauri/.github/actions/victauri-test@v0.8.1
  with:
    max-load-ms: 10000
    max-heap-mb: 512

The action installs victauri-cli, waits for the server to become healthy, and runs all 11 smoke checks. Available inputs:

Input Default Description
port 7373 Victauri server port
max-load-ms 10000 Maximum DOM complete time (ms)
max-heap-mb 512 Maximum JS heap usage (MB)
junit-path Path to write JUnit XML report
coverage false Run IPC coverage report after tests
coverage-threshold Minimum coverage % (fails if below)
health-timeout 30 Seconds to wait for server health

Or use victauri init to generate a complete CI workflow file for your project:

victauri init    # generates .github/workflows/victauri.yml

Documentation


What It Doesn't Do

  • No production use — debug builds only, by design
  • No remote access — localhost only, no port forwarding
  • Same-origin frames only — same-origin iframes are traversed; cross-origin frames are marked and skipped
  • No live frontend-IPC controlfault injection applies to commands driven through Victauri's own invoke_command, not the app's real frontend IPC (that path sits below the layer the JS bridge can reach without CDP)
  • Pre-1.0 — API may change (semver-checked in CI)

Development

cargo build --workspace                               # Build all crates
cargo test --workspace                                # Run all tests
cargo bench -p victauri-core                          # Criterion benchmarks (16)
cargo clippy --workspace --all-targets                # Lint (20 enforced lints)
cargo fmt --all -- --check                            # Format
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps  # Docs (zero warnings)

Lint policy: 20 clippy lints (pedantic + nursery) enforced at deny level — see [workspace.lints.clippy] in Cargo.toml.

Community & Contributing

Victauri is open source and built by 4DA Systems, which uses it to test its own Tauri app. We want it to become the default way to test Tauri apps full-stack — and that needs more than one company.

  • Using it on a Tauri app? Tell us — open a discussion or issue. We'd love to add you to a "used by" list and learn what broke.
  • Want to contribute? See CONTRIBUTING.md. Good first areas: more victauri-test assertion helpers, framework-specific testing guides, and CI recipes.
  • Found a bug or a Tauri app it doesn't work on? File an issue with the app and the failing tool call — those reports are the most valuable thing you can send us.

If Victauri saves you a weekend of WebDriver setup, a ⭐ helps other Tauri developers find it.

License

Apache-2.0 — LICENSE

Built and maintained by 4DA Systems.

About

Full-stack testing for Tauri apps — DOM, IPC, Rust backend, and database from one test. 28 MCP tools. Zero production cost.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors