Ensuring accessibility on the web is more than a best practice - it's a necessity for creating inclusive digital experiences. The Web Content Accessibility Guidelines (WCAG) set the standard for making content accessible to everyone, including individuals with disabilities. Among the key aspects of these guidelines are Focus Order (WCAG 2.4.3) and Visible Focus Indicators (WCAG 2.4.7), which are crucial for keyboard navigation and ensuring users can visually track their position on a page.
Focus shifting between interactive elements
However, verifying these focus-related guidelines manually can be time-consuming and prone to oversight. Automated testing offers a powerful solution, enabling developers to efficiently check for compliance and catch accessibility issues early in the development process. In this post, we'll explore how to automate the verification of focus order and visible focus indicators using Playwright, enhancing our ability to maintain WCAG compliance effortlessly.
Focus Order (WCAG 2.4.3) is a fundamental accessibility guideline that ensures users can navigate a webpage logically and predictably using keyboard inputs. When navigating with a keyboard, users rely on the tab key to move through interactive elements like links, buttons, form fields, and menus. The sequence in which these elements receive focus, known as the focus order, plays a critical role in user experience.
A logical focus order mirrors the visual order of elements on the page, guiding users through content in a way that feels intuitive and predictable. For sighted keyboard users, this allows them to follow the page's flow seamlessly. For screen reader users, it ensures the information is presented in a coherent sequence, preventing confusion and frustration.
Why Focus Order Matters for Accessibility:
Common Focus Order Issues That Affect User Experience:
Manually verifying focus order can be a time-consuming and error-prone process. It requires a thorough understanding of the element sequence and careful observation to ensure that each element receives focus in the correct order. This task becomes increasingly challenging on complex web pages with numerous interactive elements. Manual checks can miss inconsistencies, especially when unexpected tabindex values or dynamic content disrupt the intended navigation flow.
Automating focus order verification provides a more reliable and efficient approach, allowing developers to detect focus order issues quickly and accurately. Playwright, a powerful browser automation tool, offers a straightforward way to automate focus testing. Below is an example of how to automate focus order verification using Playwright.
First, the test function is defined with the name "Logical focus order verification", and the page object is used to interact with the browser.
test("Logical focus order verification", async ({ page }) => {
await page.goto("your-page-link");
This code snippet navigates to the target page where the focus order verification will be performed.
To keep track of the elements that receive focus, an empty array named focusOrder is created.
const focusOrder = [];
An expectedFocusOrder array is defined, representing the correct sequence in which elements should receive focus based on the HTML structure. This array will be used to compare the actual focus order against the expected order.
const expectedFocusOrder = [
{ tagName: "A", text: "First Link" },
{ tagName: "BUTTON", text: "First Button" },
{ tagName: "INPUT", text: "" }, // Input fields will have empty text
{ tagName: "A", text: "Second Link" },
···
];
Next, a function called logFocus is exposed to the page, which pushes details of each focused element (its tag name and inner text) into the focusOrder array.
await page.exposeFunction("logFocus", (element) => {
focusOrder.push(element);
});
This function is then utilized within an event listener added to the page. The event listener captures every focusin event, extracts the tagName and innerText of the focused element, and sends this data to the logFocus function.
await page.evaluate(() => {
document.addEventListener(
"focusin",
(event) => {
const { tagName, innerText } = event.target;
window.logFocus({ tagName, text: innerText.trim() });
},
true
);
});
The test simulates keyboard navigation by pressing the Tab key multiple times. The number of Tab presses corresponds to the number of expected focusable elements.
for (let i = 0; i < expectedFocusOrder.length; i++) {
await page.keyboard.press("Tab");
}
Finally, the test asserts that the actual focus order (focusOrder) matches the expected order (expectedFocusOrder). If the orders do not match, the test will fail, indicating an issue with the focus order on the page.
assert.deepStrictEqual(
focusOrder,
expectedFocusOrder,
"Focus order does not match the expected logical order."
);
});
Visible Focus Indicators (WCAG 2.4.7) are essential visual cues that help users identify which element on the page currently has keyboard focus. These indicators are usually shown as outlines, borders, or background color changes around the focused element, providing a clear visual sign of where the user's interaction point is on the page.
Visible focus indicators are crucial for accessibility because they allow users who rely on keyboard navigation to follow the sequence of interactions easily. This includes users with motor disabilities who cannot use a mouse, individuals with vision impairments who benefit from visual guidance, and those who prefer keyboard shortcuts for faster navigation. Without visible focus indicators, navigating a webpage becomes challenging and disorienting, significantly hindering the overall user experience.
Why Visible Focus Indicators Matter for Accessibility:
Consequences of Missing or Inadequate Focus Indicators:
Verifying visible focus indicators is crucial for ensuring accessibility compliance. Automated testing with Playwright can efficiently check that focus indicators are visible and meet WCAG 2.4.7 standards. By examining CSS properties like outline, border, and box-shadow, automation helps ensure that focus states are correctly implemented and visible to all users, particularly those relying on keyboard navigation.
Manually verifying visible focus indicators across multiple elements can be tedious and prone to errors, especially when testing various browsers and devices. Automation not only speeds up this process but also catches inconsistencies that might go unnoticed in manual testing.
The test begins by defining a list of common focusable elements, such as links, buttons, inputs, text areas, and select boxes. This list is used to identify and verify each element's focus state.
test("Visible focus indicator verification", async ({ page }) => {
const focusableSelectors = ["a", "button", "input", "textarea", "select"];
A helper function checkFocusIndicator is defined to check the visibility of focus indicators for each element. This function focuses on an element and retrieves its computed styles, which include outline, border, and box-shadow properties - key elements that define focus visibility.
async function checkFocusIndicator(element) {
await element.focus();
const styles = await element.evaluate((el) => {
const computedStyles = window.getComputedStyle(el);
return {
outlineWidth: computedStyles.outlineWidth,
outlineStyle: computedStyles.outlineStyle,
outlineColor: computedStyles.outlineColor,
borderWidth: computedStyles.borderWidth,
borderStyle: computedStyles.borderStyle,
borderColor: computedStyles.borderColor,
boxShadow: computedStyles.boxShadow,
};
});
The function checks if the focus indicator is visible by evaluating the outline, border, and box-shadow properties. It confirms that the outline is not set to none, the outline width is greater than zero, and the outline color is not transparent or fully transparent (rgba(0, 0, 0, 0)). Similarly, it checks the border and box-shadow properties to ensure they also provide visible feedback when the element is focused.
const isOutlineVisible =
styles.outlineStyle !== "none" &&
parseFloat(styles.outlineWidth) > 0 &&
styles.outlineColor !== "transparent" &&
!styles.outlineColor.includes("rgba(0, 0, 0, 0)");
const isBorderVisible =
styles.borderStyle !== "none" &&
parseFloat(styles.borderWidth) > 0 &&
styles.borderColor !== "transparent" &&
!styles.borderColor.includes("rgba(0, 0, 0, 0)");
const isBoxShadowVisible = styles.boxShadow !== "none";
The test asserts that at least one of the focus indicators (outline, border, or box-shadow) is visible. If none are present, it fails, indicating that the element does not meet accessibility standards for focus visibility.
const hasVisibleFocus =
isOutlineVisible || isBorderVisible || isBoxShadowVisible;
assert(
hasVisibleFocus,
`Element ${await element.evaluate(
(el) => el.tagName
)} does not have a visible focus indicator.`
);
The test loops through each defined focusable element on the page, applying the focus indicator check function to each. This comprehensive approach ensures that all interactive elements meet the visible focus indicator requirements.
for (const selector of focusableSelectors) {
const elements = await page.$$(selector);
for (const element of elements) {
await checkFocusIndicator(element);
}
}
});
Automating focus order and visible focus indicator tests is essential for maintaining accessibility compliance, but it comes with challenges. Automation engineers often encounter specific pitfalls when verifying focus behavior and visible indicators. This section will outline common issues during automation testing and provide practical tips to ensure accurate and reliable test results.
1. Inconsistent Test Results Due to Page Load Timing
One of the frequent challenges in automated focus testing is dealing with inconsistent page load times or dynamic content. Elements might not be fully loaded or interactive when the test script begins, leading to false negatives or skipped focus states.
How to Overcome:
2. Difficulty in Capturing Dynamic Focus States
Dynamic web applications with JavaScript-driven focus behavior can be tricky to test. Elements may receive focus based on user interactions or AJAX updates, making it hard to predict focus order accurately.
How to Overcome:
3. Handling Custom Components and Third-Party Widgets
Many modern applications use custom components or third-party widgets that do not follow standard focus behavior, often requiring special handling in automated tests.
How to Overcome:
4. Identifying Invisible Focus Indicators in Automation
Automated tests might pass even when focus indicators are subtle or invisible due to CSS properties that are difficult for scripts to detect, such as zero-width outlines or transparent borders.
How to Overcome:
5. Dealing with False Positives and Negatives
Automation scripts can generate false positives (tests passing when they shouldn't) or false negatives (tests failing incorrectly) due to subtle differences in how browsers render focus indicators or handle focus order.
How to Overcome:
6. Maintaining Test Scripts Amid Frequent UI Changes
As UI components evolve, maintaining focus-related test scripts can become challenging, especially when the DOM structure or element attributes change frequently.
How to Overcome:
Ensuring that web applications meet accessibility standards, particularly regarding Focus Order (WCAG 2.4.3) and Visible Focus Indicators (WCAG 2.4.7), is crucial for providing an inclusive user experience. Automated testing using tools like Playwright allows QA engineers to efficiently verify that focus order is logical and that focus indicators are visible, reducing the risk of accessibility issues going unnoticed. By understanding common pitfalls and leveraging automated solutions, engineers can significantly improve the accessibility of applications, making them more navigable and usable for all users. Incorporating these testing strategies into our automation suite not only ensures compliance but also helps create a more user-friendly digital environment.
The complete code examples and a local testing website are available on our GitHub page. Happy coding!