</>

Technology

Playwright

Difficulty

Advanced

Interview Question

What is Playwright Accessibility Testing and how do you use it?

Answer

Accessibility Testing in Playwright

Built-in Accessibility Snapshot

Playwright can scan the page's accessibility tree:

TypeScript
import { test, expect } from '@playwright/test';

test('check accessibility tree', async ({ page }) => {
  await page.goto('/login');

  // Get the full accessibility snapshot
  const snapshot = await page.accessibility.snapshot();
  console.log(JSON.stringify(snapshot, null, 2));
  // Shows: role, name, children for every accessible element
});

Integration with axe-core (WCAG Compliance)

Bash
npm install --save-dev @axe-core/playwright
TypeScript
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('homepage has no accessibility violations', async ({ page }) => {
  await page.goto('/');

  const results = await new AxeBuilder({ page }).analyze();

  expect(results.violations).toHaveLength(0);
});

test('login page is accessible (WCAG 2.1 AA)', async ({ page }) => {
  await page.goto('/login');

  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
    .analyze();

  if (results.violations.length > 0) {
    console.log('Violations found:');
    results.violations.forEach(v => {
      console.log(`  [${v.impact}] ${v.id}: ${v.description}`);
      v.nodes.forEach(n => console.log(`    - ${n.html}`));
    });
  }

  expect(results.violations).toHaveLength(0);
});

Exclude Elements from Scan

TypeScript
const results = await new AxeBuilder({ page })
  .withTags(['wcag2a'])
  .exclude('.third-party-widget')  // Exclude third-party widgets
  .exclude('#cookie-banner')
  .analyze();

Run on Specific Element

TypeScript
const results = await new AxeBuilder({ page })
  .include('#main-form')   // Only scan the form
  .withTags(['wcag2aa'])
  .analyze();

Role-Based Locators as Accessibility Tests

Using getByRole() inherently validates accessible HTML structure:

TypeScript
test('navigation is accessible', async ({ page }) => {
  await page.goto('/');

  // If these work, the HTML has correct ARIA roles
  await expect(page.getByRole('navigation')).toBeVisible();
  await expect(page.getByRole('main')).toBeVisible();
  await expect(page.getByRole('banner')).toBeVisible();

  // Verify interactive elements are correctly labeled
  await expect(page.getByRole('button', { name: 'Close' })).toBeVisible();
  await expect(page.getByRole('link', { name: 'Skip to main content' })).toBeVisible();
});

Keyboard Navigation Test

TypeScript
test('form is keyboard navigable', async ({ page }) => {
  await page.goto('/contact');

  // Tab through form fields
  await page.keyboard.press('Tab');
  await expect(page.getByLabel('Name')).toBeFocused();

  await page.keyboard.press('Tab');
  await expect(page.getByLabel('Email')).toBeFocused();

  await page.keyboard.press('Tab');
  await expect(page.getByLabel('Message')).toBeFocused();

  // Submit via keyboard
  await page.keyboard.press('Tab');
  await page.keyboard.press('Enter');
  await expect(page.getByText('Message sent')).toBeVisible();
});

Color Contrast Test

TypeScript
// axe-core checks color contrast automatically with wcag2aa tag
const results = await new AxeBuilder({ page })
  .withTags(['wcag2aa'])
  .analyze();

const contrastViolations = results.violations.filter(v => v.id === 'color-contrast');
expect(contrastViolations).toHaveLength(0);

Follow AutomateQA

Related Topics