Playwright’s accessibility testing capabilities are surprisingly powerful, acting less like a simple checker and more like an automated accessibility auditor that can uncover deep-seated issues.
Let’s see it in action. Imagine you have a web application, and you want to check if its main navigation menu is WCAG compliant. Here’s a Playwright test that uses the toHaveAttribute and toHaveRole matchers to audit a specific element:
import { test, expect } from '@playwright/test';
test('Main navigation accessibility audit', async ({ page }) => {
await page.goto('https://your-website.com/page-with-nav'); // Replace with your URL
// Audit the main navigation element
const navElement = page.locator('nav[aria-label="Main Navigation"]'); // Example selector
// Check for ARIA attributes that convey structure and purpose
await expect(navElement).toHaveAttribute('role', 'navigation');
await expect(navElement).toHaveAttribute('aria-label', 'Main Navigation');
// Check for semantic role and accessibility properties of contained elements
const navLinks = navElement.locator('a');
await expect(navLinks).toHaveCount(5); // Example: expecting 5 links
await expect(navLinks.first()).toHaveRole('link');
await expect(navLinks.first()).toBeVisible();
await expect(navLinks.first()).not.toBeDisabled();
// Further checks can be added for specific WCAG criteria
// e.g., checking if links have discernible text
await expect(navLinks).toHaveText(/^(Home|About|Services|Contact|Blog)$/); // Example text check
// You can also leverage the built-in accessibility tree inspection
const accessibilityTree = await navElement.evaluate((node) => {
// This is a simplified example of how you might access accessibility properties.
// In a real scenario, you'd use browser DevTools accessibility inspection or a library.
// Playwright's `toHaveRole` and `toHaveAttribute` abstract this for you.
return {
role: node.getAttribute('role'),
label: node.getAttribute('aria-label'),
// In a real browser context, you'd inspect computed ARIA properties.
};
});
console.log('Accessibility Tree Snippet:', accessibilityTree);
// Example of checking a button within the nav (if applicable)
const searchButton = navElement.locator('button[aria-label="Search"]');
await expect(searchButton).toHaveRole('button');
await expect(searchButton).toHaveAttribute('aria-label', 'Search');
});
This example demonstrates how Playwright can programmatically inspect ARIA attributes, semantic roles, and element states, mimicking what a manual accessibility auditor would look for.
The core problem Playwright’s accessibility testing addresses is the inherent difficulty and inconsistency in manually auditing web content for WCAG compliance. Manual audits are time-consuming, prone to human error, and often miss subtle programmatic issues. Playwright, by integrating with the browser’s accessibility tree and providing assertion matchers, automates much of this process, allowing for continuous, reliable accessibility checks within your development workflow.
Internally, Playwright leverages the browser’s native accessibility APIs. When you use matchers like toHaveRole or toHaveAttribute, Playwright queries the browser’s accessibility tree. This tree is a hierarchical representation of the UI elements on the page, augmented with accessibility information (roles, names, states, properties) that assistive technologies like screen readers use to interpret and interact with the page. Playwright then compares this information against your assertions.
The exact levers you control are the selectors you use to target elements and the specific accessibility properties you assert. You can target elements by their CSS selectors, text content, or even their accessibility tree properties if you know them. The matchers themselves are designed to mirror common WCAG requirements:
toHaveRole(role): Checks if an element has the correct ARIA role (e.g., 'navigation', 'button', 'link'). This is crucial for semantic understanding.toHaveAttribute(name, value): Verifies ARIA attributes likearia-label,aria-labelledby,aria-haspopup, etc. These attributes provide essential context and functionality information.toBeVisible(): Ensures elements are not hidden via CSSdisplay: noneorvisibility: hidden, which makes them inaccessible.not.toBeDisabled(): Confirms interactive elements are available for use.toHaveText(): Checks for discernible, descriptive text content, a fundamental WCAG requirement.
Beyond these direct assertions, you can also perform more advanced checks by using page.evaluate() to access the browser’s Accessibility Object Model (AOM) directly, though this is more complex and usually unnecessary for standard WCAG checks. The power lies in combining these programmatic checks with visual inspection and user testing.
One thing most people don’t realize is that Playwright’s accessibility matchers are often checking the computed accessibility tree, not just the raw DOM attributes. This means if a framework or JavaScript dynamically changes ARIA properties, Playwright can still detect those changes as they are reflected in the browser’s accessibility tree, making it effective even for complex, dynamic applications. It’s not just a static DOM snapshot.
The next step after auditing individual elements is to integrate comprehensive accessibility scanning tools like axe-core directly into your Playwright tests for a broader sweep of WCAG violations.