Run axe-core over a demo page, have AI explain each violation with WCAG citations, then complete the manual keyboard and screen-reader pass. The page carries seeded a11y bugs that scanners provably cannot catch — finding them is the pass condition, and the final report must separate scanner-caught from human-caught.
Scanners catch roughly 30–40% of accessibility issues; the industry keeps re-learning this number and the vendors keep letting people forget it. Deepa's team runs axe in CI, sees zero violations, and calls the release accessible — which is exactly the calibration failure this lab attacks. The page below plants both kinds of defect: machine-visible ones (axe will flag them, and AI will explain them well) and machine-invisible ones — technically conformant markup that is unusable by keyboard or absurd through a screen reader. Your artifact is an a11y report with two honest columns: what the scanner caught, and what only a human could.
~75 minutes hands-on · Core tier (maps to module M5 and playbook PB-29). Cost: $0 (axe-core CLI, Playwright, a free AI tier; screen reader = VoiceOver on macOS or NVDA on Windows).
mkdir -p qa-lab-10 && cd qa-lab-10 && npm init -y
npm install --save-dev @axe-core/cli @playwright/test axe-playwright serve
npx playwright install chromium
index.html — the page under test. Author it exactly; every oddity is load-bearing:
<h1 style="color:#9a9a9a;background:#ffffff">Team Settings</h1>
<img src="avatar.png" alt="image">
<p>Manage who can access your workspace below.</p>
<label>Search members <input type="search" id="q"></label>
<input type="text" placeholder="Invite by email">
<div role="button" tabindex="0" id="invite-btn"
style="outline:none;border:1px solid #888;padding:6px;width:120px">
Send invite
</div>
<h2>Members</h2>
<div id="menu-wrap">
<button id="role-menu-btn" aria-haspopup="true" aria-expanded="false">
Change role
</button>
<div id="role-menu" hidden>
<div class="opt" tabindex="0">Viewer</div>
<div class="opt" tabindex="0">Editor</div>
<div class="opt" tabindex="0">Admin</div>
</div>
</div>
<p><span style="color:#c0392b">Red rows</span> indicate suspended accounts.</p>
<table>
<tr><td>Asha Rao</td><td>Editor</td></tr>
<tr style="color:#c0392b"><td>Vikram Nair</td><td>Viewer</td></tr>
</table>
<a href="#" onclick="location='help.html'">Click here</a> for role documentation.
<script>
const btn = document.getElementById("invite-btn");
btn.addEventListener("click", () => alert("Invite sent"));
const menuBtn = document.getElementById("role-menu-btn");
const menu = document.getElementById("role-menu");
menuBtn.addEventListener("click", () => {
menu.hidden = false;
menuBtn.setAttribute("aria-expanded", "true");
document.querySelectorAll(".opt").forEach((o) => {
o.addEventListener("keydown", (e) => {
if (e.key === "Tab") e.preventDefault(); // keep focus in menu
});
});
});
</script>
Serve it: npx serve . (note the port). Create a11y-report.md with two headings: "Scanner-caught" and "Human-caught."
npx axe http://localhost:3000 --save axe-results.json (adjust port). List every violation in the "Scanner-caught" section: rule id, impacted node, severity. Expect a handful — contrast, image-alt quality is partially checkable, form labeling. Do not fix anything yet.role="button" with tabindex satisfies the rule; the rule cannot know no key handler exists) — and tag it with its WCAG SC (AI may help; verify citations).<label> for the email input; Enter/Space activation on the invite control (or make it a real <button> — the better fix, say why); Escape/Tab release plus arrow-key support for the menu; a non-color indicator for suspended rows; a descriptive link. Rerun axe (clean) and redo the keyboard pass (every task completable). The double re-verify — scanner and hands — mirrors the two-column report."Zero axe violations" is a statement about markup patterns, not about usability. Every seeded human-only bug in this page passes the scanner precisely because the rules can only see structure — a role attribute with no behavior behind it is invisible to them and fatal to a keyboard user.
axe-results.json saved; every violation explained with a verified WCAG citation.Self-grading rubric (pass = 7/10):
| Dimension | 0 | 1 | 2 |
|---|---|---|---|
| Scanner findings handled | Raw dump | Explained | Explained + citations spot-verified |
| Human-only bugs found | ≤2 | 3–4 | All 5+ |
| Attribution accuracy | Columns mixed up | Mostly right | Every finding correctly attributed with why |
| Fix quality | Cosmetic | Works | Works + semantic-HTML-first choices explained |
| Calibration honesty | No numbers | Counts only | Counts + prediction delta + playbook sentence |
Calibrated trust means knowing the scanner's 30–40% and staffing the other 60–70% deliberately: axe finds pattern violations, AI explains them with citations you spot-check, and the keyboard and screen-reader passes find the bugs that legal markup hides. The two-column report is the calibration made visible — and the human column is why a11y stays a QA skill.