Testland
Browse all skills & agents

at-spi-linux

Authors Linux desktop UI tests via AT-SPI - the DBus-based Assistive Technology Service Provider Interface implemented by `at-spi2-core` (registry daemon + `libatspi` C library + ATK GTK bridge). Covers enabling toolkit accessibility (`gsettings set org.gnome.desktop.interface toolkit-accessibility true`), driving GTK + Qt apps through Python `dogtail` (object-oriented + procedural APIs), inspecting the tree with Accerciser, scripting via `pyatspi`, and CI integration on headless Linux runners with `Xvfb` + `dbus-launch`. Use for Linux-side desktop tests of GTK applications, Qt apps with QAccessible enabled, and Electron apps on Linux.

at-spi-linux

Overview

AT-SPI (Assistive Technology Service Provider Interface) is the Linux desktop accessibility stack - a DBus-based protocol that exposes an application's UI tree to assistive technologies (screen readers, magnifiers) and to test clients. Per the at-spi2-core README:

"AT-SPI2-Core is the core of an accessibility stack for free software systems."

It provides (atspi2coreraw) "DBus interface definitions for the Assistive Technology Service Provider Interface" plus four runtime pieces:

Component (atspi2coreraw)Role
registryd"Daemon managing accessible applications and enabling communication between assistive technologies and applications."
atspi"C language binding for DBus accessibility interfaces."
atk"GObject-based API for implementing accessible applications and GUI toolkits."
atk-adaptor"Translates ATK API calls to the atspi API layer."

GTK applications publish accessibility via ATK; Qt applications publish via Qt's own QAccessible infrastructure, which exposes an AT-SPI surface on Linux. Either path lets a test client walk the tree.

Strategic frame: desktop-test-strategy-reference places AT-SPI alongside Windows UIA and macOS XCTest as the three OS- native accessibility-tree backends.

When to use

  • Linux GTK app under test (GNOME apps, GTK4 + GTK3 apps).
  • Qt application on Linux where in-process qt-test-framework doesn't fit (test must drive a separately-packaged binary).
  • Electron / Chromium app on Linux that needs out-of-process driving and Playwright _electron isn't acceptable (e.g., accessibility-conformance audit suites running against the shipped binary).
  • BDD-style acceptance tests via dogtail + behave (dogtailraw).

Step 1 - Enable toolkit accessibility

Per the dogtail README:

gsettings set org.gnome.desktop.interface toolkit-accessibility true

Without this setting, GTK applications publish nothing to the AT-SPI bus and tree-walking clients see an empty desktop.

For Qt apps, the equivalent is exporting QT_ACCESSIBILITY=1 in the environment that launches the Qt binary - without it Qt's QAccessible infrastructure stays inactive and the AT-SPI tree contains no Qt children.

For Electron / Chromium, set --force-renderer-accessibility on the binary launch (or --enable-blink-features=AccessibilityAriaVirtualContent for newer Chromium accessibility surfaces).

Step 2 - Install dogtail

Per dogtailraw:

# From PyPI
sudo python3 -m pip install dogtail

# From source
git clone https://gitlab.com/dogtail/dogtail.git
cd dogtail
python3 -m build
sudo pip3 install dist/dogtail-2.*-py3-none-any.whl

The dogtail README notes that "for Wayland support specifically: Install gnome-ponytail-daemon":

dnf install -y gnome-ponytail-daemon python3-gnome-ponytail-daemon

This bridges synthetic input events on Wayland sessions where direct X-style event injection isn't available.

Step 3 - Inspect the tree (Accerciser)

Before writing tests, walk the live tree with Accerciser (the GNOME accessibility inspector). It's the AT-SPI analogue of Inspect.exe (Windows) and Accessibility Inspector (macOS, per desktop-test-strategy-reference).

# Most distros:
sudo apt install accerciser   # Debian / Ubuntu
sudo dnf install accerciser   # Fedora
accerciser

Accerciser walks the same registryd-published tree the test client sees, and lets the author copy out the exact role + name + description triple for each widget - which is what dogtail queries against.

Step 4 - Author a dogtail test (procedural API)

Per dogtailraw, dogtail "uses Accessibility (AT-SPI) technologies to interact with desktop applications". The procedural API is the closest mirror to the underlying AT-SPI tree:

#!/usr/bin/env python3
from dogtail.procedural import run, focus, click, type, keyCombo
from dogtail.utils import screenshot

# Launch the app — dogtail starts it and attaches to the AT-SPI tree
run('gnome-calculator')

# Resolve via the accessibility tree (focus + click are role-based)
focus.application('gnome-calculator')
focus.frame('Calculator')

click('7')                    # button named "7"
click('+')
click('3')
click('=')

# Assert via the result widget
focus.text(roleName='editbox')
assert focus.widget.text == '10', f'Expected 10, got {focus.widget.text!r}'

screenshot('calc-success.png')

The role-based primitives (focus.application, focus.frame, focus.text, click, type) map onto AT-SPI roles published by the application - same primitives Orca screen reader uses.

Step 5 - Author a dogtail test (object-oriented API)

For larger suites where a Page-Object-style structure is appropriate, the object-oriented tree API exposes the registry as a navigable graph:

from dogtail.tree import root

calc = root.application('gnome-calculator')
frame = calc.child(roleName='frame')

frame.button('7').click()
frame.button('+').click()
frame.button('3').click()
frame.button('=').click()

result = frame.child(roleName='editbox')
assert result.text == '10'

root is the AT-SPI desktop entry point - dogtail's wrapper over the libatspi get_desktop() function described in the libatspi reference:

"init() … connects to the accessibility registry and initializes the SPI."

"get_desktop() and get_desktop_list() to access the accessibility tree once connected."

Step 6 - Direct pyatspi for fine-grained control

For tests that need to listen for events on the AT-SPI bus rather than poll the tree, drop down to pyatspi:

import pyatspi

def on_state_change(event):
    if event.type == 'object:state-changed:focused' and event.detail1:
        print(f'Focus moved to: {event.source.name} ({event.source.getRoleName()})')

pyatspi.Registry.registerEventListener(
    on_state_change,
    'object:state-changed:focused',
)
pyatspi.Registry.start()   # blocks; Ctrl-C to exit

This uses the AT-SPI event-listener pattern documented in atspi2docs:

"AtspiEventListener operates through a callback mechanism. The library defines a generic interface implemented by objects for the receipt of event notifications."

Step 7 - Run

# Standalone — assumes accessibility is enabled + an X / Wayland session
python3 tests/test_calculator.py

# Under pytest with JUnit output
pytest tests/ --junitxml=reports/atspi-junit.xml

Step 8 - Parsing results

JUnit XML from pytest feeds junit-xml-analysis for the cross-runner aggregation pipeline.

For dogtail-specific diagnostics, every failing run captures a screenshot (see Step 4 - screenshot() call) plus a dogtail session log under ~/.dogtail/logs/.

Step 9 - CI integration

The Linux runner needs a display server + session DBus bus before AT-SPI clients can connect:

# .github/workflows/atspi.yml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - name: Install AT-SPI + dogtail + app + Accerciser deps
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            at-spi2-core \
            python3-dogtail python3-pyatspi \
            xvfb dbus-x11 gnome-calculator
      - name: Enable accessibility for GTK
        run: |
          gsettings set org.gnome.desktop.interface toolkit-accessibility true \
            || true   # the schema needs a session bus; the env block below ensures it
      - name: Run tests under Xvfb + dbus-launch
        env:
          QT_ACCESSIBILITY: '1'
        run: |
          xvfb-run --auto-servernum --server-args='-screen 0 1280x1024x24' \
            dbus-launch --exit-with-session \
              pytest tests/ --junitxml=reports/atspi-junit.xml
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: junit
          path: reports/

xvfb-run provides the X display, dbus-launch --exit-with-session spawns the session DBus bus (at-spi2-registryd requires the session bus per atspi2coreraw - without it the registry daemon refuses to start). This matches the CI guidance in the desktop-test-strategy-reference anti-patterns table.

Anti-patterns

Anti-patternWhy it failsFix
Running dogtail tests without toolkit-accessibility=trueGTK apps publish nothing; tree is emptygsettings set org.gnome.desktop.interface toolkit-accessibility true (dogtailraw)
Driving Qt apps with QT_ACCESSIBILITY unsetQAccessible stays inactive; AT-SPI tree has no Qt childrenExport QT_ACCESSIBILITY=1 in the launch env (Step 1)
CI runner without dbus-launchregistryd refuses to start; sessions hang on attachxvfb-run … dbus-launch … pytest … (Step 9)
Locating by visible label onlyLocalisation breaks the locatorCombine roleName= + accessible name; set Atk.set_accessible_name(...) from app code
time.sleep(2) between actionsFlaky; brittleUse dogtail's doDelay config (config.searchBackoffDuration) and tree polling helpers (dogtailraw)
Wayland session without gnome-ponytail-daemonSynthetic input events get droppedInstall daemon per dogtailraw
Mixing X test runner with Wayland app under testEvent injection mismatchOne session type per CI job; matrix-build over Xorg + Wayland separately
Test relies on Accerciser running concurrentlyTwo clients on the same registry race on tree refreshUse Accerciser interactively for authoring; remove from CI runs

Limitations

  • Linux-only. AT-SPI runs only on Linux desktops; cross-OS parity lives in winappdriver (Windows) and xctest-mac-desktop (macOS).
  • Session-bus requirement. at-spi2-registryd requires a session DBus bus per atspi2coreraw; bare Docker containers (no session bus) need dbus-launch. GitHub-hosted Ubuntu runners ship with the bus but headless self-hosted runners don't necessarily.
  • GTK4 shadow rendering quirks require disabling window shadows in ~/.config/gtk-4.0/gtk.css per dogtailraw; otherwise click-coordinates land on the shadow region rather than the widget.
  • No first-party Wayland event-injection guarantee. Recent Wayland compositors (mutter, kwin) restrict synthetic input; gnome-ponytail-daemon (dogtailraw) is the documented workaround on GNOME Wayland sessions.
  • Apps must publish accessibility. Custom-drawn widgets that bypass GTK/Qt/Chromium accessibility (e.g., raw OpenGL canvases, custom-painted Cairo surfaces) are opaque to AT-SPI - same caveat as desktop-test-strategy-reference.
  • dogtail API docs incomplete. Per dogtailraw, "API docs (incomplete for both versions, in progress for 2.x)" - some method signatures need source-spelunking.

References