Playwright
// Open modal
await page.click('[data-testid="open-modal"]');
// Try to escape with Tab (should stay in modal)
const startElement = await page.evaluate(
() => document.activeElement?.id
);
// Tab through all elements
for (let i = 0; i < 10; i++) {
await page.keyboard.press('Tab');
}
const currentElement = await page.evaluate(
() => document.activeElement?.closest('[role="dialog"]')
);
// Focus should still be in modal
expect(currentElement).not.toBeNull();
// Escape should close modal
await page.keyboard.press('Escape');
await expect(page.getByRole('dialog')).not.toBeVisible();Focus Trap Detection
// Detect potential keyboard traps
async function detectTraps(page) {
const focusableSelector = `
a[href], button, input, select, textarea,
[tabindex]:not([tabindex="-1"])
`;
const elements = await page.$$(focusableSelector);
const focusPath = [];
for (const el of elements.slice(0, 20)) {
await el.focus();
focusPath.push(await page.evaluate(
() => document.activeElement?.outerHTML
));
}
// Check for cycles or stuck focus
const uniquePaths = new Set(focusPath);
if (uniquePaths.size < focusPath.length / 2) {
console.log('Potential keyboard trap detected!');
console.log('Focus cycling through:',
[...uniquePaths].slice(0, 3)
);
}
}