a11y-playwright-testing

Accessibility testing for web applications using Playwright (@playwright/test) with TypeScript and axe-core. Use when asked to write, run, or debug automated accessibility checks, keyboard navigation tests, focus management, ARIA/semantic validations, screen reader compatibility, or WCAG 2.1 Level AA compliance testing. Covers axe-core integration, POUR principles (perceivable, operable, understandable, robust), color contrast, form labels, landmarks, and accessible names.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "a11y-playwright-testing" with this command: npx skills add fugazi/test-automation-skills-agents/fugazi-test-automation-skills-agents-a11y-playwright-testing

Playwright Accessibility Testing (TypeScript)

Comprehensive toolkit for automated accessibility testing using Playwright with TypeScript and axe-core. Enables WCAG 2.1 Level AA compliance verification, keyboard operability testing, semantic validation, and accessibility regression prevention.

Activation: This skill is triggered when working with accessibility testing, WCAG compliance, axe-core scans, keyboard navigation tests, focus management, ARIA validation, or screen reader compatibility.

When to Use This Skill

  • Automated a11y scans with axe-core for WCAG 2.1 AA compliance
  • Keyboard navigation tests for Tab/Enter/Space/Escape/Arrow key operability
  • Focus management validation for dialogs, menus, and dynamic content
  • Semantic structure assertions for landmarks, headings, and ARIA
  • Form accessibility testing for labels, errors, and instructions
  • Color contrast and visual accessibility verification
  • Screen reader compatibility testing patterns

Prerequisites

RequirementDetails
Node.jsv18+ recommended
Playwright@playwright/test installed
axe-core@axe-core/playwright package
TypeScriptConfigured in project

Quick Setup

# Add axe-core to existing Playwright project
npm install -D @axe-core/playwright axe-core

First Questions to Ask

Before writing accessibility tests, clarify:

  1. Scope: Which pages/flows are in scope? What's explicitly excluded?
  2. Standard: WCAG 2.1 AA (default) or specific organizational policy?
  3. Priority: Which components are highest risk (forms, modals, navigation, checkout)?
  4. Exceptions: Known constraints (legacy markup, third-party widgets)?
  5. Assistive Tech: Which screen readers/browsers need manual testing?

Core Principles

1. Automation Limitations

⚠️ Critical: Automated tooling can detect ~30-40% of accessibility issues. Use automation to prevent regressions and catch common failures; manual audits are required for full WCAG conformance.

2. Semantic HTML First

Prefer native HTML semantics over ARIA. Use ARIA only when native elements cannot achieve the required semantics.

// ✅ Semantic HTML - inherently accessible
await page.getByRole('button', { name: 'Submit' }).click();

// ❌ ARIA override - requires manual keyboard/focus handling
await page.locator('[role="button"]').click(); // Often a <div>

3. Locator Strategy as A11y Signal

If you cannot locate an element by role or label, it's often an accessibility defect.

Locator SuccessAccessibility Signal
getByRole('button', { name: 'Submit' })Button has accessible name
getByLabel('Email')Input properly labeled
getByRole('navigation')Landmark exists
locator('.submit-btn') ⚠️May lack accessible name

Key Workflows

Automated Axe Scan (WCAG 2.1 AA)

import AxeBuilder from '@axe-core/playwright';
import { test, expect } from '@playwright/test';

test('page has no WCAG 2.1 AA violations', async ({ page }) => {
  await page.goto('/');
  
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .analyze();
  
  expect(results.violations).toEqual([]);
});

Scoped Axe Scan (Component-Level)

test('form component is accessible', async ({ page }) => {
  await page.goto('/contact');
  
  const results = await new AxeBuilder({ page })
    .include('#contact-form') // Scope to specific component
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .analyze();
  
  expect(results.violations).toEqual([]);
});

Keyboard Navigation Test

test('form is keyboard navigable', async ({ page }) => {
  await page.goto('/login');
  
  // Tab to first field
  await page.keyboard.press('Tab');
  await expect(page.getByLabel('Email')).toBeFocused();
  
  // Tab to password
  await page.keyboard.press('Tab');
  await expect(page.getByLabel('Password')).toBeFocused();
  
  // Tab to submit button
  await page.keyboard.press('Tab');
  await expect(page.getByRole('button', { name: 'Sign in' })).toBeFocused();
  
  // Submit with Enter
  await page.keyboard.press('Enter');
  await expect(page).toHaveURL(/dashboard/);
});

Dialog Focus Management

test('dialog traps and returns focus', async ({ page }) => {
  await page.goto('/settings');
  const trigger = page.getByRole('button', { name: 'Delete account' });
  
  // Open dialog
  await trigger.click();
  const dialog = page.getByRole('dialog');
  await expect(dialog).toBeVisible();
  
  // Focus should be inside dialog
  await expect(dialog.getByRole('button', { name: 'Cancel' })).toBeFocused();
  
  // Tab should stay trapped in dialog
  await page.keyboard.press('Tab');
  await expect(dialog.getByRole('button', { name: 'Confirm' })).toBeFocused();
  await page.keyboard.press('Tab');
  await expect(dialog.getByRole('button', { name: 'Cancel' })).toBeFocused();
  
  // Escape closes and returns focus to trigger
  await page.keyboard.press('Escape');
  await expect(dialog).toBeHidden();
  await expect(trigger).toBeFocused();
});

Skip Link Validation

test('skip link moves focus to main content', async ({ page }) => {
  await page.goto('/');
  
  // First Tab should focus skip link
  await page.keyboard.press('Tab');
  const skipLink = page.getByRole('link', { name: /skip to (main|content)/i });
  await expect(skipLink).toBeFocused();
  
  // Activating skip link moves focus to main
  await page.keyboard.press('Enter');
  await expect(page.locator('#main, [role="main"]').first()).toBeFocused();
});

POUR Principles Reference

PrincipleFocus AreasExample Tests
PerceivableAlt text, captions, contrast, structureImage alternatives, color contrast ratio
OperableKeyboard, focus, timing, navigationTab order, focus visibility, skip links
UnderstandableLabels, instructions, errors, consistencyForm labels, error messages, predictable behavior
RobustValid HTML, ARIA, name/role/valueSemantic structure, accessible names

Axe-Core Tags Reference

TagWCAG LevelUse Case
wcag2aLevel AMinimum compliance
wcag2aaLevel AAStandard target
wcag2aaaLevel AAAEnhanced (rarely full)
wcag21a2.1 Level AWCAG 2.1 specific A
wcag21aa2.1 Level AAWCAG 2.1 standard
best-practiceBeyond WCAGAdditional recommendations

Default Tags (WCAG 2.1 AA)

const WCAG21AA_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'];

Exception Handling

When exceptions are unavoidable:

  1. Scope narrowly - specific component/route only
  2. Document impact - which WCAG criterion, user impact
  3. Set expiration - owner + remediation date
  4. Track ticket - link to remediation issue
// ❌ Avoid: Global rule disable
new AxeBuilder({ page }).disableRules(['color-contrast']);

// ✅ Better: Scoped exclusion with documentation
new AxeBuilder({ page })
  .exclude('#third-party-widget') // Known issue: JIRA-1234, fix by Q2
  .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
  .analyze();

Troubleshooting

ProblemCauseSolution
Axe finds 0 violations but app fails manual auditAutomation covers ~30-40%Add manual testing checklist
False positive on dynamic contentContent not fully renderedWait for stable state before scan
Color contrast fails incorrectlyBackground image/gradientUse exclude for known false positives
Cannot find element by roleMissing semantic HTMLFix markup - this is a real bug
Focus not visibleMissing :focus stylesAdd visible focus indicator CSS
Dialog focus not trappedMissing focus trap logicImplement focus trap (see snippets)
Skip link doesn't workTarget missing tabindex="-1"Add tabindex to main content

CLI Quick Reference

CommandDescription
npx playwright test --grep "a11y"Run accessibility tests only
npx playwright test --headedRun with visible browser for debugging
npx playwright test --debugStep through with Inspector
PWDEBUG=1 npx playwright testDebug mode with pause

References

DocumentContent
Snippetsaxe-core setup, helpers, keyboard/focus patterns
WCAG 2.1 AA ChecklistManual audit checklist by POUR principle
ARIA PatternsCommon ARIA widget patterns and validations

External Resources

ResourceURL
WCAG 2.1 Specificationhttps://www.w3.org/TR/WCAG21/
WCAG Quick Referencehttps://www.w3.org/WAI/WCAG21/quickref/
WAI-ARIA Authoring Practiceshttps://www.w3.org/WAI/ARIA/apg/
axe-core Ruleshttps://dequeuniversity.com/rules/axe/

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

playwright-e2e-testing

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

qa-manual-istqb

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

webapp-playwright-testing

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

qa-test-planner

No summary provided by upstream source.

Repository SourceNeeds Review