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.

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.

Terminology note: "screen reader" is practitioner-emergent but well-established. ISTQB has no canonical entry; W3C WAI uses the term informally. This skill cites WebAIM and vendor docs as primary sources for keyboard commands.

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 pairingMarket share (English-speaking)
NVDAWindowsFirefox or Chrome~30-40% (free, dominant in QA shops)
JAWSWindowsChrome or Edge~40-50% (commercial; widely deployed in enterprises)
VoiceOvermacOS / iOS / iPadOSSafari (macOS); Safari (iOS)macOS: dominant for Mac users; iOS: only option
TalkBackAndroidChromeAndroid: dominant

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 lists:

  • 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 on 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 on Safari (macOS)

VoiceOver uses VO+arrow keys (Ctrl+Option+arrow) and has different quick-key patterns:

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

Re-author the same flow per VoiceOver:

## 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.

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.
  • accessibility-code-critic - adversarial agent that finds the issues this script later verifies were fixed.