# 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-within` pseudo-class
- Modern focus indicators using `:focus-visible` pseudo-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:

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()`

**Current signature:**
```go
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:**
```go
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 ID
- `timeout` (integer, default: 15) - Timeout in seconds

**Note:** The `use_real_keys` parameter has been removed. Real Tab key simulation is always used.

**Example:**
```json
{
  "tool": "web_keyboard_test_cremotemcp",
  "arguments": {
    "timeout": 15
  }
}
```

### `web_keyboard_audit_cremotemcp`

**Parameters:**
- `tab` (string, optional) - Tab ID
- `check_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:**
```json
{
  "tool": "web_keyboard_audit_cremotemcp",
  "arguments": {
    "check_focus_indicators": true,
    "timeout": 15
  }
}
```

---

## Backward Compatibility

⚠️ **Breaking Change (Simplified):**
- The `use_real_keys` parameter 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
```json
{
  "tool": "web_keyboard_audit_cremotemcp",
  "arguments": {
    "check_focus_indicators": true
  }
}
```

### For Standard Pages
```json
{
  "tool": "web_keyboard_test_cremotemcp",
  "arguments": {
    "timeout": 15
  }
}
```

### Legacy Testing (if needed)
```json
{
  "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

---

## Related Documents

- `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

