Find the right selector
Accessibility names are frequently something other than the label painted on screen, so a guessed selector is a coin flip. Read the tree first, every time.
For the syntax itself, see Selectors. For the CLI commands used here, see CLI.
Work out a selector for the first time
Section titled “Work out a selector for the first time”The CLI dumps a whole application in one command and behaves the same whatever language you are writing in, so start there.
# 1. What's running, and under what name?xa11y apps
# 2. See the full treexa11y tree --app MyApp
# 3. Try a selector against the live appxa11y find "button[name='Submit']" --app MyApp
# 4. Narrow it until exactly one element matchesxa11y find "dialog button[name='Submit']" --app MyAppxa11y find prints every match and a parenthesised count, so a selector that
matched seven things when you wanted one is visible before it reaches your
code. A selector matching nothing is an error (no elements matched selector: …) with a non-zero exit, rather than an empty list.
Read the tree from code instead
Section titled “Read the tree from code instead”dump() and tree() are available on App, Locator, and Element in every
binding. dump() returns the indented string, and tree() returns the same
data as a structured snapshot.
import xa11y
calc = xa11y.App.by_name("Calculator")
print(calc.dump()) # full indented tree, rooted at the appprint(calc.dump(max_depth=2)) # limit depthsnapshot = calc.tree() # same data as a nested dict
# Locators and elements also support dump() / tree()print(calc.locator("group[name='Keypad']").dump())use std::time::Duration;use xa11y::*;
let calc = App::by_name("Calculator", Duration::from_secs(5))?;
println!("{}", calc.dump(None)?); // full indented treeprintln!("{}", calc.dump(Some(2))?); // limit depthlet snapshot = calc.tree(None)?; // same data as a TreeNode
// Locators and elements also support dump() / tree()println!("{}", calc.locator("group[name='Keypad']").dump(None)?);import { App } from '@crowecawcaw/xa11y';
const calc = await App.byName('Calculator');
console.log(await calc.dump()); // full indented treeconsole.log(await calc.dump(2)); // limit depthconst snapshot = await calc.tree(); // same data as a nested object
// Locators and elements also support dump() / tree()console.log(await calc.locator("group[name='Keypad']").dump());max_depth of 0 returns the root node alone, 1 adds its direct children,
and so on. Omit it for the whole subtree.
Diagnose a selector that stopped matching
Section titled “Diagnose a selector that stopped matching”Start from the error. A timeout or a selector miss already names what the
locator was resolving, what it last observed, and which elements almost
matched, so read the candidates list before reaching for the CLI. See
Errors and diagnosis.
When you need to look at the live application:
# What does the tree actually look like right now?xa11y tree --app MyApp
# Is the element there under a different name?xa11y find "button" --app MyApp
# Watch events while you interact, to see what the app reports changingxa11y events --app MyAppThree failures account for most of them:
- The name differs from the on-screen label. Copy it from the tree output
character for character, or switch to a substring operator:
button[name^='Save']. - The element is there but not actionable. The diagnosis says
matched button "Export" (visible=false, enabled=true, focused=false)rather thanselector never matched. The selector is right and the wait condition is what is failing. - The element belongs to a different accessibility application. Some
toolkits register dialogs as their own application sharing the host process.
xa11y appsshows them as separate rows. See Attaching to the app under test.
Explore an unfamiliar application
Section titled “Explore an unfamiliar application”# What apps are running?xa11y apps
# What's the structure?xa11y tree --app Slack
# What can I interact with?xa11y find "[focusable='true']" --app SlackIf the tree comes back holding only an application and a window, the
application is not publishing its contents rather than being empty. On Linux
that is the Electron and Chromium default. See
Platform Details.