Content
<p align="center">
<img src="assets/logo.png" alt="Victauri" width="120">
</p>
<h1 align="center">Victauri</h1>
<p align="center">
<em>Verified Introspection & Control for Tauri Applications</em>
</p>
<p align="center">
<strong>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.</strong>
</p>
<p align="center">
<a href="https://github.com/4DA-Systems/victauri/actions/workflows/ci.yml"><img src="https://github.com/4DA-Systems/victauri/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://crates.io/crates/victauri-plugin"><img src="https://img.shields.io/crates/v/victauri-plugin.svg" alt="crates.io"></a>
<a href="https://docs.rs/victauri-plugin"><img src="https://docs.rs/victauri-plugin/badge.svg" alt="docs.rs"></a>
<a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License: Apache-2.0"></a>
<a href="https://doc.rust-lang.org/edition-guide/rust-2024/index.html"><img src="https://img.shields.io/badge/MSRV-1.88+-informational" alt="MSRV: 1.88+"></a>
</p>
---
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](https://modelcontextprotocol.io), 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`](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](scripts/compat/README.md) 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](https://playwright.dev/) or the Chrome DevTools Protocol. The Tauri plugin is unaffected. Details & migration: [#15](https://github.com/4DA-Systems/victauri/issues/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**](docs/src/compatibility.md).
## Quick Start
### Install the CLI
```bash
cargo install victauri-cli
```
### Set up your project
From your Tauri project root:
```bash
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:
```rust
// 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:
```bash
pnpm tauri dev # start your app
VICTAURI_E2E=1 cargo test --test smoke # run tests
```
Or use the CLI for instant validation:
```bash
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`):
```json
{
"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:
```rust
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:
```rust
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](docs/src/testing.md) 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
```rust
// 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:
```rust
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:
```rust
let ghosts = client.detect_ghost_commands().await?;
assert!(ghosts["confirmed_ghosts"].as_array().unwrap().is_empty(),
"Found ghost commands: {ghosts}");
```
See the [Testing Guide](docs/src/testing.md) 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](docs/src/tools-reference.md).
---
## 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](docs/src/security.md) 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:
```yaml
- 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:
```bash
victauri init # generates .github/workflows/victauri.yml
```
---
## Documentation
- [**Getting Started**](docs/src/getting-started.md) — Setup, capabilities, first connection
- [**Testing Guide**](docs/src/testing.md) — Locator API, IPC verification, visual regression, CI integration
- [**Tools Reference**](docs/src/tools-reference.md) — All 35 tools with parameters and examples
- [**Architecture**](docs/src/architecture.md) — Embedded design, JS bridge, dual protocol
- [**Configuration**](docs/src/configuration.md) — Port, auth, privacy, capacity tuning
- [**Security**](docs/src/security.md) — Threat model, privacy profiles, redaction
- [**FAQ**](docs/src/faq.md) — Common questions and troubleshooting
- [**VS Code Extension**](editors/vscode/) — Live inspection from your editor
- [**Demo App**](examples/demo-app/) — Reference app with 21 instrumented commands
- [**Agent Session**](examples/agent-session.md) — Real AI agent transcript
- [**Migration Guide**](MIGRATION.md) — Upgrading between versions
- [**Contributing**](CONTRIBUTING.md) — How to contribute
- [**Changelog**](CHANGELOG.md) — Release history
---
## 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 control** — `fault` 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
```bash
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](https://4da.ai), 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](https://github.com/4DA-Systems/victauri/discussions) or issue. We'd love to add you to a "used by" list and learn what broke.
- **Want to contribute?** See [CONTRIBUTING.md](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](LICENSE)
Built and maintained by [4DA Systems](https://4da.ai).
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available