Skip to content
JDesk
Guides

Automation & E2E

Drive a JDesk application through the opt-in automation module for end-to-end tests and CI.

JDesk has an explicitly opt-in automation endpoint — the equivalent of Electron's --remote-debugging-port or tauri-driver — so tests, CI, and agents can drive a real running app without OS-level screenshots and coordinate clicking.

Start the app with automation enabled#

Automation is deliberately a separate runtime-only module. The JDesk Gradle plugin adds it to run and jdeskDev, but not to jdeskPackage. For a manually wired launcher, add it only to that launcher's E2E/dev configuration:

val e2eRuntimeOnly by configurations.creating

dependencies {
    e2eRuntimeOnly("dev.jdesk:jdesk-automation:<jdeskVersion>")
}
java -Djdesk.automation=true ... your.app.Main
# or in a Gradle run task: systemProperty("jdesk.automation", "true")

On startup the runtime prints one line and writes a descriptor file:

JDESK-AUTOMATION port=52731 descriptor=/Users/you/.jdesk/automation/<appId>.json

The descriptor ({pid, port, token}, owner-only permissions; directory overridable via -Djdesk.automation.dir=) carries the per-run bearer token. The server binds 127.0.0.1 only and answers nothing without Authorization: Bearer <token>. Without the system property, no server exists. Production images that omit jdesk-automation also omit jdk.httpserver; requesting automation without the provider fails startup loudly. Request bodies are capped at 1 MiB.

Endpoints#

EndpointMeaning
GET /windows{"windows":["main", ...]} — open window ids
POST /evaluate {"window":"main","script":"..."}Evaluates JS; returns {"result":<parsed JSON>, "value":"<raw string>"}result is the JSON-decoded value (objects/arrays/numbers), value the raw string for back-compat
POST /input `{"window":"main","action":"clicktype
GET /snapshot?window=mainPNG screenshot of the real WebView
GET /console?window=mainCaptured page console lines (console.*, uncaught errors, and the earliest module/parse-load failures)

A minimal E2E check#

DESC=~/.jdesk/automation/com.example.app.json
PORT=$(python3 -c "import json;print(json.load(open('$DESC'))['port'])")
TOKEN=$(python3 -c "import json;print(json.load(open('$DESC'))['token'])")
AUTH="Authorization: Bearer $TOKEN"

curl -s -H "$AUTH" "http://127.0.0.1:$PORT/windows"
# /evaluate returns a real JSON value under `result`:
curl -s -H "$AUTH" -X POST "http://127.0.0.1:$PORT/evaluate" \
     -d '{"window":"main","script":"({count: document.querySelectorAll(\"li\").length})"}'
# → {"result":{"count":3},"value":"{\"count\":3}"}
# /input clicks a button, then read the effect back:
curl -s -H "$AUTH" -X POST "http://127.0.0.1:$PORT/input" \
     -d '{"window":"main","action":"click","selector":"#submit"}'
curl -s -H "$AUTH" "http://127.0.0.1:$PORT/snapshot?window=main" -o shot.png
curl -s -H "$AUTH" "http://127.0.0.1:$PORT/console?window=main"

/input synthesizes real DOM events (click/type/focus/hover/key) on the element matched by selector — enough to drive most flows. Note these are DOM events (isTrusted=false), so real OS-level hover-CSS and IME composition are not reproduced; for those, use /evaluate to call your app's own hooks. Because everything runs inside the real page, it flows through the real bridge, capability checks, and command handlers.

Use /input to interact, /evaluate to observe. /evaluate runs its script in an isolated world (a separate JS context that shares the DOM but not the page's variables or event listeners). Reads work — document.getElementById('x').value, location.href, calling window-exposed hooks — but document.querySelector('#btn').click() from /evaluate will NOT fire the listeners your page registered, and it is a common trap. To click, type, or focus and have the page react, always use /input (it dispatches into the page's own world). Reserve /evaluate for reading state and asserting results.

Earliest failures too: if the page crashes in a module import or a parse error before any script runs, /console still shows it — the capture script installs its error listeners at document-start in the capture phase, so even a failed <script type="module" src> load is recorded (native-smoke case java:early-error-capture).

Verified live on macOS (native-smoke case java:automation-endpoint): windows listing, 401 on missing token, real WKWebView PNG snapshot, and console-marker retrieval over loopback HTTP.