# Guidepup drives NVDA, Orca and VoiceOver from one TypeScript API

> Guidepup is an MIT licensed screen reader automation library that starts the real screen reader on a machine and speaks its keyboard commands, so a test can assert on what a screen reader actually announces rather than on the DOM. It suits a team adding accessibility coverage to an existing test suite, and it needs a real operating system with a real screen reader installed, not a headless browser.

**guidepup/guidepup** — Screen reader automation library for testing.

- Repository: https://github.com/guidepup/guidepup
- Website: https://guidepup.dev
- Stars: 583 · Forks: 20
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/guidepup-guidepup

## Asserting on what the screen reader says, not on the DOM

Guidepup is a screen reader automation library for testing. It enables testing for NVDA on Windows, Orca on Linux, and VoiceOver on macOS with a single API, and the documentation site is guidepup.dev.

The problem it solves is specific and important. An automated accessibility check can tell you that a button has no accessible name, and a DOM assertion can tell you a heading has the right level. Neither of those tells you what a user actually hears, and the gap between a passing automated check and a usable screen reader experience is where accessibility bugs live.

The stated capability list frames it as full control: if a screen reader has a keyboard command, then Guidepup supports it. The second item is that it mirrors real user experience, so you assert on what users really do and hear when using screen readers. The third is that it is framework agnostic, so you can run it with Jest, with Playwright, as an independent script, with no vendor lock-in.

That last point is what makes the library usable in an existing test suite. It is not a test framework you adopt, it is a driver you call from whatever you already run.

The topics in the repository list the three screen readers plus accessibility, accessibility testing and automation, and the package keywords are screen reader, accessibility, a11y, voiceover, nvda and guidepup.

## The cross-platform entry point picks the right screen reader

The cross-platform example is the shortest path to understanding the API shape.

```ts
import { screenReader } from "@guidepup/guidepup";

(async () => {
  // On Linux starts Orca, on macOS starts VoiceOver, and on Windows starts NVDA.
  await screenReader.start();

  await screenReader.next();
  await screenReader.nextHeading();
  await screenReader.nextLink();
  await screenReader.nextLandmark();
  await screenReader.previous();
  await screenReader.act();

  console.log(await screenReader.spokenPhraseLog());

  await screenReader.stop();
})();
```

The `screenReader` object is a facade that resolves to the right implementation per platform, and the comment in the example says so directly: on Linux it starts Orca, on macOS it starts VoiceOver, and on Windows it starts NVDA.

The navigation methods are the substance of it. `next`, `previous` and `act` move through the virtual buffer and activate the current item, while `nextHeading`, `nextLink` and `nextLandmark` jump by semantic type rather than by position. That distinction is the whole reason to use a screen reader in a test: a DOM query for a heading does not know whether that heading is reachable by the user's next-heading command in that context, and a screen reader does.

`spokenPhraseLog()` returns what was announced, which is the assertion surface. The rest of the API is about producing a deterministic state to assert on.

## Per-screen-reader APIs, and the Orca preview caveat

Each screen reader also has its own named export, and the difference between them is where the platform specifics live.

The VoiceOver example uses the `voiceOver` object and a `keyboardCommands` map on it, calling `voiceOver.perform(voiceOver.keyboardCommands.findNextControl)`. The NVDA example uses the `nvda` object with `nvda.keyboardCommands.moveToNextFormField`, and the Orca example uses `unstable_orca` with `unstable_orca.keyboardCommands.NextFormField`.

Two things to notice. The first is the naming of the keyboard command constants, which are camelCase for NVDA and VoiceOver and PascalCase for Orca, and the fact that the constant is referenced from the object rather than written as a string. That means the command names are typed and checked at compile time against the screen reader's actual command set, which is what keeps a renamed command from silently becoming a no-op.

The second is the `unstable_` prefix on the Orca export, and the note attached to it: Orca support is currently in preview, not all features may be implemented. The prefix is the library telling you the API for that platform may change between minor versions. The cross-platform facade starts Orca on Linux, so a Linux test run depends on the least settled of the three implementations, and the `unstable_orca` export is the way to reach the real thing when you need behaviour the facade does not expose yet.

The `lastSpokenPhrase()` method, present in the Orca, VoiceOver and NVDA examples, gives you the most recent announcement without the full log, which is what you want in a failing assertion.

## Three setup commands and a real screen reader

Setup is three commands, and the order matters because the library needs the screen reader to be present and controllable before it drives it.

The first sets up the machine for screen reader automation.

```sh
npx @guidepup/setup setup
```

The second installs the library into your project.

```sh
npm install @guidepup/guidepup
```

The third installs the Guidepup screen reader assets.

```sh
npx @guidepup/setup install
```

The distinction between the first and the third is the part worth understanding. The setup package has two jobs: configuring the environment so a screen reader can be started and driven programmatically, and installing the screen reader assets that the library talks to. The core library itself has only three runtime dependencies, dbus-native, debug and plist, which is a very small surface for something that drives a desktop application.

That dependency list is itself evidence about the architecture. dbus-native matters on Linux, where talking to Orca means talking over the accessibility bus. plist is for macOS, where the configuration lives in property lists. There is no Windows-only native dependency listed, so the NVDA path is likely driven through a different mechanism, which is one of the things the platform documentation would have to explain.

And this is the fundamental constraint of the tool: it automates the actual screen reader, so it needs the actual screen reader, installed and configured, on the machine running the test.

## The surrounding tooling: Docker, Playwright, and a virtual screen reader

The powerful tooling list is where this project is more ambitious than a single library, and it answers two objections at once.

The first objection is CI. A container image at ghcr.io/guidepup/guidepup gives you a Docker integration of Guidepup, so the screen reader is present in the environment where the tests run rather than being something you install on a developer laptop. The setup package is described as being for a local or CI environment, which says the same thing in prose.

The second objection is speed and determinism. `@guidepup/virtual-screen-reader` provides reliable unit testing for your screen reader a11y workflows, and `@guidepup/jest` provides Jest matchers for the same thing. Those are the tools for the fast layer, where you do not want to boot a real screen reader for every assertion.

`@guidepup/playwright` gives integration with Playwright, and the examples directory shows the shape of that: playwright-nvda, playwright-orca, playwright-voiceover and playwright-screenreader directories alongside circleci-voiceover and github-actions-voiceover.

That split is the interesting design. The real screen reader gives you fidelity, and the virtual one gives you speed. A sensible suite uses the virtual reader for the many small assertions and the real one for the handful of end to end checks, and the presence of both means you are not forced to choose.

The examples folder also holds applicationIdMap.ts and applicationNameMap.ts, which look like helpers for mapping a screen reader's own application list, and a logIncludesExpectedPhrases.ts file that is presumably an assertion helper.

## Package layout, release cadence and what the tests cover

The repository is a small TypeScript package. There is a src/ directory, a test/ directory, an examples/ directory, a jest.config.js, an eslint.config.mjs, a tsconfig.json, a manifest.json, an .nvmrc for the Node version, and a CODE_OF_CONDUCT.md and SECURITY.md.

The package.json describes a conventional build. The compile step is tsc, the clean step is rimraf lib, the test runner is jest, and there is a verify script that chains clean, lint, test:coverage and build into one command, which is the script a contributor runs before opening a pull request. The published files are lib/index.js with typings at lib/index.d.ts.

The test tooling list is worth a look because it shows what the tests are allowed to touch. There is nock, which intercepts HTTP requests, and ts-jest for running the TypeScript sources directly, plus ts-node. The nock dependency in particular suggests the tests mock network calls rather than reaching out, which matters for a library that otherwise starts a desktop application.

The release cadence is steady. The listed versions are 0.36.4 on 2026-10-17, 0.36.3 on 2026-10-15 and 0.36.2 on 2026-10-14, and the last push was on 2026-10-19 with the repository not archived. Three patch releases in four days is the pace of a library that is being kept aligned with the screen readers it drives, which change on their own schedules.

The licence is MIT, and the author is Craig Morten.

## Conclusion

Guidepup fits a team that already has unit and browser tests and now wants accessibility regressions caught automatically, because it drives the real screen reader and lets you assert on the spoken output rather than on markup. It does not fit a CI setup that runs headless in a container with no screen reader, unless you go to the trouble of installing one there, and it does not fit a team that wants fast unit level a11y assertions, which is what the virtual screen reader and the Jest matchers are for. Before adopting it, run the setup package on the machine or CI image you plan to use, because a screen reader that cannot start will fail every test, and check the Orca status first, since the documentation marks Orca support as in preview with not all features implemented.

## FAQ

### How do I set up Guidepup for screen reader automation?

Three commands. Run npx @guidepup/setup setup to set up the machine, npm install @guidepup/guidepup to add the library, and npx @guidepup/setup install to install the Guidepup screen reader assets.

### Which screen readers does Guidepup support?

NVDA on Windows, Orca on Linux and VoiceOver on macOS, all through a single API. The cross-platform screenReader object starts Orca on Linux, VoiceOver on macOS and NVDA on Windows.

### Is Orca support finished in Guidepup?

No. The documentation states that Orca support is currently in preview and not all features may be implemented, which is why the Orca export is named unstable_orca.

### Can Guidepup run without a real screen reader installed?

The library drives the actual screen reader, so it needs one present. For fast unit level assertions without that, the project ships @guidepup/virtual-screen-reader and @guidepup/jest matchers as separate packages, and a Docker image at ghcr.io/guidepup/guidepup for CI environments.

## Sources

- [guidepup/guidepup on GitHub](https://github.com/guidepup/guidepup)
- [License: MIT](https://github.com/guidepup/guidepup/blob/main/LICENSE)
- [Project website](https://guidepup.dev)
- [README](https://github.com/guidepup/guidepup/blob/main/README.md)
- [Releases](https://github.com/guidepup/guidepup/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/guidepup-guidepup
