Skip to content

Screenshots

xa11y can capture pixel-level snapshots of the screen alongside its accessibility APIs. This is useful for attaching screenshots to test failures, feeding vision-language models with the same frame the agent is reasoning about, or visually diffing a region around a specific control.

The entry points are:

  • Full screen captures whatever the platform treats as the whole screen. On Windows that is the virtual desktop, which spans every monitor. macOS captures one display, and Linux the X11 root window.
  • Region captures an explicit rectangle in logical screen coordinates.
  • Element captures the pixels under an accessibility element’s current bounds.

Screenshots are decoupled from the accessibility and input layers. Capture never raises or activates the target window. Whatever pixels sit at the requested coordinates are returned, including any occluding windows. Bring the target to the foreground yourself if you need a clean capture.

Platform Requirement
macOS Screen & System Audio Recording for the terminal/IDE
Windows None for normal user sessions
Linux (X11) DISPLAY set to a reachable X server
Linux (Wayland) xdg-desktop-portal with screenshot support (user consent dialog may appear)
use xa11y::*;
fn main() -> Result<()> {
// Full screen
let shot = screenshot()?;
shot.save_png("full.png")?;
// Explicit region
let shot = screenshot_region(Rect { x: 0, y: 0, width: 400, height: 300 })?;
shot.save_png("corner.png")?;
// Pixels under an accessibility element
let app = App::by_name("Calculator", std::time::Duration::from_secs(5))?;
let display = app.locator("static_text[name='Result']").element()?;
let shot = screenshot_element(&display)?;
shot.save_png("result.png")?;
Ok(())
}

A Screenshot carries raw RGBA8 pixels along with dimensions and a scale factor. width and height are physical (device) pixels, the same resolution the compositor renders at, so on HiDPI displays they exceed the logical bounds passed in. scale records the physical-to-logical ratio (1.0 on standard displays, 2.0 on typical Retina, 1.5 / 1.75 / 2.0 on common Windows and Linux HiDPI configurations). pixels.len() equals width * height * 4.

let shot = screenshot()?;
println!("{} x {} @ {}x", shot.width, shot.height, shot.scale);
// Encode as PNG bytes (e.g. to upload or embed)
let png_bytes: Vec<u8> = shot.to_png()?;
// Or write directly to disk
shot.save_png("out.png")?;
// Raw RGBA for custom processing
let rgba: &[u8] = &shot.pixels;

A common pattern is to capture the full screen (or the app under test) whenever an assertion fails, and attach it to the test report.

fn capture_on_fail(name: &str, test: impl FnOnce() -> Result<()>) -> Result<()> {
match test() {
Ok(()) => Ok(()),
Err(e) => {
if let Ok(shot) = screenshot() {
let _ = shot.save_png(format!("target/failures/{name}.png"));
}
Err(e)
}
}
}

Capture raises four of xa11y’s error variants:

  • PermissionDenied when macOS Screen Recording is not granted, or the Wayland portal denied consent.
  • Unsupported when no capture backend covers the session, which a missing DISPLAY or an unresponsive Wayland portal will both produce.
  • NoElementBounds when the element passed to screenshot_element has no bounds.
  • Platform for a raw OS or FFI failure during capture or PNG encode.

The Python example above catches xa11y.XA11yError, which covers PermissionDenied, Unsupported, and Platform. NoElementBounds surfaces as a plain ValueError in Python, so catch that too when capturing by element. See Errors for the full mapping.