5.9 KiB
Real Keyboard Simulation for Focus Indicator Testing
Date: 2025-11-20 Updated: 2025-12-09 Status: ✅ IMPLEMENTED (Now the only method - legacy programmatic method removed)
Overview
Cremote always uses real Tab key simulation for keyboard navigation testing. The legacy programmatic .focus() method has been removed because it produces false negatives.
The Problem
Programmatic .focus() cannot trigger :focus-within or :focus-visible on elements, causing false negatives for:
- Dropdown menus that rely on CSS
:focus-withinpseudo-class - Modern focus indicators using
:focus-visiblepseudo-class - Accessibility plugins that inject universal focus styles
The Solution
Always use real Tab key presses via Chrome DevTools Protocol to simulate actual user keyboard navigation, which properly triggers all CSS pseudo-classes including :focus-within and :focus-visible.
Implementation Details
New Function: testKeyboardNavigationWithRealKeys()
Located in daemon/daemon.go (lines 10849-11148), this function:
- Scans all interactive elements using JavaScript to identify focusable elements
- Focuses the body to start from a known state
- Presses Tab key repeatedly using
d.performSpecialKey(tabID, "Tab") - Checks the focused element after each Tab press
- Detects focus indicators by examining computed styles (outline, box-shadow, border)
- Builds tab order based on actual keyboard navigation flow
- Stops when cycling back to body/document or after finding all expected elements
Key Advantages
✅ Triggers :focus-within - Parent elements receive the pseudo-class
✅ Opens dropdowns automatically - CSS rules like .menu-item-has-children:focus-within > .sub-menu work
✅ Tests real user experience - Simulates actual keyboard navigation
✅ Accurate focus indicators - Detects indicators on elements inside hidden dropdowns
✅ Proper tab order - Follows browser's natural tab navigation flow
API Changes
Client Function: TestKeyboardNavigation()
Current signature:
func (c *Client) TestKeyboardNavigation(tabID string, useRealKeys bool, timeout int) (*KeyboardTestResult, error)
Parameters:
tabID- Tab ID (empty string uses current tab)useRealKeys- Ignored (always uses real Tab simulation for accuracy)timeout- Timeout in seconds
Note: The useRealKeys parameter is maintained for backward compatibility but is ignored. All keyboard testing now uses real Tab key simulation.
Client Function: GetKeyboardAudit()
Current signature:
func (c *Client) GetKeyboardAudit(tabID string, checkFocusIndicators, checkTabOrder, checkKeyboardTraps, useRealKeys bool, timeout int) (*KeyboardAuditResult, error)
Parameters:
useRealKeys- Ignored (always uses real Tab simulation for accuracy)
Note: The useRealKeys parameter is maintained for backward compatibility but is ignored.
MCP Tools Updated
web_keyboard_test_cremotemcp
Parameters:
tab(string, optional) - Tab IDtimeout(integer, default: 15) - Timeout in seconds
Note: The use_real_keys parameter has been removed. Real Tab key simulation is always used.
Example:
{
"tool": "web_keyboard_test_cremotemcp",
"arguments": {
"timeout": 15
}
}
web_keyboard_audit_cremotemcp
Parameters:
tab(string, optional) - Tab IDcheck_focus_indicators(boolean, default: true)check_tab_order(boolean, default: true)check_keyboard_traps(boolean, default: true)timeout(integer, default: 15) - Timeout in seconds
Note: The use_real_keys parameter has been removed. Real Tab key simulation is always used.
Example:
{
"tool": "web_keyboard_audit_cremotemcp",
"arguments": {
"check_focus_indicators": true,
"timeout": 15
}
}
Backward Compatibility
⚠️ Breaking Change (Simplified):
- The
use_real_keysparameter has been removed from MCP tools - Client functions still accept the parameter for backward compatibility but ignore it
- All keyboard testing now uses real Tab key simulation for accurate results
- Legacy programmatic
.focus()method has been removed
Rationale: The programmatic method produced false negatives for :focus-visible and :focus-within, making it unreliable for accessibility testing.
Testing Recommendations
For Dropdown Menus
{
"tool": "web_keyboard_audit_cremotemcp",
"arguments": {
"check_focus_indicators": true
}
}
For Standard Pages
{
"tool": "web_keyboard_test_cremotemcp",
"arguments": {
"timeout": 15
}
}
Legacy Testing (if needed)
{
"tool": "web_keyboard_test_cremotemcp",
"arguments": {
"use_real_keys": false
}
}
Expected Results
Before (Programmatic Focus)
- ❌ 29.2% pass rate on pages with dropdown menus
- ❌ False negatives for elements in hidden dropdowns
- ❌
:focus-withinnot triggered
After (Real Tab Simulation)
- ✅ ~49%+ pass rate on pages with dropdown menus
- ✅ Accurate detection of focus indicators
- ✅
:focus-withinproperly triggered - ✅ Dropdowns open automatically during testing
Performance Considerations
- Slightly slower than programmatic focus (adds ~50ms per element for Tab press + style check)
- More accurate results justify the small performance trade-off
- Timeout increased to 15 seconds by default to accommodate the additional time
- Safety limits in place (max 200 Tab presses to prevent infinite loops)
Next Steps
- ✅ Implementation complete
- ⏳ Test on pages with dropdown menus
- ⏳ Update documentation
- ⏳ Deploy to production
Related Documents
docs/FOCUS_INDICATORS_VALIDATION_SUCCESS.md- Original issue identificationmcp/LLM_USAGE_GUIDE.md- MCP tool usage guidedocs/ADA_TESTING_GUIDE.md- Accessibility testing guide