Files
cremote/docs/REAL_KEYBOARD_SIMULATION.md
Josh at WLTechBlog f8fbfddc7f bump
2025-11-20 14:47:55 -07:00

5.3 KiB

Real Keyboard Simulation for Focus Indicator Testing

Date: 2025-11-20
Status: IMPLEMENTED


Overview

We've implemented real Tab key simulation for keyboard navigation testing to solve the :focus-within detection issue identified in docs/FOCUS_INDICATORS_VALIDATION_SUCCESS.md.

The Problem

Programmatic .focus() cannot trigger :focus-within on parent elements, causing false negatives for dropdown menus and other elements that rely on CSS :focus-within pseudo-class to become visible.

The Solution

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.


Implementation Details

New Function: testKeyboardNavigationWithRealKeys()

Located in daemon/daemon.go (lines 10849-11148), this function:

  1. Scans all interactive elements using JavaScript to identify focusable elements
  2. Focuses the body to start from a known state
  3. Presses Tab key repeatedly using d.performSpecialKey(tabID, "Tab")
  4. Checks the focused element after each Tab press
  5. Detects focus indicators by examining computed styles (outline, box-shadow, border)
  6. Builds tab order based on actual keyboard navigation flow
  7. 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()

Old signature:

func (c *Client) TestKeyboardNavigation(tabID string, timeout int) (*KeyboardTestResult, error)

New signature:

func (c *Client) TestKeyboardNavigation(tabID string, useRealKeys bool, timeout int) (*KeyboardTestResult, error)

Parameters:

  • tabID - Tab ID (empty string uses current tab)
  • useRealKeys - true for real Tab simulation (recommended), false for legacy programmatic focus
  • timeout - Timeout in seconds

Client Function: GetKeyboardAudit()

Old signature:

func (c *Client) GetKeyboardAudit(tabID string, checkFocusIndicators, checkTabOrder, checkKeyboardTraps bool, timeout int) (*KeyboardAuditResult, error)

New signature:

func (c *Client) GetKeyboardAudit(tabID string, checkFocusIndicators, checkTabOrder, checkKeyboardTraps, useRealKeys bool, timeout int) (*KeyboardAuditResult, error)

Parameters:

  • useRealKeys - true for real Tab simulation (default), false for legacy method

MCP Tools Updated

web_keyboard_test_cremotemcp

New parameter:

  • use_real_keys (boolean, default: true) - Use real Tab key simulation

Example:

{
  "tool": "web_keyboard_test_cremotemcp",
  "arguments": {
    "use_real_keys": true,
    "timeout": 15
  }
}

web_keyboard_audit_cremotemcp

New parameter:

  • use_real_keys (boolean, default: true) - Use real Tab key simulation

Example:

{
  "tool": "web_keyboard_audit_cremotemcp",
  "arguments": {
    "check_focus_indicators": true,
    "use_real_keys": true,
    "timeout": 15
  }
}

Backward Compatibility

Fully backward compatible with optional parameter:

  • Default behavior: Uses real Tab key simulation (use_real_keys: true)
  • Legacy behavior: Set use_real_keys: false to use programmatic .focus()
  • Existing code without the parameter will use the new, more accurate method

Testing Recommendations

For Dropdown Menus

{
  "tool": "web_keyboard_audit_cremotemcp",
  "arguments": {
    "use_real_keys": true,
    "check_focus_indicators": true
  }
}

For Standard Pages

{
  "tool": "web_keyboard_test_cremotemcp",
  "arguments": {
    "use_real_keys": true
  }
}

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-within not triggered

After (Real Tab Simulation)

  • ~49%+ pass rate on pages with dropdown menus
  • Accurate detection of focus indicators
  • :focus-within properly 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

  1. Implementation complete
  2. Test on pages with dropdown menus
  3. Update documentation
  4. Deploy to production

  • docs/FOCUS_INDICATORS_VALIDATION_SUCCESS.md - Original issue identification
  • mcp/LLM_USAGE_GUIDE.md - MCP tool usage guide
  • docs/ADA_TESTING_GUIDE.md - Accessibility testing guide