xa11y

xa11y — Cross-Platform Accessibility Client Library for Python.

Quick start:
>>> import xa11y
>>> app = xa11y.locator('application[name="Safari"]')
>>> app.child("button[name='Submit']").press()
>>> for element in app.descendant("button").elements():
...     print(element.name)

Exceptions

AccessibilityNotEnabledError

The target app advertises an accessibility tree but it is empty.

ActionNotSupportedError

The requested action is not supported on the target element.

InvalidActionDataError

An action received invalid data (e.g. Locator.nth(0), or a

InvalidSelectorError

The selector string has invalid syntax.

PermissionDeniedError

Accessibility permissions have not been granted.

PlatformError

An OS-level accessibility error occurred.

SelectorNotMatchedError

No element in the tree matched the given selector.

TimeoutError

An operation exceeded its timeout.

XA11yError

Base exception for all xa11y errors.

Classes

App

A running application — the entry point for accessibility queries.

Element

A live element with lazy navigation.

Event

An accessibility event delivered to subscribers.

EventType

Accessibility event type constants.

InputSim

Input-simulation façade for synthesised pointer and keyboard events.

LegendEntry

One drawn annotation box: the tag in the image, and the element it

Locator

A resilient element reference that re-queries on each interaction.

Omission

An element that matched an annotate= selector but is not in the

Rect

A bounding rectangle in logical screen coordinates (device-independent

Screenshot

A captured image: raw RGBA8 pixels plus dimensions and mapping.

ShellSurface

One OS-owned shell surface — the taskbar, a desktop panel, the dock, the

Subscription

A live event subscription.

Functions

get_default_timeout(→ float)

Get the effective process-wide default timeout, in seconds.

input_sim(→ InputSim)

Construct an InputSim backed by the platform's native input path.

locator(→ Locator)

Create a top-level Locator searching from the system root.

screenshot(→ Screenshot)

Capture pixels from the screen.

set_default_timeout(→ None)

Set the process-wide default timeout, in seconds.

Package Contents

exception xa11y.AccessibilityNotEnabledError

Bases: XA11yError

The target app advertises an accessibility tree but it is empty.

Raised on Linux when a Chromium/Electron app is launched without --force-renderer-accessibility (or the ACCESSIBILITY_ENABLED=1 environment variable), so the renderer accessibility bridge never populates the window’s subtree.

Initialize self. See help(type(self)) for accurate signature.

exception xa11y.ActionNotSupportedError

Bases: XA11yError

The requested action is not supported on the target element.

Initialize self. See help(type(self)) for accurate signature.

exception xa11y.InvalidActionDataError

Bases: XA11yError

An action received invalid data (e.g. Locator.nth(0), or a text-selection range with start > end).

Initialize self. See help(type(self)) for accurate signature.

exception xa11y.InvalidSelectorError

Bases: XA11yError

The selector string has invalid syntax.

Initialize self. See help(type(self)) for accurate signature.

exception xa11y.PermissionDeniedError

Bases: XA11yError

Accessibility permissions have not been granted.

Initialize self. See help(type(self)) for accurate signature.

exception xa11y.PlatformError

Bases: XA11yError

An OS-level accessibility error occurred.

Initialize self. See help(type(self)) for accurate signature.

exception xa11y.SelectorNotMatchedError

Bases: XA11yError

No element in the tree matched the given selector.

Carries a structured diagnosis (tenet 6) so the failure is understandable without re-running it under manual tree dumps. All attributes are always present (None / [] when not applicable); the same content is rendered into the exception message.

Initialize self. See help(type(self)) for accurate signature.

candidates: list[str]

Bounded near-miss candidates, e.g. same-role elements with different names, or the running applications for app lookups.

condition: str | None

What the operation was waiting for / trying to find, if known.

elapsed: float | None

Always None for this class (present for attribute parity with TimeoutError).

last_observed: str | None

What the failing operation last observed (e.g. 'selector matched 2 element(s); nth(5) requested').

scope: str | None

Bounded rendering of the search scope: a depth-limited tree dump for scoped locators, or the application list for rootless ones.

selector: str | None

The selector that failed to match.

exception xa11y.TimeoutError

Bases: XA11yError

An operation exceeded its timeout.

Carries a structured diagnosis (tenet 6): what the wait was for (condition + selector), what it last observed, and — when the selector never matched — bounded scope context (candidates + scope). All attributes are always present (None / [] when not applicable); the same content is rendered into the message, e.g.:

Timeout after 60.0s; waiting for: visible; selector:
dialog[name^="Submit"]; last observed: selector never matched;
candidates: window "Untitled — MyApp"
search scope (bounded):
...

Initialize self. See help(type(self)) for accurate signature.

candidates: list[str]

Bounded near-miss candidates (same role, different attributes). Collected only when the selector never matched during the wait.

condition: str | None

What the wait was for: 'visible', 'attached', 'press target actionable (visible && enabled)', 'event matching predicate', 'custom predicate', …

elapsed: float | None

Wall-clock seconds the operation waited before giving up.

last_observed: str | None

The last poll’s observation: 'matched button "Export" (visible=false, enabled=true, focused=false)' vs 'selector never matched'.

scope: str | None

Bounded rendering of the search scope. Collected only when the selector never matched during the wait.

selector: str | None

The selector being resolved, when the wait had one.

exception xa11y.XA11yError

Bases: Exception

Base exception for all xa11y errors.

Initialize self. See help(type(self)) for accurate signature.

class xa11y.App

A running application — the entry point for accessibility queries.

as_element() → Element

Get an Element handle for the application root.

Useful for invoking Element-level methods (children(), parent(), etc.) without going through a locator.

static by_name(name: str, *, timeout: float | None = None) → App

Find an application by exact name.

Polls the accessibility API until the app appears or timeout (seconds) elapses. timeout=None (the default) uses the process-wide default timeout — 5 seconds unless overridden via set_default_timeout() or the XA11Y_DEFAULT_TIMEOUT environment variable. Pass timeout=0 for a single attempt with no waiting. Only “not found” errors trigger a retry; other errors fail fast.

static by_pid(pid: int, *, timeout: float | None = None) → App

Find an application by process ID.

This is the supported way to wait for a freshly launched process to surface in the accessibility tree: the lookup polls until the app becomes reachable through the platform bridge or timeout (seconds) elapses, covering the gap between the process starting and its accessibility registration completing. There is no need to hand-roll a poll over App.list(). Where the platform supports it (macOS AX, Windows UIA), the lookup attaches to the process directly instead of filtering app enumeration, so an app whose window is still unnamed mid-startup is found as soon as the accessibility API can reach it. See by_name for timeout semantics.

children() → list[Element]

Get direct children (typically windows) of this application.

dump(max_depth: int | None = None) → str

Render this application’s accessibility tree as an indented string.

Returns the string without printing it. Same depth semantics as tree(). This is the primary inspection helper — call print(app.dump()) to discover the role and name of every element in the app before writing selectors.

For the same output from the shell, use xa11y tree --app NAME.

static find(predicate: collections.abc.Callable[[App], bool], *, timeout: float | None = None) → App

Find an application matching predicate.

predicate is called with an App for each running application on every poll; the first for which it returns truthy is returned. A falsy return means “not this one, keep polling”; if the predicate raises, the search aborts immediately and that exception propagates. See by_name for timeout semantics. There is no need to hand-roll a poll over App.list().

Use this when neither a name nor a PID alone identifies the target — e.g. a Qt dialog that registers as its own accessibility application sharing the host process’s PID:

app = xa11y.App.find(
    lambda a: a.pid == pid and a.name.startswith("My Dialog"),
    timeout=30.0,
)
static foreground(*, timeout: float | None = None) → App

Resolve the application that currently holds the system foreground.

Queries the platform’s foreground mechanism directly rather than enumerating and tagging by pid. The result is the foreground process’s Application node — one per process on every platform — not the exact window holding the focus; on Windows that is the synthesized node of the foreground process (the modal case, issues #304/#305), and this stays reliable when an app shows a modal dialog. To reach the exact foreground window, call windows() and pick the entry reporting active. “Nothing focused” retries until timeout; see by_name for timeout semantics. The returned app has is_foreground == True.

static list() → list[App]

List all running applications.

Single enumeration, no polling. To wait for an app that is still starting up, use by_pid / by_name / find with a timeout instead of polling this in a loop.

locator(selector: str) → Locator

Create a Locator scoped to this application’s accessibility tree.

subscribe() → Subscription

Subscribe to accessibility events from this application.

tree(max_depth: int | None = None) → dict

Capture this application’s accessibility tree as a recursive dict.

Each dict has keys role, name, value, and children (a list of dicts with the same shape). max_depth limits traversal: 0 = only the application node, 1 = application + direct children (typically windows), None = full subtree.

Equivalent to Element.tree(...) on the application’s root element.

windows() → list[Element]

List the top-level windows of this application.

Each call queries the provider; results are not cached. The windows are the application’s top-level windows with role window or dialog, in enumeration order. On Windows the application entry is a synthesized process node whose children are the process’s top-level windows (main window plus modal dialogs). On Linux the answer is process-complete: App::windows_with merges the filtered children of every same-pid AT-SPI Application entry (an app that registers several entries reports its whole process), so the results need not be the direct children of this node and no single z-order spans them. Calling windows on a non-application element fails with ActionNotSupportedError.

property focused: bool

Deprecated alias for is_foreground.

Deprecated since version Use: is_foreground. “focused” refers to element keyboard focus elsewhere in the API; accessing this property emits a DeprecationWarning.

property is_foreground: bool

Whether this application is the foreground application.

Named is_foreground because “focused” is reserved for element-level keyboard focus (Element.focused) elsewhere in the API; this is the foreground-application flag. Populated for apps obtained via list() and find(). A point-in-time snapshot taken when the App was resolved.

Tagging is window-precise. On Windows apps surface as one synthesized Application node per process, so a pid match there is unambiguous; on Linux the AT-SPI registry can surface several entries for one pid, and only the entry reporting the window-level active flag gets is_foreground. Use foreground() to resolve the foreground application directly, then pick the exact foreground window from its windows().

property name: str
property pid: int | None
class xa11y.Element

A live element with lazy navigation.

activate() → None

Activate this window: bring it to the foreground and give it focus.

blur() → None

Remove keyboard focus from this element.

children() → list[Element]

Get direct children (lazy — each call queries the provider).

close() → None

Close this window.

collapse() → None

Collapse this element.

decrement() → None

Decrement this element’s value.

dump(max_depth: int | None = None) → str

Render the subtree rooted at this element as an indented string.

Returns the string without printing. Same depth semantics as tree(). Useful as a first step when writing a test against an unfamiliar app — call print(app.dump()) to discover the role and name of every element, then turn those into selectors.

For the same output from the shell, use xa11y tree --app NAME.

enter_fullscreen() → None

Put this window in native fullscreen (macOS AXFullScreen).

Distinct from maximize; restore leaves fullscreen. Raises ActionNotSupportedError on platforms with no fullscreen accessibility API (Windows/Linux).

expand() → None

Expand this element (e.g. tree node, combo box).

focus() → None

Move keyboard focus to this element.

increment() → None

Increment this element’s value (e.g. slider, spinner).

maximize() → None

Maximize this window.

Distinct from enter_fullscreen: this drives the platform’s maximized state, not native fullscreen. Raises ActionNotSupportedError on a platform without an accessible maximize (macOS exposes no readable or writable zoom state).

minimize() → None

Minimize this window.

move_to(x: int, y: int) → None

Move this window to the given logical screen coordinates (top-left origin).

parent() → Element | None

Get parent element (lazy — each call queries the provider).

perform_action(action: str) → None

Perform an action by snake_case name.

press() → None

Click / invoke this element.

resize_to(width: int, height: int) → None

Resize this window to the given logical width and height.

Raises InvalidActionDataError if either dimension is 0. OverflowError if either is negative or exceeds u32.

restore() → None

Restore this window to its normal state (from minimized/maximized/fullscreen).

scroll_into_view() → None

Scroll this element into view.

select() → None

Select this element (e.g. list item, tab).

select_text(start: int, end: int) → None

Select the text range from start to end (0-based offsets).

set_numeric_value(value: float) → None

Set this element’s numeric value.

set_value(value: str) → None

Replace this element’s text value.

show_menu() → None

Show this element’s context menu.

subscribe() → Subscription

Subscribe to accessibility events for this element (typically an app).

toggle() → None

Toggle this element’s checked state.

tree(max_depth: int | None = None) → dict

Capture the subtree rooted at this element as a recursive dict snapshot.

Each dict has keys role, name, value, and children (a list of dicts with the same shape). max_depth limits traversal: 0 = only this node, 1 = node + direct children, None = full subtree.

Use this when you need to inspect or analyze the tree programmatically. For an indented human-readable string, see dump(). For ad-hoc exploration from the shell, see the xa11y tree CLI command.

type_text(text: str) → None

Insert text at the current cursor position.

property actions: list[str]
property active: bool

Whether this element is the active (foreground) window.

The window that currently receives the user’s input. Meaningful for window-like elements (windows, dialogs); False elsewhere. Distinct from focused, which is element-level keyboard focus.

property bounds: Rect | None
property busy: bool
property checked: str | None

Tri-state toggle value: 'on', 'off', 'mixed', or None.

None means the element has no checked state (it is not a checkbox / radio button / toggle). These four are the only possible values. Compare against them explicitly — bool(element.checked) is True for every non-None value, including 'off':

is_checked = element.checked == "on"
property description: str | None
property editable: bool
property enabled: bool
property expanded: bool | None
property focusable: bool
property focused: bool
property fullscreen: bool | None

Whether the window is fullscreen. None = unknown / not a window.

property max_value: float | None
property maximized: bool | None

Whether the window is maximized. None = unknown / not a window.

property min_value: float | None
property minimized: bool | None

Whether the window is minimized.

None means unknown (the element is not a window, or the platform cannot report the state). Never a guessed False.

property modal: bool
property name: str | None
property numeric_value: float | None
property pid: int | None
property raw: dict[str, object]

Platform-specific raw data attached to this element.

Keys are provider-defined (e.g. "ax_role" on macOS, "uia_control_type" on Windows). Values are JSON-compatible (strings, numbers, booleans, lists, nested dicts). Intended for debugging and platform-specific queries — prefer the cross-platform fields (role, name, etc.) for portable logic.

property required: bool
property role: str
property selected: bool
property stable_id: str | None
property value: str | None
property visible: bool
class xa11y.Event

An accessibility event delivered to subscribers.

property app_name: str
property app_pid: int
property event_type: str
property state_flag: str | None

For state_changed events: the flag that changed (e.g. 'checked').

None for other event types.

property state_value: bool | None

For state_changed events: the new boolean value of the flag.

None for other event types.

property target: Element | None
class xa11y.EventType

Accessibility event type constants.

Each constant’s value is the string carried in Event.event_type, so handlers can compare against the constant or the literal string interchangeably.

ANNOUNCEMENT: str = 'announcement'
FOCUS_CHANGED: str = 'focus_changed'
MENU_CLOSED: str = 'menu_closed'
MENU_OPENED: str = 'menu_opened'
NAME_CHANGED: str = 'name_changed'
SELECTION_CHANGED: str = 'selection_changed'
STATE_CHANGED: str = 'state_changed'
STRUCTURE_CHANGED: str = 'structure_changed'
TEXT_CHANGED: str = 'text_changed'
VALUE_CHANGED: str = 'value_changed'
WINDOW_ACTIVATED: str = 'window_activated'
WINDOW_CLOSED: str = 'window_closed'
WINDOW_DEACTIVATED: str = 'window_deactivated'
WINDOW_OPENED: str = 'window_opened'
class xa11y.InputSim

Input-simulation façade for synthesised pointer and keyboard events.

Targets are either a (x, y) tuple in logical screen coordinates (same space as Element.bounds), or an Element (uses its bounds centre); each backend converts to physical device pixels at the OS boundary. Key values are strings: printable characters are literal ("a", "7", ";"); named keys use their Pascal name ("Enter", "ArrowUp", "F5"); modifiers are "Shift", "Ctrl", "Alt", "Meta".

Input simulation is distinct from the accessibility action layer — prefer Locator.press() / Locator.type_text() when the target exposes the semantic action. Use InputSim for gestures with no a11y equivalent (drag-and-drop, scroll wheels, global shortcuts).

chord(key: str, held: list[str] = ...) → None

Tap key while the keys in held are held down.

click(target: tuple[int, int] | Element, *, button: MouseButtonName = 'left', count: int = 1, held: list[str] | None = None, anchor: AnchorName | tuple[int, int] | None = None) → None

Click at target.

button selects the mouse button, count repeats the click (2 = double-click), held lists keys held down for the duration, and anchor picks the point inside an Element’s bounds — a named anchor or an (dx, dy) offset from its top-left corner. anchor is ignored when target is a raw tuple.

double_click(target: tuple[int, int] | Element) → None

Left double-click at target.

drag(start: tuple[int, int] | Element, end: tuple[int, int] | Element, *, button: MouseButtonName = 'left', held: list[str] | None = None, duration: float = 0.15) → None

Drag from start to end.

button selects the button held during the drag, held lists keys held for its duration, and duration is the total drag time in seconds.

key_down(key: str) → None

Press key without releasing it. Pair with key_up().

For a whole tap use press(); to hold modifiers around one tap use chord(). This is the primitive for sequences neither expresses, such as holding a key across several other actions.

key_up(key: str) → None

Release a key previously pressed with key_down().

mouse_down(button: MouseButtonName = 'left') → None

Press a mouse button at the current pointer location, without releasing it.

Pair with mouse_up(); for a whole click use click(). Takes no target — the button is pressed wherever the pointer already is, so call move_to() first to position it.

mouse_up(button: MouseButtonName = 'left') → None

Release a mouse button at the current pointer location.

move_to(target: tuple[int, int] | Element) → None

Move the pointer to target without pressing any button.

press(key: str) → None

Tap a key (press + release).

right_click(target: tuple[int, int] | Element) → None

Right-click at target.

scroll(target: tuple[int, int] | Element, dx: int = 0, dy: int = 0) → None

Scroll at target. dx positive → right, dy positive → down.

type_text(text: str) → None

Type literal text into the currently focused control.

class xa11y.LegendEntry

One drawn annotation box: the tag in the image, and the element it came from.

A model reads a tag off the PNG; selector is what acts on the element that tag labels:

entry = next(e for e in shot.legend if e.tag == "B1")
app.locator(entry.selector).set_value("42")
property bounds: Rect

The element’s bounds in logical screen coordinates.

property color: tuple[int, int, int]

The box colour as an (r, g, b) tuple, for correlating a box with its entry by eye.

property group: int

1-based, matching the position of this element’s locator in annotate=.

property index: int

1-based, and exactly the :nth(n) argument in selector.

property name: str | None

The element’s accessible name, when it has one.

property role: str

The element’s role, snake_case as everywhere else.

property selector: str

A selector usable as-is against the same scope the group had — "button:nth(7)".

property tag: str

What is drawn in the box — "B7". A group letter and a 1-based index, with no separator that downscaling could lose.

class xa11y.Locator

A resilient element reference that re-queries on each interaction.

Locators never hold a live reference to a UI element. Instead, they store a selector and resolve it on demand, making them immune to staleness. Action methods auto-wait for the element to appear before acting, using the process-wide default timeout (5 seconds unless overridden via set_default_timeout() or the XA11Y_DEFAULT_TIMEOUT environment variable). wait_* methods use the same default when no explicit timeout= is passed.

activate() → None

Activate the matched window: bring it to the foreground and give it focus.

blur() → None

Remove keyboard focus from the matched element.

child(selector: str) → Locator

Return a new Locator scoped to direct children matching selector.

close() → None

Close the matched window.

collapse() → None

Collapse the matched element.

count() → int

Count matching elements.

decrement() → None

Decrement the matched element (slider, spinner).

descendant(selector: str) → Locator

Return a new Locator scoped to descendants matching selector.

dump(max_depth: int | None = None) → str

Render the subtree rooted at the matched element as an indented string.

Returns the string without printing it. Same depth and resolution semantics as tree().

element() → Element

Get a single Element handle for the matched element.

elements() → list[Element]

Get all matching elements.

enter_fullscreen() → None

Put the matched window in native fullscreen.

Distinct from maximize; restore leaves fullscreen.

exists() → bool

Check if a matching element exists.

expand() → None

Expand the matched element.

first() → Locator

Return a new Locator that selects the first match.

focus() → None

Set keyboard focus on the matched element.

increment() → None

Increment the matched element (slider, spinner).

maximize() → None

Maximize the matched window.

Distinct from enter_fullscreen: this drives the platform’s maximized state, not native fullscreen.

minimize() → None

Minimize the matched window.

move_to(x: int, y: int) → None

Move the matched window to the given logical screen coordinates (top-left origin).

nth(n: int) → Locator

Return a new Locator that selects the n-th match (1-based).

perform_action(action: str) → None

Perform an action by snake_case name.

press() → None

Click / invoke the matched element.

resize_to(width: int, height: int) → None

Resize the matched window to the given logical dimensions.

Raises InvalidActionDataError if either dimension is 0, before any auto-wait polling begins. OverflowError if either is negative or exceeds u32.

restore() → None

Restore the matched window to its normal state (from minimized, maximized, or fullscreen).

This is the inverse of minimize, maximize, and enter_fullscreen: it clears every special state the platform can clear. There is deliberately no separate “exit fullscreen” method — leaving fullscreen is the same absolute state write this performs.

scroll_into_view() → None

Scroll the matched element into the visible area.

select() → None

Select the matched element (list item, tab, etc.).

select_text(start: int, end: int) → None

Select a text range within the matched element (0-based offsets).

set_numeric_value(value: float) → None

Set the numeric value of the matched element (slider, spinner).

set_value(value: str) → None

Set the text value of the matched element.

show_menu() → None

Show the context menu for the matched element.

toggle() → None

Toggle the matched element (checkbox, switch).

tree(max_depth: int | None = None) → dict

Capture the subtree rooted at the matched element as a recursive dict.

Each dict has keys role, name, value, and children (a list of dicts with the same shape). max_depth limits traversal: 0 = only this node, 1 = node + direct children, None = full subtree.

Resolves the selector once; fails fast with SelectorNotMatchedError if no match — does not auto-wait.

type_text(text: str) → None

Type text at the current cursor position on the matched element.

wait_attached(timeout: float | None = None) → Element

Wait until the element exists in the tree.

wait_detached(timeout: float | None = None) → None

Wait until the element is removed from the tree.

wait_disabled(timeout: float | None = None) → Element

Wait until the element is disabled.

wait_enabled(timeout: float | None = None) → Element

Wait until the element is enabled.

wait_focused(timeout: float | None = None) → Element

Wait until the element has keyboard focus.

wait_hidden(timeout: float | None = None) → None

Wait until the element is hidden or removed.

wait_unfocused(timeout: float | None = None) → Element

Wait until the element does not have keyboard focus.

wait_until(predicate: collections.abc.Callable[[Element | None], bool], timeout: float | None = None) → None

Wait until an arbitrary predicate is satisfied.

A predicate that raises aborts the wait immediately and propagates the exception — it is not swallowed as “not yet” (which would resurface later as a misleading timeout). Mirrors App.find.

wait_visible(timeout: float | None = None) → Element

Wait until the element is visible, polling the provider.

timeout=None (the default) uses the process-wide default timeout — see set_default_timeout(). Applies to all wait_* methods.

property selector: str

The CSS-like selector string for this locator.

class xa11y.Omission

An element that matched an annotate= selector but is not in the image.

Reported rather than dropped: a legend that disagreed with the picture, with no way to find out why, is what this exists to prevent.

property name: str | None

The element’s accessible name, when it has one.

property reason: OmissionReasonName

Why it could not be drawn: no bounds at all, zero-area bounds, or bounds outside what was captured (a second monitor, or outside an explicit region).

property role: str

The element’s role, snake_case.

property selector: str

The selector that would reach this element.

class xa11y.Rect

A bounding rectangle in logical screen coordinates (device-independent points) on every platform, origin at the top-left of the primary display.

Same coordinate space as screenshot(region=...) and input targets, so bounds pass straight through. To index into a captured image multiply by the screenshot’s scale (physical = logical * scale).

property height: int
property width: int
property x: int
property y: int
class xa11y.Screenshot

A captured image: raw RGBA8 pixels plus dimensions and mapping.

width and height describe the actual returned image. scale is a compatibility hint for simple single-display captures; use the explicit coordinate methods for mixed-DPI, cropped, or resized images. pixels has length width * height * 4.

legend, omitted and truncated describe what annotate= drew. They are [], [] and 0 on an unannotated capture, so consumers need no version check.

crop(x: int, y: int, width: int, height: int) → Screenshot

Crop by image pixels while preserving coordinate mapping.

desktop_rect_to_image(x: int, y: int, width: int, height: int) → list[Rect]

Convert a desktop rectangle to one image rectangle per display piece.

desktop_to_image(x: int, y: int) → tuple[int, int]

Convert a desktop point to a pixel in this image.

image_to_desktop(x: int, y: int) → tuple[int, int]

Convert an image pixel to a desktop point suitable for input.

resize(width: int, height: int) → Screenshot

Resize image pixels while scaling coordinate mapping.

save_png(path: str | bytes | object) → None

Encode as PNG and write to path. Accepts str, bytes or os.PathLike.

to_png() → bytes

Encode the image as a PNG and return the bytes.

property height: int
property legend: list[LegendEntry]

One entry per drawn box, in group order and then match order.

property mapping_available: bool

Whether desktop/image coordinate conversion metadata is available.

property omitted: list[Omission]

Elements that matched an annotate= selector but could not be drawn, each with the reason.

property pixels: bytes

Raw RGBA8 pixel bytes (width * height * 4).

property scale: float
property truncated: int

How many matched elements were not described at all because the annotation cap was reached. 0 when the cap did not bite.

property width: int
class xa11y.ShellSurface

One OS-owned shell surface — the taskbar, a desktop panel, the dock, the menu bar, a process’s status items, the desktop, or an open flyout.

ShellSurface is not an Element. It represents the surface as a whole and provides a locator() to search its accessibility tree, exactly as App does for an application.

as_element() → Element

Get an Element handle for the surface root.

Useful for invoking Element-level methods (children(), parent(), etc.) without going through a locator.

static by_kind(kind: ShellSurfaceKindName, *, timeout: float | None = None) → ShellSurface

Wait for exactly one shell surface of kind.

kind is the snake_case kind name — ShellSurfaceKindName above is the full set, and is the only place this file spells them out. An unknown name raises ValueError before the accessibility API is touched.

Polls until a single surface of that kind exists or timeout (seconds) elapses — see App.by_name for timeout semantics. The wait is what makes the Windows overflow workflow a one-liner: press the taskbar’s “Show Hidden Icons” button, then wait for the flyout:

taskbar = xa11y.ShellSurface.by_kind("taskbar", timeout=0)
taskbar.locator("button[name='Show Hidden Icons']").press()
flyout = xa11y.ShellSurface.by_kind("flyout", timeout=3.0)

Raises SelectorNotMatchedError both when no surface of that kind is present and when several are — ambiguity is refused rather than first-matched, with the candidates on the exception’s candidates attribute so the caller can disambiguate via list() and a pid.

children() → list[Element]

Get direct children of the surface root.

dump(max_depth: int | None = None) → str

Render this surface’s accessibility tree as an indented string.

Returns the string without printing it. Same depth semantics as tree(). This is the primary inspection helper — call print(surface.dump()) to discover the role and name of every element in the surface before writing selectors.

static list() → list[ShellSurface]

List the OS shell surfaces currently on screen.

Single enumeration, no polling. The listing is live: flyout surfaces appear only while they are open, and enumerating never opens, closes, focuses, or presses anything. A platform with no surface of a given kind simply returns none — that is honest scope, not a failure.

locator(selector: str) → Locator

Create a Locator scoped to this surface’s accessibility tree.

tree(max_depth: int | None = None) → dict

Capture this surface’s accessibility tree as a recursive dict.

Each dict has keys role, name, value, and children (a list of dicts with the same shape). max_depth limits traversal: 0 = only the surface root, 1 = root + direct children, None = full subtree.

property kind: ShellSurfaceKindName

What this surface is, as a snake_case string ("taskbar", "status_items", …). The same spelling in both bindings, in the CLI’s --shell flag, and in the shell_kind raw attribute on the surface root.

property name: str

The owning app for per-app surfaces ("Safari" menu bar), the platform’s own name otherwise ("Taskbar", "Dock"). Falls back to the kind when the platform vends no name for the root.

property pid: int | None

Owning process where the platform reports one honestly. On Windows this is the shell host (explorer.exe), because UIA carries no per-icon owner — documented as the host, never faked.

class xa11y.Subscription

A live event subscription.

close() → None
recv(timeout: float = 5.0) → Event
try_recv() → Event | None
wait_for(predicate: collections.abc.Callable[[Event], bool], timeout: float = 5.0) → Event
xa11y.get_default_timeout() → float

Get the effective process-wide default timeout, in seconds.

Resolution order: the set_default_timeout() value, else the XA11Y_DEFAULT_TIMEOUT environment variable, else the built-in 5.0.

xa11y.input_sim() → InputSim

Construct an InputSim backed by the platform’s native input path.

xa11y.locator(selector: str) → Locator

Create a top-level Locator searching from the system root.

xa11y.screenshot(*, element: Element | None = None, region: tuple[int, int, int, int] | None = None, annotate: list[Locator | str] | None = None) → Screenshot

Capture pixels from the screen.

With no arguments, captures the full primary display. Pass element to capture the pixels under an element’s current bounds, or region as (x, y, width, height) to capture an explicit rectangle in logical screen coordinates. Passing both raises ValueError.

annotate draws a numbered box over every element each locator matches, and fills in Screenshot.legend, Screenshot.omitted and Screenshot.truncated. Each entry is one group, with its own colour and tag letter, and must be scoped to an application: app.locator("button"). An entry that is neither a Locator nor a str raises TypeError.

A rootless group raises InvalidSelectorError — a bare str, or locator(), searches every application, and each entry’s <selector>:nth(n) would then count within one application while the legend counts across all of them. The two agree only for a single application, so the selector on an entry would name a different element than the box it labels.

Cropping and annotating are independent: annotations outside the captured area land in omitted rather than being clamped to an edge.

shot = xa11y.screenshot(

element=app.locator(“window”).element(), annotate=[app.locator(“button”), app.locator(“text_field”)],

)

xa11y.set_default_timeout(timeout: float) → None

Set the process-wide default timeout, in seconds.

Becomes the default for every auto-waiting action method, wait_* call, and app lookup (App.by_name / App.by_pid / App.find) that doesn’t pass an explicit timeout=. An explicit per-call timeout= always wins. Takes precedence over the XA11Y_DEFAULT_TIMEOUT environment variable (seconds, read once at import).

Pass 0 for “single attempt, no polling” semantics. Raises ValueError for negative or non-finite values.