Update accessbility tests to fix false positives
This commit is contained in:
196
daemon/daemon.go
196
daemon/daemon.go
@@ -10826,15 +10826,32 @@ func (d *Daemon) analyzeEnhancedAccessibility(tabID string, timeout int) (*Enhan
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AccessibilityFeatures represents detected accessibility features
|
||||
type AccessibilityFeatures struct {
|
||||
PluginsDetected []string `json:"plugins_detected"`
|
||||
HasFocusVisible bool `json:"has_focus_visible"`
|
||||
HasUniversalFocus bool `json:"has_universal_focus"`
|
||||
CSSVariables map[string]string `json:"css_variables"`
|
||||
}
|
||||
|
||||
// ConfidenceLevel represents the confidence in test results
|
||||
type ConfidenceLevel struct {
|
||||
Level string `json:"level"` // LOW, MEDIUM, HIGH
|
||||
Reason string `json:"reason"`
|
||||
Recommendation string `json:"recommendation"`
|
||||
}
|
||||
|
||||
// KeyboardTestResult represents the result of keyboard navigation testing
|
||||
type KeyboardTestResult struct {
|
||||
TotalInteractive int `json:"total_interactive"`
|
||||
Focusable int `json:"focusable"`
|
||||
NotFocusable int `json:"not_focusable"`
|
||||
NoFocusIndicator int `json:"no_focus_indicator"`
|
||||
KeyboardTraps int `json:"keyboard_traps"`
|
||||
TabOrder []KeyboardTestElement `json:"tab_order"`
|
||||
Issues []KeyboardTestIssue `json:"issues"`
|
||||
TotalInteractive int `json:"total_interactive"`
|
||||
Focusable int `json:"focusable"`
|
||||
NotFocusable int `json:"not_focusable"`
|
||||
NoFocusIndicator int `json:"no_focus_indicator"`
|
||||
KeyboardTraps int `json:"keyboard_traps"`
|
||||
TabOrder []KeyboardTestElement `json:"tab_order"`
|
||||
Issues []KeyboardTestIssue `json:"issues"`
|
||||
AccessibilityFeatures *AccessibilityFeatures `json:"accessibility_features,omitempty"`
|
||||
Confidence *ConfidenceLevel `json:"confidence,omitempty"`
|
||||
}
|
||||
|
||||
// KeyboardTestElement represents an interactive element in tab order
|
||||
@@ -10857,6 +10874,111 @@ type KeyboardTestIssue struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// detectAccessibilityFeatures detects accessibility plugins and :focus-visible implementations
|
||||
func (d *Daemon) detectAccessibilityFeatures(page *rod.Page) (*AccessibilityFeatures, error) {
|
||||
jsCode := `() => {
|
||||
// Check for common accessibility plugins
|
||||
const plugins = {
|
||||
accessifix: document.querySelectorAll('[class*="accessifix"], [id*="accessifix"]').length > 0,
|
||||
userway: document.querySelectorAll('[class*="userway"], [id*="userway"]').length > 0,
|
||||
accessibe: document.querySelectorAll('[class*="accessibe"], [id*="accessibe"]').length > 0,
|
||||
audioeye: document.querySelectorAll('[class*="audioeye"], [id*="audioeye"]').length > 0,
|
||||
equalweb: document.querySelectorAll('[class*="equalweb"], [id*="equalweb"]').length > 0
|
||||
};
|
||||
|
||||
const pluginsDetected = Object.keys(plugins).filter(k => plugins[k]);
|
||||
|
||||
// Check for :focus-visible rules
|
||||
let hasFocusVisible = false;
|
||||
let hasUniversalFocus = false;
|
||||
|
||||
try {
|
||||
Array.from(document.styleSheets).forEach(sheet => {
|
||||
try {
|
||||
Array.from(sheet.cssRules || []).forEach(rule => {
|
||||
if (rule.selectorText && rule.selectorText.includes(':focus-visible')) {
|
||||
hasFocusVisible = true;
|
||||
// Check for universal :focus-visible (*, html body, etc.)
|
||||
if (rule.selectorText.match(/^(\*|html\s+body)\s*:focus-visible/)) {
|
||||
hasUniversalFocus = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
// CORS or other stylesheet access error - ignore
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
// Error accessing stylesheets - ignore
|
||||
}
|
||||
|
||||
// Check for CSS custom properties (Accessifix-specific)
|
||||
const root = getComputedStyle(document.documentElement);
|
||||
const cssVariables = {};
|
||||
|
||||
const focusWidth = root.getPropertyValue('--accessifix-focus-width');
|
||||
const focusColor = root.getPropertyValue('--accessifix-focus-color');
|
||||
const focusStyle = root.getPropertyValue('--accessifix-focus-style');
|
||||
|
||||
if (focusWidth) cssVariables.width = focusWidth.trim();
|
||||
if (focusColor) cssVariables.color = focusColor.trim();
|
||||
if (focusStyle) cssVariables.style = focusStyle.trim();
|
||||
|
||||
return JSON.stringify({
|
||||
plugins_detected: pluginsDetected,
|
||||
has_focus_visible: hasFocusVisible,
|
||||
has_universal_focus: hasUniversalFocus,
|
||||
css_variables: cssVariables
|
||||
});
|
||||
}`
|
||||
|
||||
result, err := page.Eval(jsCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to detect accessibility features: %w", err)
|
||||
}
|
||||
|
||||
var features AccessibilityFeatures
|
||||
err = json.Unmarshal([]byte(result.Value.String()), &features)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse accessibility features: %w", err)
|
||||
}
|
||||
|
||||
return &features, nil
|
||||
}
|
||||
|
||||
// calculateConfidence calculates confidence level based on detected accessibility features
|
||||
func (d *Daemon) calculateConfidence(features *AccessibilityFeatures, noFocusIndicator int) *ConfidenceLevel {
|
||||
// Low confidence: Plugin or universal :focus-visible detected
|
||||
if len(features.PluginsDetected) > 0 || features.HasUniversalFocus {
|
||||
pluginList := "accessibility plugin"
|
||||
if len(features.PluginsDetected) > 0 {
|
||||
pluginList = strings.Join(features.PluginsDetected, ", ")
|
||||
}
|
||||
|
||||
return &ConfidenceLevel{
|
||||
Level: "LOW",
|
||||
Reason: fmt.Sprintf("Detected %s or universal :focus-visible implementation", pluginList),
|
||||
Recommendation: "Manual keyboard testing required for validation. Automated tests may not accurately detect :focus-visible implementations.",
|
||||
}
|
||||
}
|
||||
|
||||
// Medium confidence: Partial :focus-visible implementation
|
||||
if features.HasFocusVisible && !features.HasUniversalFocus {
|
||||
return &ConfidenceLevel{
|
||||
Level: "MEDIUM",
|
||||
Reason: "Partial :focus-visible implementation detected",
|
||||
Recommendation: "Manual validation recommended for elements with :focus-visible styles.",
|
||||
}
|
||||
}
|
||||
|
||||
// High confidence: No special implementations detected
|
||||
return &ConfidenceLevel{
|
||||
Level: "HIGH",
|
||||
Reason: "No accessibility plugins or :focus-visible detected",
|
||||
Recommendation: "Results likely accurate. Standard focus indicators detected.",
|
||||
}
|
||||
}
|
||||
|
||||
// testKeyboardNavigationWithRealKeys tests keyboard navigation using real Tab key presses
|
||||
// This properly triggers :focus-within and other CSS pseudo-classes
|
||||
func (d *Daemon) testKeyboardNavigationWithRealKeys(tabID string, timeout int) (*KeyboardTestResult, error) {
|
||||
@@ -10867,6 +10989,17 @@ func (d *Daemon) testKeyboardNavigationWithRealKeys(tabID string, timeout int) (
|
||||
return nil, fmt.Errorf("failed to get page: %v", err)
|
||||
}
|
||||
|
||||
// Detect accessibility features first
|
||||
features, err := d.detectAccessibilityFeatures(page)
|
||||
if err != nil {
|
||||
d.debugLog("Warning: failed to detect accessibility features: %v", err)
|
||||
// Continue with testing even if detection fails
|
||||
features = &AccessibilityFeatures{
|
||||
PluginsDetected: []string{},
|
||||
CSSVariables: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// First, get all interactive elements and their info
|
||||
jsCode := `() => {
|
||||
const results = {
|
||||
@@ -11158,6 +11291,13 @@ func (d *Daemon) testKeyboardNavigationWithRealKeys(tabID string, timeout int) (
|
||||
d.debugLog("Completed keyboard navigation test: %d elements in tab order, %d without focus indicators",
|
||||
len(initialResult.TabOrder), initialResult.NoFocusIndicator)
|
||||
|
||||
// Add accessibility features and confidence to result
|
||||
initialResult.AccessibilityFeatures = features
|
||||
initialResult.Confidence = d.calculateConfidence(features, initialResult.NoFocusIndicator)
|
||||
|
||||
d.debugLog("Accessibility features detected: plugins=%v, focus-visible=%v, universal=%v, confidence=%s",
|
||||
features.PluginsDetected, features.HasFocusVisible, features.HasUniversalFocus, initialResult.Confidence.Level)
|
||||
|
||||
return &initialResult, nil
|
||||
}
|
||||
|
||||
@@ -12090,10 +12230,10 @@ func (d *Daemon) getPageAccessibilityReport(tabID string, tests []string, standa
|
||||
}
|
||||
}
|
||||
|
||||
// Run keyboard test if requested
|
||||
// Run keyboard test if requested (use real keys for accurate :focus-visible detection)
|
||||
if runAll || contains(tests, "keyboard") {
|
||||
d.debugLog("Running keyboard navigation test...")
|
||||
keyboardResult, err := d.testKeyboardNavigation(tabID, timeout)
|
||||
d.debugLog("Running keyboard navigation test with real Tab key simulation...")
|
||||
keyboardResult, err := d.testKeyboardNavigationWithRealKeys(tabID, timeout)
|
||||
if err == nil {
|
||||
d.processKeyboardResults(report, keyboardResult)
|
||||
}
|
||||
@@ -12343,12 +12483,14 @@ func (d *Daemon) getContrastAudit(tabID string, prioritySelectors []string, thre
|
||||
|
||||
// KeyboardAuditResult represents a keyboard navigation audit
|
||||
type KeyboardAuditResult struct {
|
||||
Status string `json:"status"`
|
||||
TotalInteractive int `json:"total_interactive"`
|
||||
Focusable int `json:"focusable"`
|
||||
Issues []KeyboardIssue `json:"issues"`
|
||||
TabOrderIssues []string `json:"tab_order_issues"`
|
||||
Recommendation string `json:"recommendation"`
|
||||
Status string `json:"status"`
|
||||
TotalInteractive int `json:"total_interactive"`
|
||||
Focusable int `json:"focusable"`
|
||||
Issues []KeyboardIssue `json:"issues"`
|
||||
TabOrderIssues []string `json:"tab_order_issues"`
|
||||
Recommendation string `json:"recommendation"`
|
||||
AccessibilityFeatures *AccessibilityFeatures `json:"accessibility_features,omitempty"`
|
||||
Confidence *ConfidenceLevel `json:"confidence,omitempty"`
|
||||
}
|
||||
|
||||
// getKeyboardAudit performs a keyboard navigation assessment
|
||||
@@ -12371,10 +12513,12 @@ func (d *Daemon) getKeyboardAudit(tabID string, checkFocusIndicators, checkTabOr
|
||||
|
||||
// Build audit result
|
||||
result := &KeyboardAuditResult{
|
||||
TotalInteractive: keyboardResult.TotalInteractive,
|
||||
Focusable: keyboardResult.Focusable,
|
||||
Issues: []KeyboardIssue{},
|
||||
TabOrderIssues: []string{},
|
||||
TotalInteractive: keyboardResult.TotalInteractive,
|
||||
Focusable: keyboardResult.Focusable,
|
||||
Issues: []KeyboardIssue{},
|
||||
TabOrderIssues: []string{},
|
||||
AccessibilityFeatures: keyboardResult.AccessibilityFeatures,
|
||||
Confidence: keyboardResult.Confidence,
|
||||
}
|
||||
|
||||
// Determine status
|
||||
@@ -12413,9 +12557,19 @@ func (d *Daemon) getKeyboardAudit(tabID string, checkFocusIndicators, checkTabOr
|
||||
if result.Status == "FAIL" {
|
||||
result.Recommendation = "Critical keyboard accessibility issues found. Fix keyboard traps immediately."
|
||||
} else if result.Status == "PARTIAL" {
|
||||
result.Recommendation = "Add visible focus indicators to all interactive elements."
|
||||
baseRec := "Add visible focus indicators to all interactive elements."
|
||||
// Adjust recommendation based on confidence level
|
||||
if result.Confidence != nil && result.Confidence.Level == "LOW" {
|
||||
result.Recommendation = baseRec + " " + result.Confidence.Recommendation
|
||||
} else {
|
||||
result.Recommendation = baseRec
|
||||
}
|
||||
} else {
|
||||
result.Recommendation = "Keyboard navigation is accessible."
|
||||
// Add confidence-based note even for passing results
|
||||
if result.Confidence != nil && result.Confidence.Level == "LOW" {
|
||||
result.Recommendation += " Note: " + result.Confidence.Recommendation
|
||||
}
|
||||
}
|
||||
|
||||
d.debugLog("Successfully generated keyboard audit for tab: %s", tabID)
|
||||
|
||||
Reference in New Issue
Block a user