Skip to content

Latest commit

Β 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Playwright A11y Testing Skill

Senior developer expert skill for E2E accessibility testing with Playwright and W3C ARIA APG patterns

License: MIT Playwright WCAG axe-core

🎯 Overview

Expert skill for writing comprehensive E2E accessibility tests with Playwright, following W3C ARIA Authoring Practices Guide (APG) patterns and WCAG 2.2 AAA compliance. This skill provides real browser testing capabilities that jsdom/Vitest cannot match.

πŸš€ Quick Start

Installation

# Install the skill
npx skills add rcellas/playwright-a11y

# Install Playwright dependencies
npm install -D @playwright/test @axe-core/playwright axe-core

# Install Playwright browsers
npx playwright install chromium

Basic Configuration

playwright.config.ts

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:4200',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:4200',
    reuseExistingServer: !process.env.CI,
  },
});

Your First Accessibility Test

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

test('Modal is accessible', async ({ page }) => {
  // WCAG 4.1.2: Name, Role, Value
  // APG: Dialog Pattern
  
  // GIVEN: Modal is rendered
  await page.goto('/modal-page');
  await page.locator('button:has-text("Open Modal")').click();
  
  const modal = page.locator('[role="dialog"]');
  
  // THEN: Has correct ARIA attributes
  await expect(modal).toHaveAttribute('aria-modal', 'true');
  await expect(modal).toBeVisible();
  
  // WHEN: User presses Escape
  await page.keyboard.press('Escape');
  
  // THEN: Modal closes (WCAG 2.1.2: No Keyboard Trap)
  await expect(modal).not.toBeVisible();
});

πŸ“š Documentation

Core Guides

  • SKILL.md - Complete skill documentation with all patterns and best practices
  • WCAG 2.2 Reference - WCAG 2.2 success criteria with Playwright examples
  • APG Patterns - All 17 W3C APG patterns with test contracts
  • Common Mistakes - Common accessibility mistakes and how to test for them

πŸŽ“ Key Concepts

Real Browser Testing vs jsdom

Playwright provides real browser testing that jsdom/Vitest cannot:

Feature Playwright jsdom/Vitest
Focus Management βœ… Real focus behavior ❌ Simulated
Keyboard Events βœ… Real browser events ⚠️ Limited
Visual Testing βœ… Computed styles ❌ Not available
Focus Trap βœ… Tab cycling ❌ Cannot test
Cross-browser βœ… Chrome/Firefox/Safari ❌ jsdom only

Test Format: Gherkin (Given-When-Then)

All accessibility tests follow Gherkin format for clarity:

test('description', async ({ page }) => {
  // WCAG X.X.X: Criterion name
  // APG Pattern: Pattern name - Rule quote
  
  // GIVEN: Initial state setup
  await page.goto('/page');
  
  // WHEN: User performs an action
  await page.keyboard.press('Tab');
  
  // THEN: Accessibility contract is verified
  await expect(element).toBeFocused();
});

Supported APG Patterns

This skill provides test examples for 17+ W3C ARIA APG patterns:

  1. Dialog (Modal) - Focus trap, Escape to close, focus return
  2. Tabs - Arrow key navigation, Home/End keys
  3. Combobox - Autocomplete, listbox navigation
  4. Menu Button - Dropdown menus, keyboard navigation
  5. Accordion - Expand/collapse, aria-expanded
  6. Checkbox - Three-state checkboxes
  7. Radio Group - Arrow key navigation
  8. Switch - Toggle controls
  9. Disclosure - Show/hide content
  10. Slider - Arrow key adjustments
  11. Alert - Screen reader announcements
  12. Alert Dialog - Modal alerts
  13. Listbox - Selectable lists
  14. Breadcrumb - Navigation trails
  15. Tooltip - Hover and focus tooltips
  16. Tree View - Hierarchical navigation
  17. Toolbar - Tool collections

πŸ” WCAG 2.2 Coverage

This skill helps you test for all WCAG 2.2 levels:

  • Level A - Minimum conformance
  • Level AA - Standard conformance (most common)
  • Level AAA - Highest conformance

New WCAG 2.2 Criteria

  • 2.4.11 Focus Not Obscured (Minimum) - AA
  • 2.4.12 Focus Not Obscured (Enhanced) - AAA
  • 2.5.7 Dragging Movements - AA
  • 2.5.8 Target Size (Minimum) - AA (24x24 pixels)
  • 3.2.6 Consistent Help - A
  • 3.3.7 Redundant Entry - A
  • 3.3.8 Accessible Authentication (Minimum) - AA

πŸ› οΈ Features

Mandatory Autofix Behavior

The skill automatically detects and fixes common issues:

  1. βœ… Detects failures (a11y assertions, test environment, imports)
  2. βœ… Applies minimal safe edits to code/config/spec files
  3. βœ… Re-runs the same failing test scope
  4. βœ… Repeats until green or blocked by missing product decision
  5. βœ… Reports exactly what was changed and why

Framework Support

  • βœ… React - Full support with Playwright
  • βœ… Angular - Full support with Playwright
  • βœ… Vue - Full support with Playwright
  • βœ… Svelte - Full support with Playwright
  • βœ… Vanilla JS - Full support with Playwright

Cross-Browser Testing

  • βœ… Chromium - Chrome, Edge, Brave
  • βœ… Firefox - Mozilla Firefox
  • βœ… WebKit - Safari

πŸ“– Usage Examples

Testing Focus Management

test('modal traps focus within dialog', async ({ page }) => {
  // APG Dialog: "Tab and Shift+Tab do not move focus outside the dialog"
  
  await page.goto('/modal-page');
  await page.locator('button:has-text("Open Modal")').click();
  
  const dialog = page.locator('[role="dialog"]');
  const firstButton = dialog.locator('button').first();
  const lastButton = dialog.locator('button').last();
  
  // Focus last element
  await lastButton.focus();
  
  // Tab should wrap to first element
  await page.keyboard.press('Tab');
  await expect(firstButton).toBeFocused();
  
  // Shift+Tab should wrap to last element
  await page.keyboard.press('Shift+Tab');
  await expect(lastButton).toBeFocused();
});

Testing Keyboard Navigation

test('tabs support arrow key navigation', async ({ page }) => {
  // APG Tabs: "When focus is on a tab, ArrowRight moves focus to the next tab"
  
  await page.goto('/tabs-page');
  
  const tab1 = page.locator('[role="tab"]').nth(0);
  const tab2 = page.locator('[role="tab"]').nth(1);
  
  await tab1.focus();
  await expect(tab1).toHaveAttribute('aria-selected', 'true');
  
  // ArrowRight moves to next tab
  await page.keyboard.press('ArrowRight');
  await expect(tab2).toBeFocused();
  await expect(tab2).toHaveAttribute('aria-selected', 'true');
});

Testing with axe-core

test('has no WCAG 2.2 AAA violations', async ({ page }) => {
  // WCAG 2.2 AAA automated scan
  
  await page.goto('/page');
  
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2aaa', 'wcag21aaa', 'wcag22aaa'])
    .analyze();
  
  expect(results.violations).toEqual([]);
});

🀝 Contributing

This skill is based on:

πŸ“ License

MIT

πŸ”— Resources

πŸ’‘ Tips

  • Test keyboard navigation first - Most accessibility issues are keyboard-related
  • Use real browser testing - Playwright provides accurate focus and keyboard behavior
  • Follow APG patterns - Don't reinvent the wheel
  • Run axe-core scans - Catch automated violations early
  • Test in multiple browsers - Accessibility behavior can vary

πŸ†˜ Support

For issues or questions:

  1. Check the SKILL.md documentation
  2. Review Common Mistakes
  3. Consult APG Patterns
  4. Open an issue on GitHub

Remember: Accessibility is not optional. Every interactive component must be keyboard accessible and screen reader compatible.

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors