Skip to main content

Overview

Accessibility testing ensures your web application is usable by people with disabilities. Playwright provides built-in accessibility features and integrations with popular accessibility testing libraries.

Accessibility Snapshot

Playwright provides an accessibility tree snapshot API to inspect the semantic structure of your page.
import { test, expect } from '@playwright/test';

test('get accessibility snapshot', async ({ page }) => {
  await page.goto('https://example.com');
  
  const snapshot = await page.accessibility.snapshot();
  console.log(JSON.stringify(snapshot, null, 2));
});

ARIA Attributes

Test ARIA attributes and roles to ensure proper semantic markup.
import { test, expect } from '@playwright/test';

test('verify ARIA roles', async ({ page }) => {
  await page.goto('https://example.com');
  
  // Check for specific roles
  const navigation = page.getByRole('navigation');
  await expect(navigation).toBeVisible();
  
  const main = page.getByRole('main');
  await expect(main).toBeVisible();
  
  const button = page.getByRole('button', { name: 'Submit' });
  await expect(button).toBeEnabled();
});

Keyboard Navigation

Test keyboard accessibility to ensure all functionality is accessible without a mouse.
import { test, expect } from '@playwright/test';

test('keyboard tab navigation', async ({ page }) => {
  await page.goto('https://example.com');
  
  // Tab through focusable elements
  await page.keyboard.press('Tab');
  await expect(page.locator(':focus')).toHaveAttribute('name', 'username');
  
  await page.keyboard.press('Tab');
  await expect(page.locator(':focus')).toHaveAttribute('name', 'password');
  
  await page.keyboard.press('Tab');
  await expect(page.locator(':focus')).toHaveRole('button');
});

Focus Management

Test that focus is properly managed in interactive components.
import { test, expect } from '@playwright/test';

test('modal focus trap', async ({ page }) => {
  await page.goto('https://example.com');
  
  await page.getByRole('button', { name: 'Open modal' }).click();
  
  const modal = page.getByRole('dialog');
  await expect(modal).toBeVisible();
  
  // Focus should be trapped in modal
  const focusableElements = modal.getByRole('button');
  const count = await focusableElements.count();
  
  // Tab through all elements
  for (let i = 0; i < count + 1; i++) {
    await page.keyboard.press('Tab');
  }
  
  // Focus should cycle back to first element in modal
  const focused = page.locator(':focus');
  await expect(focused).toBeFocused();
  
  // Verify focus is still in modal
  await expect(modal.locator(':focus')).toBeVisible();
});

Color Contrast

While Playwright doesn’t have built-in color contrast testing, you can compute contrast ratios.
Check Contrast
import { test, expect } from '@playwright/test';

function getContrastRatio(rgb1: number[], rgb2: number[]): number {
  const getLuminance = (rgb: number[]) => {
    const [r, g, b] = rgb.map(val => {
      val = val / 255;
      return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
    });
    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
  };
  
  const lum1 = getLuminance(rgb1);
  const lum2 = getLuminance(rgb2);
  const brightest = Math.max(lum1, lum2);
  const darkest = Math.min(lum1, lum2);
  
  return (brightest + 0.05) / (darkest + 0.05);
}

test('check color contrast', async ({ page }) => {
  await page.goto('https://example.com');
  
  const button = page.getByRole('button', { name: 'Submit' });
  
  const colors = await button.evaluate(el => {
    const style = window.getComputedStyle(el);
    const parseRGB = (color: string) => {
      const match = color.match(/\d+/g);
      return match ? match.map(Number) : [0, 0, 0];
    };
    
    return {
      color: parseRGB(style.color),
      backgroundColor: parseRGB(style.backgroundColor),
    };
  });
  
  const contrast = getContrastRatio(colors.color, colors.backgroundColor);
  
  // WCAG AA requires 4.5:1 for normal text
  expect(contrast).toBeGreaterThanOrEqual(4.5);
});

Screen Reader Testing

Test how your content is announced to screen readers.
Screen Reader Text
import { test, expect } from '@playwright/test';

test('verify screen reader text', async ({ page }) => {
  await page.goto('https://example.com');
  
  // Check for visually hidden but screen-reader accessible text
  const srOnly = page.locator('.sr-only');
  await expect(srOnly).toHaveText('For screen readers only');
  
  // Verify aria-describedby
  const input = page.getByRole('textbox', { name: 'Email' });
  const describedBy = await input.getAttribute('aria-describedby');
  const description = page.locator(`#${describedBy}`);
  await expect(description).toHaveText('Enter your email address');
  
  // Check live regions
  const liveRegion = page.locator('[aria-live="polite"]');
  await expect(liveRegion).toBeEmpty();
  
  await page.getByRole('button', { name: 'Load more' }).click();
  await expect(liveRegion).toHaveText('10 more items loaded');
});

Form Accessibility

Ensure forms are accessible with proper labels and error messages.
import { test, expect } from '@playwright/test';

test('verify form labels', async ({ page }) => {
  await page.goto('https://example.com/form');
  
  // Check explicit labels
  const emailInput = page.getByRole('textbox', { name: 'Email address' });
  await expect(emailInput).toBeVisible();
  
  // Check implicit labels
  const nameInput = page.getByLabel('Full name');
  await expect(nameInput).toBeVisible();
  
  // Verify label association
  const passwordInput = page.locator('#password');
  const labelFor = await passwordInput.getAttribute('id');
  const label = page.locator(`label[for="${labelFor}"]`);
  await expect(label).toHaveText('Password');
});

Integration with Axe

Integrate with axe-core for comprehensive accessibility testing.
// npm install --save-dev @axe-core/playwright

Best Practices

Semantic HTML

Use semantic HTML elements and ARIA roles to provide meaningful structure.

Keyboard First

Test all functionality with keyboard navigation before testing with assistive technologies.

Multiple Methods

Combine automated testing (axe) with manual testing using actual screen readers.

Progressive Enhancement

Ensure core functionality works without JavaScript before adding enhancements.

Locators

Use role-based locators for accessible selectors

Assertions

Assert accessibility attributes

Build docs developers (and LLMs) love