Accessibility helps people with different needs use the product. Automated scanning is useful but cannot replace human testing.
automation + assistive technology + human judgment
Series: Playwright Zero to Expert | Demo app used throughout this series: saucedemo.com
Recall Part 7’s honest observation: getByRole locators, by design, reward good accessibility practice, because a locator working correctly is itself evidence a screen reader user could identify that same element too. This part makes that connection explicit and complete — accessibility isn’t a side effect of good locator strategy, it’s a genuinely important testing discipline in its own right.
What Accessibility Actually Means, Concretely
Accessibility
is the practice of building software that people with disabilities can genuinely use — someone using a screen reader because they’re blind or have low vision, someone navigating entirely by keyboard because they can’t use a mouse due to a motor impairment, someone with color blindness who can’t distinguish a status conveyed purely through color. This isn’t a niche, edge-case concern — it’s a real, substantial portion of any application’s actual user base, and in many jurisdictions, it’s also a genuine legal requirement, not merely a nice-to-have.
The Accessibility Tree, and ARIA
Recall Part 1.3’s DOM tree. The accessibility tree is a separate, parallel structure the browser builds from the DOM specifically for assistive technology (like screen readers) to use — a simplified representation focused specifically on each element’s role, name, and state, exactly the same three properties getByRole has been built around this entire series.
ARIA (Accessible Rich Internet Applications)
is a set of HTML attributes specifically designed to improve this accessibility tree when plain, semantic HTML alone isn’t sufficient to convey an element’s purpose:
<div role="button" aria-label="Close dialog" tabindex="0">×</div>
It’s worth being honest about something here, directly connecting back to Part 1.1’s discussion of semantic HTML: this exact example is also, genuinely, a small case study in accessibility done as a workaround rather than done correctly from the start.
A real <button> element automatically has the correct role, is automatically keyboard-focusable, and automatically responds to both mouse clicks and the Enter/Space keys — all without any ARIA attributes needed at all. The <div> version above only works because someone remembered to manually add role="button", aria-label, and tabindex="0" — and manually add the JavaScript to make it respond to keyboard input, which this snippet doesn’t even show.
The genuinely important, often-repeated real-world principle: semantic HTML should always be preferred over ARIA-patched non-semantic HTML — ARIA exists to handle the genuine cases where semantic HTML truly can’t express something, not as a routine substitute for using the right element in the first place.
Analogy: The Building Ramp & Braille Signage vs. The Retrofitted Window Ladder Imagine making a physical public library accessible to all citizens:
- Semantic HTML (Standard Ramp & Braille Sign): You build a wide concrete ramp at the front entrance and install a Braille sign saying “Main Lobby Entrance” next to a heavy double door. Everyone — including parents with strollers, wheelchair users, and blind individuals — can find the door, know its role (entrance), and enter standardly (using real semantic elements like
<button>or<input>).- ARIA-Patched Elements (Retrofitted Window Ladder): You block the front door. Instead, you open a window on the second floor, drop a wooden ladder, and glue a paper sign next to it: “Note: This is a door, treat it as a door” (
role="button"on a<div>). To make it work, you must hire guards (JavaScript keyboard event listeners) to manually carry wheelchair users up the ladder. It is brittle and fails the moment a guard is off duty.
📊 Visual Flowchart: Keyboard Focus & Accessibility Tree Sequence
Here is how focus events cycle through focusable elements, updating assistive readouts sequentially:
graph TD
Start["1. User lands on page: goto('/')"] --> Tab1["2. Press 'Tab'"]
Tab1 --> Focus1["Focus shifts to: Username input field"]
Focus1 --> Read1["Accessibility Tree exposes:<br>Role: 'textbox'<br>Name: 'Username'"]
Read1 --> Type1["3. Type: 'standard_user'"]
Type1 --> Tab2["4. Press 'Tab'"]
Tab2 --> Focus2["Focus shifts to: Password input field"]
Focus2 --> Read2["Accessibility Tree exposes:<br>Role: 'textbox'<br>Name: 'Password'"]
Read2 --> Type2["5. Type: 'secret_sauce'"]
Type2 --> Tab3["6. Press 'Tab'"]
Tab3 --> Focus3["Focus shifts to: Login submit button"]
Focus3 --> Read3["Accessibility Tree exposes:<br>Role: 'button'<br>Name: 'Login'"]
Read3 --> Enter["7. Press 'Enter' to submit form"]
Roles and Labels — Directly Testable
Recall Part 7’s getByRole('button', { name: 'Login' }). Here’s the genuinely important insight this whole part has been building toward: this locator succeeding is not incidental to accessibility — it’s a direct, positive accessibility signal. If getByRole('button', { name: 'Login' }) correctly and unambiguously finds SauceDemo’s login button, that’s concrete, mechanical proof that a screen reader user, encountering that exact same element, would also correctly hear “Login, button” announced — because both Playwright’s getByRole and a real screen reader are reading from the exact same underlying accessibility tree.
Keyboard Navigation
A genuinely large share of accessibility, beyond screen readers specifically, comes down to whether an application is fully usable without a mouse at all:
test("user can complete login using only the keyboard", async ({ page }) => {
await page.goto("/");
await page.keyboard.press("Tab"); // moves focus to the first focusable element
await page.keyboard.type("standard_user");
await page.keyboard.press("Tab"); // moves focus to the next field
await page.keyboard.type("secret_sauce");
await page.keyboard.press("Enter"); // submits, if the form responds correctly to Enter
await expect(page.getByText("Products")).toBeVisible();
});
This is a genuinely different, and genuinely valuable, kind of test from anything else in this series — it doesn’t use a single locator-based click or fill at all. It verifies something purely about navigability and focus order, which is exactly the thing a keyboard-only user actually depends on, and exactly the thing a purely locator-based, click-driven test suite would never, by its own nature, happen to catch on its own.
Automated Scanning with axe-core
Manually writing keyboard-navigation and role-based checks for every single page is valuable, but genuinely incomplete on its own — many accessibility issues (insufficient color contrast, missing form labels, improper heading structure) are things a broad, automated scanner can catch far more comprehensively and efficiently than manually writing individual, targeted assertions for every possible issue.
npm install -D @axe-core/playwright
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test("login page has no detectable accessibility violations", async ({
page,
}) => {
await page.goto("/");
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
AxeBuilder(...).analyze() runs a comprehensive, automated scan of the current page’s accessibility tree and reports concrete, specific violations — with the exact element affected, the specific rule violated, and typically a link explaining why it matters and how to fix it.
It’s worth being honest, though, about a real, important limitation, echoing the exact same caution Part 25 gave about visual testing’s own honest limits: automated scanning catches a genuinely large share of mechanical accessibility issues, but it cannot fully verify true, holistic usability — whether a screen reader user’s actual, real experience navigating through a whole page in order genuinely makes sense, whether focus moves in a logical, sensible sequence through a complex, multi-step interaction.
Automated scanning is a strong, valuable, efficient baseline; it is not, on its own, a complete substitute for genuine, periodic manual accessibility review, ideally involving people who actually rely on assistive technology in their everyday lives.
How It Works in a Real Test Run
Accessibility testing combines semantic inspection, keyboard behavior, visual review, assistive-technology testing, and user research. Automated axe scans catch rule-detectable problems but cannot decide whether alternative text is meaningful or a workflow is understandable.
Playwright locators based on role and accessible name exercise the same semantic surface exposed through the accessibility tree. That makes accessible design and resilient automation allies, while still requiring manual testing for the parts automation cannot judge.
Interview Questions
Q: What is the accessibility tree, and how is it related to the DOM?
Ans: The accessibility tree is a separate, parallel structure the browser builds from the DOM, specifically for assistive technology like screen readers to use. It’s a simplified representation focused specifically on each element’s role, accessible name, and state — the exact same three properties Playwright’s getByRole locator strategy is built around.
Q: Why is getByRole('button', { name: 'Login' }) succeeding considered direct evidence of good accessibility, rather than just a convenient locator strategy?
Ans: Because getByRole reads from the exact same underlying accessibility tree a real screen reader uses. If that locator correctly and unambiguously finds the intended button, it’s concrete, mechanical proof that a screen reader user encountering that same element would correctly perceive it as a button with that specific accessible name — the locator’s success and a screen reader’s correct behavior are drawing from the identical underlying source.
Q: Why is semantic HTML generally preferred over an ARIA-patched non-semantic element, even though ARIA attributes can technically make a <div> behave like a button?
Ans: A real <button> element automatically has the correct role, is automatically keyboard-focusable, and automatically responds correctly to both mouse and keyboard interaction, with none of that behavior needing to be manually added or remembered. A <div> retrofitted with ARIA attributes only works correctly if someone remembers to add every single necessary piece — role, label, focusability, and the actual keyboard-handling JavaScript — making it a genuinely more fragile and error-prone approach compared to simply using the correct semantic element from the start.
Q: Why does keyboard-navigation testing represent a genuinely different kind of test from typical locator-based click/fill tests in this series?
Ans: It verifies something about focus order and overall navigability — whether an application is fully usable without a mouse — rather than verifying the correctness of any single specific interaction. A test suite built entirely around locator-based clicks and fills could pass completely while an application is still genuinely unusable for a keyboard-only user, since focus order and keyboard responsiveness aren’t things that kind of test happens to check at all by its nature.
Q: What is a genuine, honest limitation of automated accessibility scanning tools like axe-core, even though they’re valuable and worth using?
Ans: Automated scanning catches a large share of mechanical, rule-based issues — like missing labels or insufficient color contrast — efficiently and comprehensively. It cannot fully verify true, holistic usability, such as whether a screen reader user’s actual real-world experience navigating a complex page in sequence genuinely makes logical sense. Automated scanning is a strong, valuable baseline, not a complete substitute for genuine manual accessibility review, ideally involving actual assistive-technology users.
Q: A team wants to add accessibility testing to their existing Playwright suite. What would you recommend as a starting approach, and why?
Ans: I’d recommend starting with automated scanning (using something like axe-core) integrated directly into the existing suite, since it’s efficient, broad, and catches a large share of common mechanical issues with relatively little added effort. I’d complement this with a smaller number of deliberate keyboard-navigation tests for the application’s most critical user journeys, and note that neither of these fully replaces periodic manual review — automated coverage is a strong, valuable starting baseline, not a complete accessibility testing strategy on its own.
Exercises — Part 35
Understand:
Explain, in your own words, why a <div> with role="button" added via ARIA is inherently more fragile than a real <button> element, listing at least two specific things that could be forgotten or done incorrectly with the ARIA-based approach that simply can’t go wrong with a real button.
Simple Practice:
Install @axe-core/playwright and run an automated accessibility scan against SauceDemo’s login page (or any real page you have access to), reviewing and writing down at least one violation reported, if any are found.
Real-World Scenario: Write a keyboard-navigation test for SauceDemo’s checkout flow (or another multi-step form you’ve tested earlier in this series), verifying that a user can Tab through each required field and submit the form using only the keyboard, without any mouse-based clicks at all.
Challenge:
Research the specific difference between aria-label and aria-labelledby, and write a short explanation, in your own words, of when you’d use each — along with one concrete example HTML snippet for each case.
Next: Part 36 — Performance Testing Perspective
— the honest, important boundary between browser automation and genuine performance/load testing.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed