July 9, 2026
Playwright vs Selenium for Testing Keyboard Navigation, Focus Traps, and ARIA-Driven Modal Workflows
A practical comparison of Playwright vs Selenium keyboard navigation testing for focus traps, ARIA modal workflows, and accessibility-focused UI automation.
Keyboard navigation is where UI automation stops being a locator problem and starts being a user-intent problem. Clicking a button or filling a form is easy to model. Proving that a modal traps focus correctly, that Escape closes it without breaking the page, or that Tab order still works after a refactor is harder, because the test has to behave like a keyboard user, not just a script.
That is why the conversation around Playwright vs Selenium changes when the test target is a modal-heavy application with ARIA dialogs, command palettes, drawers, and nested overlays. Both tools can press keys. Both can find elements. Neither one magically understands accessibility intent unless you encode that intent carefully in your test design.
For teams that want less framework code, a platform like Endtest can be relevant because it supports accessibility checks inside web tests and uses an agentic AI workflow for test creation and maintenance. That is not a replacement for understanding the mechanics, but it can reduce how much custom code you need for recurring keyboard-centric regression coverage.
What keyboard navigation tests actually need to prove
When people say they want to test keyboard accessibility, they usually mean a mix of behaviors:
- The first interactive element gets focus in the right place.
- Tab and Shift+Tab move through controls in a predictable order.
- Focus never escapes a modal dialog while it is open.
- Escape closes the modal and returns focus to the triggering control.
- Arrow keys, Home, End, Enter, and Space behave correctly for menus, tabs, and composite widgets.
- Screen-reader-relevant attributes such as
role,aria-modal,aria-labelledby,aria-hidden, andaria-expandedare consistent with what the user can do.
A selector can tell you whether a button exists. It cannot tell you whether focus is trapped inside a dialog after a state transition, or whether the active element is still valid after the DOM re-renders.
For modal workflows, the most useful assertions are often about focus state, not just visibility state.
That distinction matters because a visible close button is not enough if the tab sequence lets users fall behind the overlay, or if the app removes the opener from the DOM and strands focus nowhere useful.
Why modals and focus traps are difficult to automate
Modal logic tends to combine several moving parts:
- An overlay with z-index and scroll lock behavior
- A focus trap that intercepts Tab and Shift+Tab
- A close action bound to Escape and often to the backdrop
- Focus restoration to the trigger element
- Dynamic rendering, sometimes with portals
- Validation messages, live regions, or nested forms inside the dialog
The test becomes brittle if it assumes too much about implementation details. A test that clicks a close icon and checks that the dialog disappeared may miss a broken trap. A test that asserts the active element after every keypress may fail for the wrong reason if the component library uses async rendering or if animations delay the focus change by a tick.
Good keyboard automation needs to tolerate implementation variance while still checking the user-visible contract.
Playwright vs Selenium keyboard navigation testing, at a glance
The core difference is not that one can press keys and the other cannot. It is how much infrastructure and helper code you need to get reliable, readable results.
Playwright strengths for keyboard flows
Playwright tends to be more ergonomic for keyboard-centric tests because it ships with a modern selector model, auto-waiting, and stronger primitives for asserting state after interactions. In practice, that helps when a dialog appears, traps focus, and re-renders content after every keypress.
Useful properties for this area include:
locator.press()for element-scoped key actionspage.keyboard.press()for global key sequencesexpect(locator).toBeFocused()for focus assertions- Built-in waiting around element actionability
- Easy context isolation across tests
Selenium strengths for keyboard flows
Selenium is still perfectly capable, especially in organizations that already have a large WebDriver estate or need cross-language support. If your team has strong harness discipline, Selenium can drive keyboard interactions cleanly.
Its advantages are less about ergonomics and more about portability and ecosystem maturity:
- Broad language support
- Familiar WebDriver semantics
- Good fit for existing frameworks and grids
- Works well when your org already standardized on it
The tradeoff is that keyboard tests usually require more explicit waiting, more helper methods for focus checks, and more guardrails around stale elements and asynchronous UI updates.
A practical example, opening a modal and verifying focus trap behavior
Suppose you have a settings modal that opens from a button, contains two inputs and a save button, and should keep focus inside until it closes.
Playwright example
import { test, expect } from '@playwright/test';
test('modal traps focus and restores it on close', async ({ page }) => {
await page.goto('/settings');
const openButton = page.getByRole(‘button’, { name: ‘Open settings’ }); await openButton.click();
const dialog = page.getByRole(‘dialog’, { name: ‘Settings’ }); await expect(dialog).toBeVisible(); await expect(dialog.getByLabel(‘Display name’)).toBeFocused();
await page.keyboard.press(‘Tab’); await expect(dialog.getByLabel(‘Email notifications’)).toBeFocused();
await page.keyboard.press(‘Escape’); await expect(dialog).toBeHidden(); await expect(openButton).toBeFocused(); });
This reads close to the user story. The important part is not just that the dialog opens, but that the first focus target is correct and focus returns to the trigger.
Selenium example in Python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome() wait = WebDriverWait(driver, 10)
driver.get(‘https://example.com/settings’) open_button = driver.find_element(By.CSS_SELECTOR, ‘button[aria-label=”Open settings”]’) open_button.click()
dialog = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ‘[role=”dialog”][aria-label=”Settings”]’))) first_field = dialog.find_element(By.CSS_SELECTOR, ‘input[name=”displayName”]’) assert first_field == driver.switch_to.active_element
first_field.send_keys(Keys.TAB) second_field = dialog.find_element(By.CSS_SELECTOR, ‘input[name=”emailNotifications”]’) assert second_field == driver.switch_to.active_element
second_field.send_keys(Keys.ESCAPE) wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ‘[role=”dialog”][aria-label=”Settings”]’))) assert open_button == driver.switch_to.active_element
The Selenium version is fine, but it is more manual. The test author has to think about element lookup, explicit waits, and focus comparison mechanics.
Where Playwright usually wins
1. Focus assertions are easier to write and read
Playwright has a strong notion of locators and focus assertions. That makes it easier to express keyboard intent without falling back to browser internals.
If your modal is built from portals or nested components, a locator-based test still tends to stay readable. That matters when a team needs to maintain dozens of focus-related tests across a design system.
2. Auto-waiting helps with async UI transitions
Focus traps often involve async rendering, animation frames, and state updates. Playwright’s actionability checks and auto-waiting reduce the amount of explicit retry logic you need.
This is especially helpful when testing:
- Dialog open and close animations
- Deferred portal mounting
- Lazy-loaded modal content
- Validation states that appear after keyboard input
3. Keyboard APIs feel natural
Playwright gives you both element-level and page-level keyboard APIs, so it is easy to model real navigation. A good example is combining Tab, Escape, and arrow keys in one test without building a large utility layer.
4. Better fit for intent-rich assertions
A strong modal test is not just “was there a button.” It is “did focus move to the first field, and did escape restore the opener.” Playwright’s matcher ecosystem makes that intent clearer.
Where Selenium still holds up
1. Existing test estates and language diversity
If the app already has hundreds or thousands of WebDriver tests, Selenium may be the least risky way to extend coverage. Teams with mixed Java, Python, and C# skill sets often prefer Selenium because the mental model is already in place.
2. Grid and infrastructure standardization
Some organizations already run browser tests through a centralized grid or enterprise setup. In that world, Selenium can be a better operational fit, even if it takes more code to express the keyboard flows.
3. Lower migration cost when the team is already fluent
If your developers and SDETs know WebDriver well, the cost of switching test stacks can outweigh the benefits, especially if your keyboard accessibility checks are only one part of a broader regression suite.
The downside is that focus trap tests usually become helper-heavy. Once you add wrappers for waits, retries, active element checks, and modal lifecycle handling, the test suite can become harder to diagnose when it fails.
The specific assertions that catch real accessibility regressions
For keyboard navigation, these are the assertions that tend to produce the highest signal.
1. Active element after open
When a dialog opens, verify that focus lands on the expected element, usually the first focusable control, the dialog container, or a designated initial focus target.
2. No focus escape while open
Walk through the trap with Tab and Shift+Tab. At the edges, the focus should wrap, not fall through to the page behind it.
3. Escape closes the right layer
In nested modal systems, Escape should close the topmost layer first. Tests should verify that closing one dialog does not accidentally submit a parent form or leave a nested overlay active.
4. Trigger focus restoration
After closing, focus should return to the opener or another logical target. This matters for keyboard and screen reader users alike.
5. Role and ARIA coherence
If you are already automating the workflow, inspect the semantics too. A dialog should behave like a dialog, a menu like a menu, and a tablist like a tablist.
The WCAG guidance is the right north star here, but automated tests still need to reflect your component library’s contract.
A small focus-trap helper can pay for itself
Whether you use Playwright or Selenium, one useful pattern is a helper that records the active element sequence during a keyboard walk. That makes it easier to debug a trap that fails only on one browser or only after a UI refactor.
typescript
async function focusedName(page: any) {
return page.evaluate(() => {
const el = document.activeElement as HTMLElement | null;
return el?.getAttribute('aria-label') || el?.textContent?.trim() || el?.tagName;
});
}
This kind of helper is not about replacing proper assertions. It is about making failures explainable. If a focus trap breaks, you want to know whether focus moved to the backdrop, the body, a hidden element, or nowhere useful.
Common failure modes and how each tool surfaces them
Hidden but focusable elements
A dialog might visually close while still leaving focusable nodes in the DOM. Automated keyboard tests can catch that if they assert the active element after close and then try a Tab sequence from the trigger.
Playwright usually surfaces this with a clear failure around focus or actionability. Selenium may surface it as a stale element, an unexpected active element, or a test that passes one run and fails another if timing differs.
Portals and detached DOM nodes
Many modal libraries render into portals. That is not a problem by itself, but tests that rely on brittle CSS chains can break when the modal moves outside the original subtree.
Here, role-based locators in Playwright tend to be easier to maintain. Selenium can do the job, but the test author often needs more explicit selector discipline.
Browser-specific keyboard behavior
Different browsers can behave differently around focus order, native controls, and shadow DOM. If your app uses custom widgets, always validate on the browsers your users actually use. A test that passes in Chromium is not enough if a large customer base uses Safari.
This is also where teams sometimes realize they need more than a code library. Platforms like Endtest, which can run accessibility checks inside web tests and support managed execution, are attractive when the team wants less low-level harness maintenance. If you are evaluating that direction, it helps to understand both the accessibility testing capability and how it fits into broader regression coverage.
Endtest as a lower-code alternative for keyboard-centric regression coverage
For teams that want to validate modal workflows, focus behavior, and ARIA-related issues without owning a large test framework, Endtest can be a reasonable alternative to consider. Its accessibility checks can be added directly to web tests, and the platform uses an agentic AI workflow to create and maintain tests as editable platform-native steps rather than source code.
That is not the right answer for every team. If your engineers want full code control, Playwright or Selenium may still be the better foundation. But if your main pain is maintaining a growing regression suite around dynamic UIs, it is worth looking at the broader tradeoffs in dynamic UI and maintainable regression coverage.
Choosing between Playwright and Selenium for this use case
A useful decision rule is this:
- Pick Playwright if your team is writing new tests for modern web apps, especially if modal, overlay, and focus-trap behavior is central to the product.
- Pick Selenium if your org already has a mature WebDriver practice, broad language requirements, or a large existing suite that you need to extend incrementally.
- Consider a lower-code platform like Endtest if the bigger problem is maintenance overhead, not test design, and you want keyboard-centric flows plus accessibility checks without owning much framework code.
Playwright is usually better when:
- Your UI is component-driven and heavily asynchronous.
- Your tests need to express user intent clearly.
- You want fewer explicit waits and less infrastructure glue.
- You are building fresh automation around accessibility-sensitive dialogs and drawers.
Selenium is usually better when:
- You already have a large WebDriver investment.
- Your organization standardizes on multiple languages and shared infrastructure.
- You want maximum continuity with legacy automation.
- You are prepared to build a stronger helper layer around focus and keyboard state.
How to structure keyboard accessibility tests so they stay useful
The biggest mistake is writing tests that only validate one happy path. Modal workflows fail in edge cases, so your suite should include:
- Open with keyboard, not just mouse click
- Trap focus after first open and after repeated open-close cycles
- Submit with keyboard, then verify focus restoration
- Close with Escape from different focus points inside the dialog
- Re-open after validation error and confirm focus lands correctly
- Run the same checks on the browsers your users actually rely on
If you are using CI, keep the suite small but representative. A focused set of keyboard-flow tests will usually give better value than a broad but shallow collection of clicks.
name: ui-regression
on: [push, pull_request]
jobs: keyboard-accessibility: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ‘20’ - run: npm ci - run: npx playwright test tests/modal-keyboard.spec.ts
Bottom line
For playwright vs selenium keyboard navigation testing, the real question is not which tool can press keys. It is which tool helps your team model focus as a first-class part of the test.
Playwright is generally the stronger choice for modern modal-heavy interfaces because its locators, waiting model, and focus assertions make keyboard intent easier to encode and maintain. Selenium remains a solid option when you are extending an established WebDriver ecosystem or need broad language and infrastructure continuity.
If your team wants to reduce the amount of framework code needed for keyboard-centric workflows, especially alongside accessibility checks, Endtest is a relevant alternative to evaluate. The right choice depends less on brand preference and more on how much of the burden you want in code, in infrastructure, and in maintenance.
The most robust approach is to test the real contract: where focus goes, how it wraps, how it closes, and whether ARIA semantics still match behavior after the UI changes. That is the part users feel, and the part your automation needs to protect.