UI tree dump (JSON)
While Dump mode renders the UI tree into an image for humans, UI tree dump writes a machine-readable JSON sidecar next to the screenshot it describes, for tools and AI agents to read. When enabled, capturing MyTest.png also writes MyTest.uitree.json (the Compose semantics + View hierarchy of the current run) and, by default, an annotated MyTest.annotated.png (see Annotated image below).
The sidecar is written on record and compare/verify, next to the image the task writes: MyTest.uitree.json on record, and MyTest_actual.uitree.json in the compare output directory on compare/verify (an unchanged verify still writes it).
The sidecar is informational only: it never participates in image diffing and never fails verification. Bitmap-based captureRoboImage(Bitmap...) captures (which have no component tree) do not produce one.
Supported on Android/Robolectric, Compose Desktop, and Compose iOS, with identical output.
Enabling it
Via the Gradle property (no code change):
Or per capture via RoborazziOptions:
Format
The format is deliberately grep-first: one node per line with all of its scalar attributes, so a single grep finds a node and its coordinates.
boundsis[left, top, right, bottom]in RAW (unscaled) window pixel coordinates; the rootcaptureobject carries the outputimageWidth/imageHeightandscale. Map to image pixels withimage = (raw − root.bounds origin) × capture.scale— the root origin is0, 0for full-screen captures.Empty/default fields are omitted (no empty maps/lists, no
visibilitywhen the node is visible).nis a sequential (1-based, pre-order) number on annotatable nodes only — visible nodes with a test tag, aText/ContentDescription, or an action; a numberedMergeDescendantsnode's descendants are skipped.Output is deterministic: the same UI produces byte-identical JSON (no timestamps, no hashes).
Annotated image (Set-of-Mark)
"Set-of-Mark" refers to a prompting technique for vision-language models: overlaying numbered marks on image regions lets a model refer to a region unambiguously by its number (Yang et al., 2023).
Written by default next to the screenshot — a copy of the output with every numbered node drawn as a labelled box — under the same naming as the sidecar: MyTest.annotated.png on record, MyTest_actual.annotated.png on compare/verify (an unchanged verify annotates the identical golden).
Each box's number is the same n as in the JSON, so an agent can point at "element #3" and look up "n": 3 for its exact bounds, properties, and actions. The annotated image is a display artifact of the current run only — never compared, never failing a capture, never treated as a golden. Opt out with UiTreeDumpOptions(annotateImage = false).
Primary use case: letting an AI agent prove its UI fixes
AI agents are bad at judging small layout changes from screenshots — ask one to "add margin above the button" and it will often claim "fixed" when nothing moved. The deterministic UI tree gives it exact coordinates to check instead, so the agent can read the button's bounds before and after and prove the change actually landed.
Record and read the node line:
./gradlew recordRoborazziDebug -Proborazzi.dumpUiTree=true grep login_button build/outputs/roborazzi/MyTest.uitree.json # { "n": 1, "testTag": "login_button", "bounds": [16, 24, 204, 72], ... }boundsis[left, top, right, bottom], so the top edge is at24.Make the change, keeping that
beforevalue in context.Re-record (it overwrites the sidecar in place) and grep the same line:
# { "n": 1, "testTag": "login_button", "bounds": [16, 32, 204, 80], ... }Top moved
24 → 32— the button provably sits 8px lower. (To prove a gap, also compare the sibling above: itsbottomvs thistop.) Because the output is deterministic, any change in the numbers is a real layout change, not noise.