Skip to main content
Ask most browser tooling to test a desktop app and the first question is “what URL do I open?”. There isn’t one. That is the whole problem, and it is why desktop verification is mostly unserved. Reticle inverts the direction. Your app dials the local daemon, the agent drives it through the same tools it uses for the web, and the verdicts read the same. No Chromium to launch, no screenshot to interpret.

The thing nobody else does

A desktop app’s most interesting failures happen at the IPC boundary. The renderer asks the main process for something, and the main process quietly fails. The renderer cannot see that. A screenshot certainly cannot. Reticle records it as a request:
Then it contradicts a UI that moved on regardless:
The app said it loaded. One IPC call in the same window returned 500. Reticle puts both in front of the agent instead of believing the happier one. Left, an app window showing three synced todo items and nothing wrong. Right, Reticle's view of the same moment: the todos:loaded signal, an ipc://todos:archive call returning 500, and a signal-contradicted finding naming both
The window on the left is an illustration. The JSON on the right is real output from the desktop battery, which starts a real Electron main process and a packaged Tauri binary and drives them headless on every change.

What works, and what is proven

Bold rows are re-proven by pnpm test:e2e:desktop on every change. The rest were measured by hand. That distinction is deliberate. This table used to report a hand-run score with nothing in the repo reproducing it, so it could go stale without anything failing.

Three things that are genuinely different

It works occluded

Minimized, app-hidden, behind a fullscreen app on another Space. The agent keeps driving.

Screenshots cannot lie

Captured from the window’s own backing store, never the screen.

Missing wiring is declared

Electron without the preload reports coverage: partial, not a clean pass.

Why screenshots read the backing store

webContents.capturePage() reads the window’s own pixels. Capturing a screen region was tried and deliberately rejected. A region capture photographs whatever is on top. An app window behind your editor yields a picture of the editor, saved as a visual baseline that a later diff would trust. A screenshot tool that can silently return another window’s pixels manufactures exactly the false green Reticle exists to eliminate.
One honest caveat. A fully occluded or minimized window is only partially composited, so parts of a capture may come back blank. Bring the window forward for a complete image. It is never the wrong window.

Why a missing preload is not a silent pass

Without @reticlehq/electron/preload, every IPC call is invisible to Reticle. A tool that just reported clean there would be telling you the IPC layer is fine when it never looked. Instead verdicts carry coverage: partial and name the missing line.

Getting started

The workflow has no browser step in it at all.
  1. Start the daemon once.
  2. Start your app exactly as you always do.
  3. Confirm it connected.
    Your window appears as a session, and the agent can drive it.

Electron

Screenshots are one line in the main process:
No CDP flag, no extra packages, and it works on a packaged file:// renderer.

Tauri

The frontend is identical to any web app. The CSP is not optional:
Tauri’s default CSP blocks the bridge WebSocket before it opens. Your app runs perfectly and simply never connects, with no error to go on.Keep ipc: http://ipc.localhost in connect-src, because Tauri v2 needs it for invoke itself. Drop the ws:// entries from your release config.
IPC observation needs nothing on the Rust side. An invoke('load_todos') already reaches Reticle as ipc://load_todos. Screenshots and headless mode need the crate, which is versioned independently of the npm packages:

Keep reading

Full desktop guide

Every capability in detail, what IPC looks like to an agent, and desktop troubleshooting.

Manual install

The wiring for both runtimes, step by step.
Last modified on August 14, 2026