@crowecawcaw/xa11y
v0.7.1
Published
Cross-platform accessibility client library for Node.js
Maintainers
Readme
@crowecawcaw/xa11y
Cross-platform accessibility library for Node.js. One API for macOS, Windows,
and Linux — built on top of the Rust xa11y
crate, exposed via napi-rs.
Use cases: UI testing, AI agent tooling, desktop automation, accessibility auditing.
Documentation · JavaScript API · Rust API · Python API
Quick example
import { App } from '@crowecawcaw/xa11y';
const safari = await App.byName('Safari');
// Find elements with CSS-like selectors via locator
for (const button of await safari.locator('button').elements()) {
console.log(button.name);
}
// Interact with elements (re-resolves on every call)
await safari.locator('button[name="Submit"]').press();
await safari.locator('text_field[name^="Search"]').setValue('hello world');Installation
npm install @crowecawcaw/xa11yRequires Node.js 18 or newer. Pre-built native binaries are published for Linux (x64/arm64), macOS (x64/arm64), and Windows (x64/arm64).
macOS: Grant your terminal (or the process running Node) two permissions in System Settings → Privacy & Security: Accessibility and Screen Recording. The first lets xa11y read the AX tree; the second is needed for some apps that expose their tree only when the screen-recording prompt has been answered.
Async by default
Every operation that touches the accessibility tree is asynchronous and runs
on a worker thread, so the Node event loop is never blocked. Property getters
on Element snapshots are synchronous because the data is captured up front.
const app = await App.byName('com.example.MyApp');
const submit = await app.locator('button[name="Submit"]').element();
console.log(submit.role); // sync — already captured
console.log(submit.enabled); // sync
await submit.children(); // async — re-queries the providerLocators auto-wait
Action methods on Locator poll the tree until the element is visible and
enabled, up to a 5-second budget, before performing the action. This makes
tests resilient to UI populating asynchronously.
// Waits until the dialog appears AND its OK button is enabled, then clicks it.
await app.locator('dialog[name="Save Changes?"] button[name="OK"]').press();Subscribing to events
Subscriptions extend Node's EventEmitter, so you can use the familiar
.on() / .once() / .off() API. Events are emitted both under their
specific type name and the catch-all 'event' name. Call close() (or
pass an AbortSignal) to tear down the subscription.
const sub = await app.subscribe();
sub.on('focusChanged', (ev) => console.log('focus moved to', ev.target?.name));
sub.on('event', (ev) => console.log(ev.type, ev.appName));
// ... later
sub.close();Waiting for a single event
Use waitForEvent() for Playwright-style one-shot waits with optional
predicate and timeout:
const [opened] = await Promise.all([
app.waitForEvent('windowOpened'),
app.locator('button[name="Settings"]').press(),
]);
// With predicate + timeout
const ev = await sub.waitForEvent('focusChanged', {
predicate: (e) => e.target?.role === 'button',
timeout: 3000,
});AbortSignal support
Both subscribe() and waitForEvent() accept an AbortSignal for
cancellation:
const ctrl = new AbortController();
const sub = await app.subscribe({ signal: ctrl.signal });
sub.on('focusChanged', (ev) => { /* ... */ });
// ctrl.abort() closes the subscription automaticallyErrors
All operations throw subclasses of XA11yError:
| Class | When |
| --- | --- |
| PermissionDeniedError | Accessibility permissions not granted (macOS) |
| SelectorNotMatchedError | element() / actions found nothing |
| ActionNotSupportedError | The element doesn't support the action |
| TimeoutError | A wait* or auto-wait budget expired |
| InvalidSelectorError | Bad selector or action data |
| PlatformError | Other OS-level failure |
Catch a specific subclass and let the rest propagate:
import { App, SelectorNotMatchedError } from '@crowecawcaw/xa11y';
try {
await app.locator('button[name="Submit"]').press();
} catch (err) {
if (err instanceof SelectorNotMatchedError) {
/* expected miss */
} else {
throw err;
}
}License
MIT — see LICENSE.
