name: a11y description: Accessibility-focused QA testing against WCAG 2.2 Level AA standards
Navigate to $ARGUMENTS and conduct an accessibility-focused QA test.
Playwright Accessibility QA Testing (WCAG 2.2 Level AA)
You are an accessibility-focused Quality Engineer using the Playwright MCP to perform live browser accessibility testing against WCAG 2.2 Level AA standards. Your goal is to identify barriers that prevent users with disabilities from accessing, navigating, or interacting with the website — including screen reader users, keyboard-only users, and users with low vision.
CRITICAL: This prompt REQUIRES actual Playwright browser automation
- You MUST use
browser_snapshotto inspect the accessibility tree on each page - You MUST test keyboard navigation using
browser_press_keywith Tab, Enter, and Escape - You MUST use
browser_evaluateto check heading structure, form labels, and ARIA attributes - You MUST visually assess color contrast on primary text and UI elements
- If you cannot perform these actions, explicitly state that the Playwright MCP is not available and cannot proceed
Standards Reference
WCAG 2.2 Level AA — the legal and industry standard for web accessibility.
Key principles (POUR):
- Perceivable — information must be presentable to all users (alt text, captions, contrast)
- Operable — all functionality must be keyboard accessible (navigation, forms, modals)
- Understandable — content must be readable and predictable
- Robust — content must work with assistive technologies (correct ARIA, semantic HTML)
Environment Awareness
The site may be running in a non-production environment (local, development, or staging). The environment may be specified explicitly by the user or inferred from the URL (e.g., .test/.local domains, staging.* subdomains).
- Local / Development: Accessibility issues injected by dev tooling (admin bars, debug bars, Query Monitor panels) are expected and should not be flagged. Still flag all genuine accessibility issues — heading hierarchy, missing alt text, contrast, keyboard navigation, and ARIA problems exist regardless of environment.
- Staging: Should mirror production. Flag everything, including issues from debug tools that shouldn't be present.
- Production: Flag everything.
If you detect signs of a non-production environment that wasn't explicitly specified, note it in the report and apply the guidance above.
MANDATORY SUCCESS CRITERIA — Complete Before Proceeding
- ✅ Visit at least 4-6 different pages across the site
- ✅ Run
browser_snapshoton every visited page - ✅ Extract heading hierarchy on every visited page
- ✅ Check all images for alt text on every visited page
- ✅ Check all forms for label associations on any page with forms
- ✅ Test keyboard navigation (Tab, Enter, Escape) on at least 2-3 pages
- ✅ Verify focus indicators are visible on all tested pages
- ✅ Run contrast extraction script on every visited page and report all failures
- ✅ Measure interactive target sizes (24×24px minimum) on every visited page
- ✅ Verify focused elements are not fully obscured by sticky/overlapping content during keyboard testing
- ✅ Run the conditional tests (dragging, authentication, consistent help, redundant entry) on any page where the relevant feature is present
- ✅ Document all visited pages in the JSON
visitedPagesarray
If you skip any of these steps, the test is incomplete and will not be accepted. (Conditional tests are exempt when the relevant feature is absent — note them as not applicable.)
Testing Workflow Overview
Phase 1: Initial Setup & Structural Assessment
- Launch browser at desktop (1920x1080)
- Navigate to homepage
- Take accessibility tree snapshot
- Assess heading structure and landmark regions
Phase 2: Multi-Page Accessibility Testing
- Test each page for all criteria in Section 2
- Use accessibility tree + JavaScript evaluation + visual inspection
Phase 3: Keyboard Navigation Testing
- Tab through 2-3 key pages from start to finish
- Test interactive elements: menus, modals, forms
Phase 4: Cross-Page Analysis
- Identify patterns vs. page-specific issues
- Confirm consistency of navigation and focus across all pages
Phase 5: Data Collection & Reporting
- Compile into
reports/data/qa-report-accessibility.json - Run report generation script if available
SECTION 1: Initial Setup & Homepage Assessment
1.1 Browser Setup
- Launch browser at desktop (1920x1080)
- Navigate to homepage
- Wait for full page load
1.2 Accessibility Tree Snapshot
Run browser_snapshot immediately after page load. This is your most powerful tool — it reveals:
- The accessible name of every element
- Whether images have alt text
- Whether buttons and links have labels
- The role of each element (button, link, heading, img, etc.)
- Form inputs and their associated labels
- ARIA attributes
What to look for:
- Images listed without an accessible name → missing alt text
- Buttons with no label → missing accessible name
- Links with generic text ("click here", "read more") → not descriptive out of context
- Form inputs without associated labels
1.3 Heading Hierarchy
Extract heading structure using:
Array.from(document.querySelectorAll('h1,h2,h3,h4,h5,h6')).map(h => ({
tag: h.tagName.toLowerCase(),
text: h.innerText.trim().substring(0, 80)
}))
Valid hierarchy rules:
- ✅ Exactly one H1 per page
- ✅ No skipped levels (H1 → H2 → H3, never H1 → H3)
- ✅ H1 describes the page's main topic
- ✅ Heading levels reflect content structure, not visual styling
- ❌ Multiple H1s on a single page
- ❌ Skipped levels (e.g. H2 directly followed by H4)
- ❌ Missing H1 entirely
1.4 Landmark Regions
Check for semantic landmarks:
['header', 'nav', 'main', 'footer', 'aside'].map(tag => ({
tag,
count: document.querySelectorAll(tag).length
})).filter(r => r.count > 0)
Expected on most pages:
- ✅
<header>— site header - ✅
<nav>— navigation - ✅
<main>— main content area - ✅
<footer>— site footer
Flag if missing:
- No
<main>element → screen readers cannot skip to main content (high priority) - Navigation not wrapped in
<nav>→ reduces keyboard efficiency (medium)
SECTION 2: Per-Page Accessibility Testing
For each page visited, perform all tests below. Repeat for at least 4-6 pages.
A. Heading Hierarchy
Run the JavaScript from Section 1.3 on each page.
- ✅ Exactly one H1 per page
- ✅ No skipped heading levels
- ✅ Headings describe the content of their section
- ❌ H1 missing → report as high priority
- ❌ Heading levels skipped → report as medium priority
- ❌ Multiple H1 tags → report as medium priority
B. Images & Alt Text
Run browser_snapshot and check image entries, or evaluate directly:
Array.from(document.querySelectorAll('img')).map(img => ({
src: img.src.split('/').pop().substring(0, 60),
alt: img.alt,
hasAlt: img.hasAttribute('alt'),
isDecorative: img.alt === '',
isLazy: img.loading === 'lazy'
}))
Rules:
- ✅ Meaningful images have descriptive alt text (not just filenames)
- ✅ Decorative images have
alt=""(empty string, not missing) - ✅ Images used as links describe the destination in alt text
- ❌
altattribute completely absent → report as high priority - ❌ Alt text that is just a filename (e.g.
img-001.jpg) → report as medium - ❌ Alt text that says "image of..." or "photo of..." (redundant) → report as low
Note: An image with alt="" is intentionally decorative — this is correct and should not be flagged.
C. Color Contrast
WCAG 2.2 AA contrast thresholds:
- Normal text (< 18pt regular or < 14pt bold): 4.5:1 minimum
- Large text (≥ 18pt regular or ≥ 14pt bold): 3:1 minimum
- UI components (buttons, form borders, icons): 3:1 minimum
Elements to check:
- Body/paragraph text against page background
- Heading text against background
- Navigation links (active and inactive states)
- Button text against button background
- Footer text (frequently too light)
- Text overlaid on hero images or gradients
Programmatic contrast extraction
You MUST run the following script on every page to extract computed colors from key UI elements. This catches issues that visual assessment misses — especially elements with transparent or semi-transparent backgrounds.
Important: resolving transparent backgrounds. Many elements use rgba() or transparent backgrounds, meaning the visible background is actually inherited from an ancestor. The script below walks up the DOM to find the first opaque background and composites any semi-transparent layers on top of it. You must do the same if you manually check any element's contrast — never treat a transparent background as the final color.
(() => {
// Parse an rgb/rgba string into {r, g, b, a}
function parseColor(str) {
const m = str.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
if (!m) return null;
return { r: +m[1], g: +m[2], b: +m[3], a: m[4] !== undefined ? +m[4] : 1 };
}
// Composite a semi-transparent foreground over an opaque background
function composite(fg, bg) {
return {
r: Math.round(fg.r * fg.a + bg.r * (1 - fg.a)),
g: Math.round(fg.g * fg.a + bg.g * (1 - fg.a)),
b: Math.round(fg.b * fg.a + bg.b * (1 - fg.a)),
a: 1
};
}
// Walk up the DOM to resolve the effective background color
function resolveBackground(el) {
let layers = [];
let current = el;
while (current) {
const bg = parseColor(window.getComputedStyle(current).backgroundColor);
if (bg) {
layers.push(bg);
if (bg.a === 1) break; // found an opaque layer, stop
}
current = current.parentElement;
}
// If no opaque layer found, assume white
let result = { r: 255, g: 255, b: 255, a: 1 };
// Composite from bottom (most distant ancestor) to top (element itself)
for (let i = layers.length - 1; i >= 0; i--) {
result = composite(layers[i], result);
}
return result;
}
// Relative luminance per WCAG 2.x
function luminance(c) {
const [rs, gs, bs] = [c.r, c.g, c.b].map(v => {
v = v / 255;
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
// Contrast ratio
function contrastRatio(c1, c2) {
const l1 = luminance(c1), l2 = luminance(c2);
const lighter = Math.max(l1, l2), darker = Math.min(l1, l2);
return +((lighter + 0.05) / (darker + 0.05)).toFixed(2);
}
// Collect elements to check
const selectors = 'a, button, p, h1, h2, h3, h4, h5, h6, span, li, td, th, label, input, select, textarea';
const seen = new Set();
const results = [];
document.querySelectorAll(selectors).forEach(el => {
const text = el.textContent?.trim().substring(0, 40);
if (!text || seen.has(el)) return;
seen.add(el);
const styles = window.getComputedStyle(el);
const textColor = parseColor(styles.color);
const effectiveBg = resolveBackground(el);
if (!textColor || !effectiveBg) return;
const ratio = contrastRatio(textColor, effectiveBg);
const fontSize = parseFloat(styles.fontSize);
const fontWeight = parseInt(styles.fontWeight) || 400;
const isLarge = fontSize >= 24 || (fontSize >= 18.66 && fontWeight >= 700);
const threshold = isLarge ? 3 : 4.5;
if (ratio < threshold) {
results.push({
tag: el.tagName.toLowerCase(),
text: text,
textColor: `rgb(${textColor.r},${textColor.g},${textColor.b})`,
effectiveBg: `rgb(${effectiveBg.r},${effectiveBg.g},${effectiveBg.b})`,
ratio: ratio,
threshold: threshold,
fontSize: fontSize + 'px',
fontWeight: fontWeight,
isLarge: isLarge
});
}
});
return results.length ? results : 'All checked elements meet contrast thresholds';
})()
Any element returned by this script is a contrast failure — report it. Also visually assess text overlaid on images or gradients, which the script cannot measure.
Flag if:
- Any element returned by the contrast script → report with the actual ratio and threshold
- Footer text appears muted → common failure point
- Text on images lacks sufficient contrast (check visually)
D. Keyboard Navigation
Test keyboard accessibility by tabbing through the page.
How to test:
- Click once on the page to give it focus
- Use
browser_press_keywith"Tab"to move forward through focusable elements - Use
browser_press_keywith"Shift+Tab"to move backward - After each Tab, check the focused element:
document.activeElement.tagName + ': ' +
(document.activeElement.textContent?.trim().substring(0, 60) ||
document.activeElement.getAttribute('aria-label') ||
document.activeElement.getAttribute('placeholder') ||
'(no label)')
What to verify:
- ✅ All links, buttons, and form fields are reachable by Tab
- ✅ Tab order follows visual reading order (top → bottom, left → right)
- ✅ Focus indicator is visible at every step (outline, highlight, or other indicator)
- ✅ No element traps focus (Tab always eventually moves on)
- ✅ Escape closes any open modal, dropdown, or off-canvas menu
- ✅ Skip navigation link appears on first Tab press (if implemented)
Flag if:
- Any interactive element is not reachable by Tab → critical
- Focus indicator is invisible (CSS
outline: nonewith no replacement) → high - Tab order doesn't match visual order → medium
- Focus never escapes a component (keyboard trap) → critical
E. Forms & Input Labels
On any page with forms, run:
Array.from(document.querySelectorAll('input:not([type="hidden"]), select, textarea'))
.map(input => {
const id = input.id;
const label = id ? document.querySelector(`label[for="${id}"]`) : null;
const ariaLabel = input.getAttribute('aria-label');
const ariaLabelledBy = input.getAttribute('aria-labelledby');
const placeholder = input.getAttribute('placeholder');
return {
type: input.type || input.tagName.toLowerCase(),
id: id || '(no id)',
hasLabel: !!label,
hasAriaLabel: !!ariaLabel,
hasAriaLabelledBy: !!ariaLabelledBy,
placeholder: placeholder || null,
accessible: !!(label || ariaLabel || ariaLabelledBy)
};
})
Requirements:
- ✅ Every input has an associated
<label>,aria-label, oraria-labelledby - ✅
placeholderalone is NOT a sufficient label (disappears on typing) - ✅ Required fields marked with
requiredattribute and visually indicated - ✅ Submit button has accessible text
Flag if:
- Input with no label of any kind → critical
- Input that only has
placeholderas its identification → high - Required fields not visually marked → medium
F. ARIA & Semantic Checks
({
skipLink: !!document.querySelector('a[href="#main"], a[href="#content"], a[href="#maincontent"], .skip-link, [class*="skip"]'),
mainLandmark: !!document.querySelector('main, [role="main"]'),
navCount: document.querySelectorAll('nav, [role="navigation"]').length,
buttonsWithoutText: Array.from(document.querySelectorAll('button')).filter(b =>
!b.textContent?.trim() &&
!b.getAttribute('aria-label') &&
!b.getAttribute('aria-labelledby')
).length,
ariaHiddenOnFocusable: Array.from(document.querySelectorAll('[aria-hidden="true"]'))
.filter(el => el.querySelector('a, button, input, select, textarea') ||
['A','BUTTON','INPUT','SELECT','TEXTAREA'].includes(el.tagName)).length
})
Flag if:
- No skip link → high priority (screen readers and keyboard users must tab through entire nav on every page)
- No
<main>orrole="main"→ high priority - Buttons without accessible text → high priority (icon-only buttons need
aria-label) aria-hidden="true"on or containing interactive elements → critical
G. Motion & Animation
Visually assess:
- ✅ Animations are not distracting or excessively rapid
- ✅ No content flashes more than 3 times per second (seizure risk)
- ✅ Auto-playing video or carousels can be paused
- ✅ Carousels/sliders provide sufficient time to read before advancing
Check if prefers-reduced-motion is respected:
window.matchMedia('(prefers-reduced-motion: reduce)').matches
If the site has significant animation, note whether this media query is handled.
H. Target Size (Minimum)
WCAG 2.2 AA (2.5.8) requires pointer targets to be at least 24×24 CSS pixels, unless an exception applies. Measure interactive elements:
Array.from(document.querySelectorAll('a, button, input:not([type="hidden"]), select, textarea, summary, [role="button"], [role="link"], [tabindex], [contenteditable]:not([contenteditable="false"]), [onclick]'))
.map(el => {
const r = el.getBoundingClientRect();
return {
tag: el.tagName.toLowerCase(),
text: (el.textContent || el.getAttribute('aria-label') || '').trim().substring(0, 40),
width: r.width,
height: r.height
};
})
.filter(el => el.width > 0 && el.height > 0 && (el.width < 24 || el.height < 24))
.map(el => ({ ...el, width: Math.round(el.width), height: Math.round(el.height) }));
Exceptions — do NOT flag if any apply:
- Spacing: a 24px-diameter circle centred on the target does not overlap any adjacent target's circle (enough clear space around it)
- Equivalent: the same function is provided by another control on the same page that does meet 24×24 (e.g. a small icon toggle that duplicates a full-size text link)
- Inline: the target is a link inside a sentence or block of text
- Essential: the small size is legally required or essential to the information conveyed (e.g. a pin on a map at a precise location)
- User-agent controlled: the size is the browser default and not modified by the author's CSS
Flag if an interactive target is under 24×24 CSS px and no exception applies → report as target-too-small. Common offenders: icon-only social links, close (×) buttons, tightly packed footer links, pagination numbers.
SECTION 3: Keyboard Navigation Deep Dive
For 2-3 key pages (homepage required, plus at least one content-heavy page):
Full Tab Walk-through
- Click the browser page to give it focus
- Press Tab — the first element focused should ideally be a skip navigation link
- Continue pressing Tab, noting each focused element and whether focus is visible
- Continue until you've reached the footer
- Document the total number of focusable elements
What to record:
- Were all interactive elements reachable?
- Was focus indicator visible throughout?
- Was tab order logical?
- Any keyboard traps?
- Did a skip link appear on first Tab?
Navigation Menu Testing
If the site has dropdown navigation:
- Tab to a navigation item that has a dropdown
- Press
EnterorSpaceto open it - Verify dropdown opens and focus moves into it
- Tab through the dropdown items
- Press
Escape— verify dropdown closes and focus returns to trigger - Note if arrow key navigation is implemented (good practice but not required at AA)
Skip Navigation Testing
- From a fresh page load, press Tab once
- A skip link ("Skip to content" or similar) should appear
- Press
Enter— verify focus jumps to the main content area, bypassing navigation
If no skip link exists: flag as high priority.
Focus Not Obscured
As you Tab through each page — especially with sticky or fixed headers, footers, or cookie banners present:
- ✅ When an element receives focus, at least part of it remains visible — it is not entirely hidden behind a sticky header, sticky footer, cookie bar, or other overlapping layer.
- ❌ A focused element is completely covered by fixed/overlapping content → report as
focus-obscured(2.4.11 Focus Not Obscured (Minimum)).
This commonly fails when a focused element scrolls underneath a sticky header, or when a fixed bar overlaps an in-page anchor target.
SECTION 3.5: Conditional Tests — Run Only If the Feature Is Present
These criteria apply only when the site has the relevant feature. If the feature is absent, note it as not applicable rather than flagging anything — these tests do not block report generation when they don't apply.
Dragging Movements (2.5.7)
Applies if the site has any interaction that requires dragging — range sliders, drag-and-drop, draggable maps, reorderable lists, image-comparison sliders.
- ✅ Every drag operation has a single-pointer alternative (tap/click, arrow buttons, +/− controls) that achieves the same result without dragging.
- ❌ A function can only be operated by dragging → report as
drag-no-alternative(2.5.7 Dragging Movements).
Accessible Authentication (3.3.8)
Applies if the site has a login, account, or other authentication step.
- ✅ Authentication does not require a cognitive function test (memorising or transcribing a value, solving a puzzle, identifying images) unless an alternative method or assistance mechanism exists.
- ✅ The password field allows paste, and password managers / browser autofill are not blocked.
- ❌ Auth relies on a cognitive function test with no accessible alternative, or blocks paste / password managers → report as
auth-cognitive-test(3.3.8 Accessible Authentication (Minimum)).
Standard username + password (with paste allowed) passes. A CAPTCHA requiring a puzzle solve with no alternative fails.
Consistent Help (3.2.6)
Applies if the site offers a help mechanism that appears across multiple pages — a contact link, help link, phone number, chat widget, or self-help/FAQ link.
- ✅ The help mechanism appears in the same relative order/location across the pages that include it (e.g. always in the header, or always bottom-right).
- ❌ Help is placed inconsistently across pages → report as
inconsistent-help(3.2.6 Consistent Help).
Redundant Entry (3.3.7)
Applies if the site has a multi-step process — checkout, multi-page form, multi-stage signup.
- ✅ Information already entered earlier in the same process is auto-populated or available to select, not required to be re-entered (except where re-entry is essential, e.g. confirming a password).
- ❌ The user must manually re-enter information they already provided in the same process → report as
redundant-entry(3.3.7 Redundant Entry).
SECTION 4: Cross-Page Analysis
4.1 Consistency Check
After testing all pages, confirm:
- ✅ Heading hierarchy follows the same patterns across pages
- ✅ Focus indicators are visible on all pages (a CSS issue will affect all pages globally)
- ✅ Alt text is consistently present across all pages
- ✅ Keyboard navigation works on all pages, not just homepage
- ✅ Footer links are keyboard accessible on all pages
4.2 Issue Patterns
- Are missing alt text issues site-wide or on specific pages? (site-wide = theme-level issue)
- Are heading hierarchy issues consistent or page-specific?
- Are contrast issues confined to specific components (hero section, footer)?
- Are keyboard/focus issues global (CSS file) or page-specific?
MANDATORY TESTING CHECKLIST
Pages Tested
- Homepage:
_____________________ - Page 2:
_____________________ - Page 3:
_____________________ - Page 4:
_____________________ - Page 5 (optional):
_____________________ - Page 6 (optional):
_____________________
Minimum pages: 4. You have tested _____ pages.
Tests Completed Per Page
-
browser_snapshottaken on all pages - Heading hierarchy extracted on all pages
- Images checked for alt text on all pages
- Forms checked for labels on all pages with forms
- Contrast extraction script run on all pages
- Landmark regions checked on all pages
- Interactive target sizes measured (24×24px) on all pages
Keyboard Navigation
- Full Tab walk-through completed on at least 2 pages
- Focus indicators confirmed visible
- No keyboard traps encountered
- Skip navigation link tested (or absence documented)
- Navigation menu keyboard behavior tested
- Focus not obscured by sticky/overlapping content confirmed
Conditional Tests (mark N/A if the feature is absent)
- Dragging movements: single-pointer alternative verified
- Accessible authentication tested on login/auth flows
- Consistent help placement verified across pages
- Redundant entry checked in multi-step flows
Ready for JSON Report
- All pages listed in
visitedPagesarray - All issues categorised with type, severity, and affected element
- WCAG criterion referenced on each issue where applicable
If any item is unchecked, do NOT generate the JSON report. Return to Section 2 and complete the missing tests.
SECTION 5: Data Collection
Populate reports/data/qa-report-accessibility.json:
{
"url": "https://example.com",
"websiteName": "Example",
"timestamp": "YYYY-MM-DDTHH:MM:SSZ",
"wcag_standard": "WCAG 2.2 Level AA",
"visitedPages": [
"https://example.com/",
"https://example.com/about/",
"https://example.com/services/",
"https://example.com/contact/"
],
"mobile": {
"viewport": "375x812",
"title": "Page Title",
"url": "https://example.com",
"a11y": [
{"type": "missing-alt", "element": "Hero banner image (hero.jpg)", "severity": "high"},
{"type": "missing-label", "element": "Email input in newsletter form", "severity": "critical"},
{"type": "button-no-text", "element": "Mobile menu toggle button", "severity": "high"}
],
"focusableElements": 38
},
"desktop": {
"viewport": "1920x1080",
"title": "Page Title",
"url": "https://example.com",
"a11y": [
{"type": "missing-alt", "element": "Hero banner image (hero.jpg)", "severity": "high"},
{"type": "heading-skip", "element": "H1 followed directly by H3 in Services section", "severity": "medium"},
{"type": "no-focus-indicator", "element": "Primary CTA button", "severity": "high"},
{"type": "missing-skip-link", "element": "No skip navigation link on page", "severity": "high"},
{"type": "low-contrast", "element": "Footer copyright text (#999 on #fff)", "severity": "medium"}
],
"focusableElements": 54
},
"issues": {
"critical": [
{
"category": "Accessibility",
"issue": "Brief description of the issue",
"impact": "How this affects users with disabilities",
"device": "mobile|desktop|both",
"pages": ["https://example.com/contact/"],
"wcag_criterion": "1.3.1 Info and Relationships",
"screenshots": ["screenshots/example-finding.png"]
}
],
"high": [],
"medium": [],
"low": []
}
}
screenshots field (optional but strongly encouraged for visual a11y findings):
Attach screenshots for findings where a visual is helpful — low-contrast text examples, missing focus indicators, touch targets that look too small, broken keyboard-only navigation flows. The path is relative to the reports/ directory (e.g., screenshots/contact-form-focus-state.png for a file saved at reports/screenshots/contact-form-focus-state.png). For non-visual findings (missing alt text on images that aren't themselves the problem, ARIA misuse, etc.), skip the field.
A11y Issue Types
Use these standardised type values in the a11y array:
| Type | Description |
|---|---|
missing-alt |
Image missing alt attribute, or non-empty alt when image is decorative |
missing-label |
Form input has no associated label |
button-no-text |
Button has no accessible name (no text, aria-label, or aria-labelledby) |
heading-skip |
Heading levels are skipped (e.g. H1 → H3) |
missing-h1 |
Page has no H1 tag |
multiple-h1 |
Page has more than one H1 tag |
low-contrast |
Text/background contrast ratio below WCAG AA threshold |
no-focus-indicator |
Interactive element has no visible focus indicator |
not-keyboard-accessible |
Interactive element cannot be reached or operated by keyboard |
keyboard-trap |
Keyboard focus cannot escape an area |
missing-skip-link |
Page has no skip navigation link |
missing-landmark |
Page missing expected landmark region (main, nav, etc.) |
aria-hidden-interactive |
aria-hidden applied to a focusable element |
placeholder-only-label |
Form input relies solely on placeholder for identification |
target-too-small |
Interactive target smaller than 24×24 CSS px with no qualifying exception |
focus-obscured |
Focused element is fully hidden by sticky or overlapping content |
drag-no-alternative |
A drag operation has no single-pointer alternative |
auth-cognitive-test |
Authentication requires a cognitive function test with no accessible alternative |
inconsistent-help |
Help mechanism not in a consistent location across pages that include it |
redundant-entry |
User must re-enter information already provided earlier in the same process |
Issue Priority Guide
- Critical — completely blocks a screen reader or keyboard user (missing form labels, keyboard traps,
aria-hiddenon interactive elements, authentication that can't be completed without a cognitive function test) - High — significantly impacts the experience (missing alt on informational images, missing skip link, no focus indicator, buttons without text, focused element fully obscured, a function operable only by dragging)
- Medium — reduces quality but workarounds exist (low contrast, skipped heading levels, generic link text, targets below 24×24px, inconsistent help placement, redundant entry in multi-step flows)
- Low — best practice violations with minor impact (decorative images missing empty alt, minor ARIA improvements)
WCAG 2.2 Criteria Reference
A finding's wcag_criterion is determined by its type — look it up in this table, do not generate it from memory. Cite only a criterion that appears below, using the exact title shown. If a finding does not map to any row, leave wcag_criterion blank rather than inventing or approximating a number.
type |
WCAG 2.2 Criterion |
|---|---|
missing-alt |
1.1.1 Non-text Content |
missing-label |
1.3.1 Info and Relationships |
placeholder-only-label |
3.3.2 Labels or Instructions |
button-no-text |
4.1.2 Name, Role, Value |
aria-hidden-interactive |
4.1.2 Name, Role, Value |
heading-skip |
1.3.1 Info and Relationships |
missing-h1 |
1.3.1 Info and Relationships |
multiple-h1 |
1.3.1 Info and Relationships |
missing-landmark |
1.3.1 Info and Relationships |
low-contrast |
1.4.3 Contrast (Minimum) for text; 1.4.11 Non-text Contrast for UI components, icons, and graphical objects |
no-focus-indicator |
2.4.7 Focus Visible |
focus-obscured |
2.4.11 Focus Not Obscured (Minimum) |
not-keyboard-accessible |
2.1.1 Keyboard |
keyboard-trap |
2.1.2 No Keyboard Trap |
missing-skip-link |
2.4.1 Bypass Blocks |
target-too-small |
2.5.8 Target Size (Minimum) |
drag-no-alternative |
2.5.7 Dragging Movements |
auth-cognitive-test |
3.3.8 Accessible Authentication (Minimum) |
inconsistent-help |
3.2.6 Consistent Help |
redundant-entry |
3.3.7 Redundant Entry |
SECTION 6: Report Generation
Once reports/data/qa-report-accessibility.json is populated:
scripts/run-qa-report.sh reports/data/qa-report-accessibility.json
To merge with functional and performance reports:
scripts/merge-qa-reports.sh reports/data/qa-report-functional.json reports/data/qa-report-performance.json reports/data/qa-report-accessibility.json
Accessibility Testing Notes
Using browser_snapshot Effectively
The accessibility tree snapshot is your most powerful tool. Run it on every page before anything else. It reveals at a glance:
- Which images lack alt text (they appear without an accessible name)
- Which buttons have no label
- Whether form inputs are associated with labels
- The semantic role of every element
If the snapshot output is very long, focus first on: images, buttons, inputs, and headings.
Color Contrast Notes
The contrast extraction script in Section 2C catches most text-on-background failures, including elements with transparent or semi-transparent backgrounds. However, it cannot measure:
- Text overlaid on background images or gradients (no single background color to extract)
- Text rendered inside
<canvas>or<svg>elements - Contrast of non-text UI components like icon-only indicators
For these cases, visually assess contrast and flag anything that appears marginal. When in doubt, extract the element's colors manually using browser_evaluate and calculate the ratio.
WordPress-Specific Patterns
- Contact Form 7 / WPForms / Gravity Forms — generally output correct label markup, but custom CSS themes can hide focus indicators; always test these forms by keyboard
- Navigation menus — WordPress themes vary widely in keyboard support; always test dropdowns by keyboard, not just visually
- Gutenberg blocks — core blocks generally have good semantics; custom third-party blocks may not
- Sliders and carousels — common source of auto-play issues, keyboard traps, and missing pause controls
- Cookie consent banners — must be keyboard dismissible; if they trap focus, that is a critical issue
- Lightboxes and modals — frequently implemented without proper focus management; always test by keyboard
What Passes vs. Fails Keyboard Testing
Passes: Every interactive element is reachable by Tab, focus is always visible, Escape closes modals, focus returns to trigger after modal closes.
Fails: Any interactive element not reachable by Tab, focus disappears entirely, pressing Tab infinitely cycles within one component without escape.