This example completes a real desktop task: open Chromium, reach a sample page, find and click a link, confirm the window title changed, and capture the screen. It uses the keyboard and accessibility tree throughout, with no pixel coordinates.
The example page is built inside the sandbox by the file and command planes, so the whole flow runs offline and gives consistent results.
The example uses these routes:
/v2/computer/actions/v2/computer/actions/batch/v2/computer/accessibility/nodes/v2/computer/windows/v2/computer/screenshotThe fixture also uses /v2/fs/write and /v2/commands.
GET /v2/sandbox reports computer under capabilities, plus files and exec for the fixture. The computer-use worker runs next to aiod on a host with a desktop and a running Chromium. The Computer image starts with an empty desktop, so open the browser first, from the desktop's Browser icon or by running /opt/gem/browser-launch.sh through exec:
The Computer image provides all of it (see Preset). Every /v2/computer/* route answers 503 while the worker is down.
The fixture serves itself with python3; any static server would do. Pick a port nothing else is on; 18782 is used below.
Two HTML files through the file plane, then a static server through the command plane. mode: "async" starts the process and returns at once, leaving it addressable by command_id:
The two pages carry what the desktop is searched by: a link whose accessible name is its own text, and a title per page that the window list can be polled for.
Waiting for the port matters. An async command answers before its process has bound anything, and a navigation that arrives too early lands on Chromium's error page instead of the fixture. The same probe proves the port is free again at the end.
Three steps, one script, through the shared Aio helper: navigate, act on an accessibility node, observe. BASE_URL, sb, the port and the fixture directory carry over from the block above.
Neither page change is waited out with a fixed sleep. wait_for_title polls the window list until a title says the desktop caught up, and gives up after ten seconds — the same shape as waiting for the port above. The screenshot route answers with the image itself, not with JSON, so it goes through sb.http; a PNG reply also carries x-image-width and x-image-height. The bytes go to the caller, and nothing is written inside the sandbox.
Notes from the run:
WINDOW_ACTIVATE asks the window manager and does not wait for the answer, which is the one WAIT in the script. Without the activation the keys land nowhere and the title never changes.nodes(role=..., name=...) matches by substring by default; pass match="exact" for an exact name, match="regex" for a pattern. The link's accessible name is its own text — the node's attributes say name-from: contents.--force-renderer-accessibility. Without it the desktop tree holds the panel and the desktop and no browser at all, and the search for the link comes back empty.NODE_SET_VALUE on it answers 409 node ... has neither editable text nor a value to set. The keyboard path (HOTKEY + TYPING) is the reliable way to enter a URL.node_id stays valid while the element exists; once the page it came from is gone, nodes(node_id=...) answers 404 node ... is gone.windows returns {snapshot_id, windows: [{window_id, title, process_id, bounds, minimized}]}. The title is the cheapest thing to poll, and it changes as soon as the navigation commits: both waits above returned on their first pass.You can also run PyAutoGUI on the same desktop. It uses screen coordinates instead of the accessibility tree, so coordinates must be checked again when the window moves. Run the script through /v2/commands; the desktop already has DISPLAY set:
Use the accessibility-tree flow when an element must be located reliably; use PyAutoGUI for direct desktop input.
The fixture server outlives the script unless it is stopped. commands/{id}/kill stops it, fs/delete removes the directory, and the port probe from the first block confirms the port came back:
The browser keeps showing the last page it loaded; nothing on the desktop has to be reset.
info reports the display, the resolution, and the supported operations:
Scale model coordinates to screen_resolution before a pixel action.
include_screenshot=true returns the post-action frame as a base64 PNG in the top-level screenshot field — that is what the screenshot flag on act sends:
On Windows, input aimed at a secure desktop (a UAC prompt, the lock screen) is refused with 403.
Send a sequence in one request when no decision is needed between steps. On this route include_screenshot is a body field next to actions, not a query parameter, so sb.post carries it:
Limits: 50 actions per batch, 10 s per WAIT, 20 s of waiting per batch. A failed action stops the batch; data.failed_index and data.error say which and why.
One screenshot and one action per step:
A model that decides from one frame per step needs the desktop to have settled before that frame is taken; where the loop can check a title or a node instead, poll for it as the story above does.
Start and stop through the same route:
Fetch the file with the File API.
The prebuilt Computer image provides:
DISPLAY=:99computer-use worker next to aiod--force-renderer-accessibility/vnc501/503 distinction