Testland
Browse all skills & agents

screen-reader-test-author

Builds a screen-reader test narrative - a step-by-step manual test script for NVDA (Windows), JAWS (Windows), VoiceOver (macOS / iOS), or TalkBack (Android) - that exercises a specific user flow through a component or page and captures the expected announcement at each step. Use when authoring an accessibility-acceptance test the team will run before sign-off, OR when scripting a manual a11y audit.

Install with skills.sh (any agent)

npx skills add testland/qa --skill screen-reader-test-author
View source

screen-reader-test-author

Overview

Automated tools (axe-core, pa11y, Lighthouse) catch ~30-40% of accessibility issues - the structural ones. The remaining 60-70% require manual screen-reader testing by an actual human using NVDA / JAWS / VoiceOver / TalkBack. The team needs a repeatable script that any tester (not just the original author) can follow.

This skill takes a user flow and produces that script: per-step keystroke + expected announcement.

When to use

  • A high-stakes feature ships and needs accessibility sign-off.
  • Automated a11y tools are green but the team wants confidence the actual screen-reader experience is good.
  • Onboarding a new accessibility tester - they need a script to follow until they internalize the conventions.
  • Documenting an "a11y regression repro" when a bug ships.

Step 1 - Pick the screen reader

Most a11y audit conventions cover at least:

Screen readerPlatformBrowser pairing
NVDAWindowsFirefox or Chrome
JAWSWindowsChrome or Edge
VoiceOvermacOS / iOS / iPadOSSafari (macOS); Safari (iOS)
TalkBackAndroidChrome

For the most-coverage-per-effort, test NVDA + Firefox and VoiceOver + Safari - these are also the WAI-recommended pairs.

Start each script by naming the SR + browser + OS combination.

Step 2 - Define the flow

A flow is a sequence of user goals. Don't write step-by-step keystrokes upfront - that's Step 3. The flow is at user-intent level:

flow_name: "User edits their profile email"
preconditions:
  - User is logged in.
  - User is on the /profile page.
  - Screen reader is on; browser virtual cursor is at the page heading.

steps:
  - intent: "Navigate to the profile-edit form via main nav"
  - intent: "Tab through the form to the email field"
  - intent: "Edit the email value to a new valid email"
  - intent: "Submit the form"
  - intent: "Hear the success confirmation announcement"

Step 3 - Per intent, capture keystrokes + expected announcement

For each intent, the script row lists three things:

  • Keystroke - the actual key combo the tester presses.
  • Expected announcement - what the screen reader should say.
  • Why - the WCAG SC or APG pattern the announcement satisfies.

NVDA browse mode navigates with quick-keys (H by heading, F by form field); VoiceOver uses VO (Ctrl+Option) chords:

ActionVoiceOver shortcut
Read next / previousVO + Right / Left arrow
Next headingVO + Cmd + H
Next form controlVO + Cmd + J
Activate (Enter)VO + Space
Web rotorVO + U (form / heading / link list overlay)

A minimal NVDA + Firefox excerpt for the flow "User edits their profile email":

KeystrokeExpected announcement
H"Edit profile, heading level 2" - moves to next heading.
F"Email, edit, blank" - jumps to the first form field.
Tab, Enter"Save changes, button", then a live region announces "Profile saved".

Full per-reader scripts (NVDA + Firefox and VoiceOver + Safari) for this flow, with the WCAG / APG mapping in the "Why" column: references/screen-reader-scripts.md.

VoiceOver announcements are sometimes more verbose than NVDA's (role words like "edit text" where NVDA says "edit"); that is a vendor convention, not a bug to suppress.

Step 4 - Define what counts as PASS

For each step, the announcement should:

  1. Identify the element type - "button", "edit", "link", "heading level 2", etc.
  2. Read the visible label verbatim - if the visible label is "Save changes", the SR should say "Save changes" (not "Submit" or the id attribute).
  3. Convey state - for a checkbox, "checked" / "not checked"; for a button, "expanded" / "collapsed" / "pressed".
  4. Convey position in a set - for a tab, "1 of 4"; for a list item, "1 of 10".

Failures to flag:

FailureLikely cause
Announcement reads CSS class names or id attributesLabel is missing; SR fell back to other text.
Element type is wrong ("link" instead of "button")<a> used as a button; or role="link" on a button.
State is missingaria-expanded / aria-checked not set.
Position-in-set is missingaria-setsize / aria-posinset not set on list items.
Live region announcement doesn't fireRegion added with content already present, OR aria-live="off".

Step 5 - Wire to the test plan

The script becomes a manual test in the project's test plan. Common formats:

Markdown checklist (lightweight)

## A11y Acceptance - User edits profile email

### NVDA + Firefox (Windows)

- [ ] Step 1: Pressing H twice lands on "Profile information" heading.
- [ ] Step 2: Pressing F lands on the email field; announces "Email, edit, blank".
- [ ] Step 3: After submit, a live region announces "Profile saved".

### VoiceOver + Safari (macOS)

- [ ] Step 1: VO+Cmd+H twice lands on "Profile information" heading.
- [ ] Step 2: VO+Cmd+J lands on email; announces "Email, edit text".
- [ ] Step 3: After submit, live region announces "Profile saved".

Sign-off: ___________________ Date: __________

Test-management tool integration

For teams using TestRail / Xray / Zephyr - encode the flow as a manual test case with one step per row, expected result per row. The skill emits the matching format; per testrail-integration (when shipped) the test case can be uploaded automatically.

Anti-patterns

Anti-patternWhy it failsFix
Testing on only one screen readerNVDA bugs ≠ JAWS bugs ≠ VoiceOver bugs.At least two reader/browser pairs (NVDA+Firefox, VoiceOver+Safari).
Asking the developer to test their own workFamiliarity bias; the dev knows how it should sound.Independent tester or accessibility specialist; use the script blindly.
Recording expected announcements verbatim from one versionScreen-reader announcement strings change between versions.Test the announcement contains key elements (label, role, state); avoid string-equality.
Skipping the "Why" columnTester can't generalize from "this announcement was wrong" to "the underlying ARIA pattern is broken."Always link to the WCAG SC or APG pattern.

Limitations

  • Manual. Cannot fully automate; the tester is the QA instrument.
  • Vendor variance. A test script that passes on NVDA might fail on JAWS due to vendor-specific announcement choices; the underlying code may be correct.
  • Mobile tests. TalkBack and iOS VoiceOver have very different gesture sets from desktop SRs; mobile flows need separate scripts.

References

  • W3C WCAG 2.2 - https://www.w3.org/TR/WCAG22/
  • WebAIM Screen Reader Survey - https://webaim.org/projects/screenreadersurvey/
  • NVDA documentation - https://www.nvaccess.org/files/nvda/documentation/userGuide.html
  • VoiceOver Getting Started - https://www.apple.com/accessibility/mac/vision/ + Apple developer docs
  • wcag-keyboard-navigation, aria-authoring-patterns - patterns this skill references for the "Why" column.

Per-screen-reader worked scripts

View source (opens in new window)

Per-screen-reader worked scripts

Full NVDA + Firefox and VoiceOver + Safari scripts for the sample flow "User edits their profile email." SKILL.md Step 3 has the row template, the VoiceOver chord table, and a minimal excerpt; these are the complete per-reader versions, including the WCAG SC / APG notes that populate the "Why" column.

NVDA + Firefox (Windows)

## NVDA + Firefox (Windows) - User edits their profile email

### Pre-conditions
- NVDA is running.
- Firefox is on https://app.example.com/profile.
- Virtual cursor mode is on (NVDA default for browsers).

### Step 1 - Navigate to the profile-edit form

| Keystroke | Expected announcement                                          |
|-----------|----------------------------------------------------------------|
| H         | "Edit profile, heading level 2" - moves to next heading.       |
| H         | "Profile information, heading level 3" - moves further.        |
| F         | "Email, edit, blank" - jumps to first form field.              |

NVDA's `H` quick-key navigates by heading; `F` by form field.
The announcements should match the visible heading text and field
labels (per WCAG SC 2.4.6 Headings and Labels).

### Step 2 - Edit the email value

| Keystroke         | Expected announcement                                  |
|-------------------|--------------------------------------------------------|
| Enter (focus mode) | "Email, edit, has autocomplete" - enters focus mode.  |
| (type new email)  | (each character spoken if `say characters` is on)     |

### Step 3 - Submit the form

| Keystroke | Expected announcement                                          |
|-----------|----------------------------------------------------------------|
| Tab       | "Save changes, button" - moves to submit.                       |
| Enter     | (no announcement immediately; wait for live region)             |

### Step 4 - Success confirmation

| Behavior          | Expected announcement                                          |
|-------------------|----------------------------------------------------------------|
| (page response)   | "Profile saved" - announced via `aria-live="polite"` region.   |

If no announcement: the success region is missing `aria-live`
attribute, OR the region is added to the DOM with content already
in it (live regions only announce **changes** post-mount).

VoiceOver + Safari (macOS)

VoiceOver uses VO+arrow keys (Ctrl+Option+arrow) and different quick-key patterns from NVDA. Re-author the same flow:

## VoiceOver + Safari (macOS) - User edits their profile email

### Step 1 - Navigate to the profile-edit form

| Keystroke    | Expected announcement                                       |
|--------------|-------------------------------------------------------------|
| VO + Cmd + H | "Edit profile, heading level 2"                              |
| VO + Cmd + J | "Email, edit text"                                           |

VoiceOver's announcements are sometimes more verbose than NVDA's (reads role words like "edit text" where NVDA says "edit"). This is a vendor convention; don't try to suppress it.

Related skills

a11y-violation-gate

Builds a CI gate that fails the build on **new** WCAG / a11y violations introduced by a PR while grandfathering pre-existing violations on a per-rule / per-page baseline. Aggregates verdicts from axe-core / pa11y / Lighthouse a11y / WAVE / IBM Equal Access scans. Use when a project has accumulated a11y debt and a strict "zero violations" gate would block every PR - the ratchet pattern lets the team ship while preventing regressions.

aria-authoring-patterns

Reference for the W3C ARIA Authoring Practices Guide (APG) - covers the 31 canonical interactive-widget patterns (Combobox, Dialog, Menu, Tabs, Tree, etc.), their required ARIA roles and states, the keyboard-interaction model per pattern, and the canonical-violations to watch for. Use when authoring a custom interactive widget that doesn't have a native HTML equivalent, or when reviewing one for ARIA correctness.

axe-a11y

Authors and runs axe-core accessibility scans - the most-deployed open-source a11y engine - via the `axe.run()` JavaScript API or the @axe-core/playwright / @axe-core/cli wrappers, parses the `violations[]` results into per-rule severity (critical / serious / moderate / minor), configures rule disable / disable-by-tag patterns, and emits JUnit-shaped output for CI gating. Use when the project ships UI tests in JavaScript / TypeScript and wants automated a11y coverage on every PR.

ibm-equal-access-a11y

Authors and runs IBM Equal Access accessibility-checker scans - IBM's open-source a11y engine with WCAG 2.0 / 2.1 / 2.2 + US Section 508 rule sets, integrating with Node / Selenium / Puppeteer / Playwright / Karma / Cypress test runners. Distinguished by IBM's enterprise-tier rule coverage and Section 508 specificity. Use when the project ships to US federal / public-sector customers (Section 508 mandate) or when the team values IBM-branded a11y reporting.

lighthouse-a11y

Configures Lighthouse CI's Accessibility category for automated accessibility testing (a11y / WCAG coverage) - `categories:accessibility` audits backed by axe-core (axe) - with per-URL minimum-score assertions (fail a build when a page's score drops below a threshold) and per-audit overrides, distinct from the Performance category that `lighthouse-perf` covers. Use when the project already runs Lighthouse CI for Web Vitals and the team wants to add accessibility coverage in the same pipeline rather than spinning up a separate scanner.

pa11y-a11y

Authors and runs pa11y accessibility scans - a CLI / Node.js tool that wraps HTML CodeSniffer (htmlcs) and / or axe-core engines - with `pa11y {url}` invocation, reporter selection (cli / csv / json / html / tsv), WCAG standard selection (WCAG2A / WCAG2AA / WCAG2AAA), and rule ignoring. Use when the project needs scriptable a11y scans without a full test framework, or when a Node-stack project wants an alternative to direct axe-core use.

wave-a11y

Runs WebAIM WAVE accessibility scans via the WAVE API or the browser-extension UI - produces visual overlay of errors / alerts / structural elements directly on the page, plus categorized JSON output for CI use. Use when the team values manual-review-friendly visual feedback (the WAVE overlay) alongside automated CI scans, or when a regulatory audit requires WebAIM-branded reports.

wcag-checklist-builder

Builds a per-component WCAG 2.2 accessibility checklist from a component spec - covers focus management, color contrast, ARIA roles & states, keyboard interaction, error handling, and live-region announcements - emitting a markdown checklist or YAML test plan that pairs with screen-reader-test-author for manual verification and the violation gate for automated scans. Use during component-spec review or pre-implementation acceptance.

wcag-color-contrast

Reference for WCAG 2.2 color-contrast conformance - covers SC 1.4.3 Contrast (Minimum, AA), 1.4.6 Contrast (Enhanced, AAA), 1.4.11 Non-text Contrast (AA), and 1.4.13 Content on Hover or Focus (AA) - with the canonical contrast ratios (4.5:1 normal text, 3:1 large text and UI components), measurement formula references, and bulk design-token checking patterns. Use when designing a color palette, reviewing a component for accessibility, or auditing existing CSS for contrast violations.

wcag-compliance-reporter

Builds a per-page WCAG 2.2 compliance score report by aggregating output from one or more accessibility scanners (axe-core / pa11y / lighthouse / WAVE / IBM Equal Access), pivoting violations by Success Criterion (1.4.3 contrast, 2.4.7 focus visible, etc.), grouping by conformance level (A / AA / AAA), reporting per-page coverage gaps explicitly (the "this page wasn't scanned" failure mode), and emitting both an executive summary and a per-page drill-down. Use after a multi-page accessibility scan - pa11y-ci, axe across a sitemap, lighthouse-batch - when the team needs a shareable conformance report rather than a per-page tool dump.

wcag-focus-trap

Reference for **intentional** focus management in modal / dialog / drawer / popover components - the canonical pattern that satisfies WCAG SC 2.4.3 (Focus Order) without violating SC 2.1.2 (No Keyboard Trap). Covers focus-on-open, focus-cycle-within-container, Escape-closes-and-restores, return-to-trigger, and inert-the-rest-of-the-page. Use when authoring or reviewing any component that displays content over the page (modals, drawers, popovers, command palettes).

wcag-keyboard-navigation

Reference catalog for WCAG 2.2 keyboard-navigation conformance - covers SC 2.1.1 (Keyboard), 2.1.2 (No Keyboard Trap), 2.1.4 (Character Key Shortcuts), 2.4.3 (Focus Order), 2.4.7 (Focus Visible), 2.4.11/2.4.12 (Focus Not Obscured) - with conformance levels (A/AA), test scripts, and per-criterion failure patterns. Use when authoring or reviewing keyboard-only interaction support.

widget-a11y-test-matrix

Per-widget manual accessibility test matrices where every row pairs one keystroke with the expected focus behavior, the expected NVDA announcement, the expected VoiceOver announcement, and the WCAG 2.2 success criterion that row verifies. Covers button, toggle button, checkbox, text input, modal dialog, menu button, and combobox archetypes, plus universal Tab traversal. Use when a rendered widget has cleared automated scanning and a tester needs a fill-in pass/fail sheet to run by hand against NVDA and VoiceOver.