This commit is contained in:
Josh at WLTechBlog
2025-10-03 12:49:50 -05:00
parent 3dc3a4caf1
commit a3a70d5091
11 changed files with 2634 additions and 2 deletions

View File

@@ -3448,7 +3448,136 @@ type ContrastCheckElement struct {
PassesAAA bool `json:"passes_aaa"`
RequiredAA float64 `json:"required_aa"`
RequiredAAA float64 `json:"required_aaa"`
Error string `json:"error,omitempty"`
}
// PageAccessibilityReport represents a comprehensive accessibility assessment of a single page
type PageAccessibilityReport struct {
URL string `json:"url"`
Timestamp string `json:"timestamp"`
ComplianceStatus string `json:"compliance_status"` // COMPLIANT, NON_COMPLIANT, PARTIAL
OverallScore int `json:"overall_score"` // 0-100
LegalRisk string `json:"legal_risk"` // LOW, MEDIUM, HIGH, CRITICAL
CriticalIssues []AccessibilityIssue `json:"critical_issues"`
SeriousIssues []AccessibilityIssue `json:"serious_issues"`
HighIssues []AccessibilityIssue `json:"high_issues"`
MediumIssues []AccessibilityIssue `json:"medium_issues"`
SummaryByWCAG map[string]WCAGSummary `json:"summary_by_wcag"`
ContrastSummary ContrastSummary `json:"contrast_summary"`
KeyboardSummary KeyboardSummary `json:"keyboard_summary"`
ARIASummary ARIASummary `json:"aria_summary"`
FormSummary *FormSummary `json:"form_summary,omitempty"`
Screenshots map[string]string `json:"screenshots,omitempty"`
EstimatedHours int `json:"estimated_remediation_hours"`
}
// AccessibilityIssue represents a single accessibility issue
type AccessibilityIssue struct {
WCAG string `json:"wcag"`
Title string `json:"title"`
Description string `json:"description"`
Impact string `json:"impact"`
Count int `json:"count"`
Examples []string `json:"examples,omitempty"`
Remediation string `json:"remediation"`
}
// WCAGSummary represents violations grouped by WCAG principle
type WCAGSummary struct {
Violations int `json:"violations"`
Severity string `json:"severity"`
}
// ContrastSummary represents a summary of contrast check results
type ContrastSummary struct {
TotalChecked int `json:"total_checked"`
Passed int `json:"passed"`
Failed int `json:"failed"`
PassRate string `json:"pass_rate"`
CriticalFailures []ContrastFailure `json:"critical_failures"`
FailurePatterns map[string]FailurePattern `json:"failure_patterns"`
}
// ContrastFailure represents a critical contrast failure
type ContrastFailure struct {
Selector string `json:"selector"`
Text string `json:"text"`
Ratio float64 `json:"ratio"`
Required float64 `json:"required"`
FgColor string `json:"fg_color"`
BgColor string `json:"bg_color"`
Fix string `json:"fix"`
}
// FailurePattern represents a pattern of similar failures
type FailurePattern struct {
Count int `json:"count"`
Ratio float64 `json:"ratio"`
Fix string `json:"fix"`
}
// KeyboardSummary represents a summary of keyboard navigation results
type KeyboardSummary struct {
TotalInteractive int `json:"total_interactive"`
Focusable int `json:"focusable"`
MissingFocusIndicator int `json:"missing_focus_indicator"`
KeyboardTraps int `json:"keyboard_traps"`
TabOrderIssues int `json:"tab_order_issues"`
Issues []KeyboardIssue `json:"issues"`
}
// KeyboardIssue represents a keyboard accessibility issue
type KeyboardIssue struct {
Type string `json:"type"`
Severity string `json:"severity"`
Count int `json:"count"`
Description string `json:"description"`
Fix string `json:"fix"`
Examples []string `json:"examples,omitempty"`
}
// ARIASummary represents a summary of ARIA validation results
type ARIASummary struct {
TotalViolations int `json:"total_violations"`
MissingNames int `json:"missing_names"`
InvalidAttributes int `json:"invalid_attributes"`
HiddenInteractive int `json:"hidden_interactive"`
Issues []ARIAIssue `json:"issues"`
}
// ARIAIssue represents an ARIA accessibility issue
type ARIAIssue struct {
Type string `json:"type"`
Severity string `json:"severity"`
Count int `json:"count"`
Description string `json:"description"`
Fix string `json:"fix"`
Examples []string `json:"examples,omitempty"`
}
// FormSummary represents a summary of form accessibility
type FormSummary struct {
FormsFound int `json:"forms_found"`
Forms []FormAudit `json:"forms"`
}
// FormAudit represents accessibility audit of a single form
type FormAudit struct {
ID string `json:"id"`
Fields int `json:"fields"`
Issues []FormIssue `json:"issues"`
ARIACompliance string `json:"aria_compliance"`
KeyboardAccessible bool `json:"keyboard_accessible"`
RequiredMarked bool `json:"required_fields_marked"`
}
// FormIssue represents a form accessibility issue
type FormIssue struct {
Type string `json:"type"`
Severity string `json:"severity"`
Count int `json:"count,omitempty"`
Description string `json:"description"`
Fix string `json:"fix"`
Ratio float64 `json:"ratio,omitempty"`
}
// CheckContrast checks color contrast for text elements on the page
@@ -4260,3 +4389,241 @@ func (c *Client) TestReflow(tabID string, widths []int, timeout int) (*ReflowTes
return &result, nil
}
// GetPageAccessibilityReport performs a comprehensive accessibility assessment of a page
// and returns a summarized report with actionable findings
// If tabID is empty, the current tab will be used
// tests is an array of test types to run (e.g., ["wcag", "contrast", "keyboard", "forms"])
// If empty, runs all tests
// standard is the WCAG standard to test against (e.g., "WCAG21AA")
// includeScreenshots determines whether to capture screenshots of violations
// timeout is in seconds, 0 means no timeout
func (c *Client) GetPageAccessibilityReport(tabID string, tests []string, standard string, includeScreenshots bool, timeout int) (*PageAccessibilityReport, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Include tests if provided
if len(tests) > 0 {
params["tests"] = strings.Join(tests, ",")
} else {
params["tests"] = "all"
}
// Include standard if provided
if standard != "" {
params["standard"] = standard
} else {
params["standard"] = "WCAG21AA"
}
// Include screenshot flag
if includeScreenshots {
params["include_screenshots"] = "true"
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("page-accessibility-report", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to get page accessibility report: %s", resp.Error)
}
// Parse the response data
var result PageAccessibilityReport
dataBytes, err := json.Marshal(resp.Data)
if err != nil {
return nil, fmt.Errorf("failed to marshal response data: %w", err)
}
err = json.Unmarshal(dataBytes, &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal page accessibility report: %w", err)
}
return &result, nil
}
// ContrastAuditResult represents a smart contrast audit with prioritized failures
type ContrastAuditResult struct {
TotalChecked int `json:"total_checked"`
Passed int `json:"passed"`
Failed int `json:"failed"`
PassRate string `json:"pass_rate"`
CriticalFailures []ContrastFailure `json:"critical_failures"`
FailurePatterns map[string]FailurePattern `json:"failure_patterns"`
}
// GetContrastAudit performs a smart contrast check with prioritized failures
// If tabID is empty, the current tab will be used
// prioritySelectors is an array of CSS selectors to prioritize (e.g., ["button", "a", "nav"])
// threshold is the WCAG level to test against ("AA" or "AAA")
// timeout is in seconds, 0 means no timeout
func (c *Client) GetContrastAudit(tabID string, prioritySelectors []string, threshold string, timeout int) (*ContrastAuditResult, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Include priority selectors if provided
if len(prioritySelectors) > 0 {
params["priority_selectors"] = strings.Join(prioritySelectors, ",")
}
// Include threshold if provided
if threshold != "" {
params["threshold"] = threshold
} else {
params["threshold"] = "AA"
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("contrast-audit", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to get contrast audit: %s", resp.Error)
}
// Parse the response data
var result ContrastAuditResult
dataBytes, err := json.Marshal(resp.Data)
if err != nil {
return nil, fmt.Errorf("failed to marshal response data: %w", err)
}
err = json.Unmarshal(dataBytes, &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal contrast audit: %w", err)
}
return &result, nil
}
// KeyboardAuditResult represents a keyboard navigation audit
type KeyboardAuditResult struct {
Status string `json:"status"` // PASS, FAIL, PARTIAL
TotalInteractive int `json:"total_interactive"`
Focusable int `json:"focusable"`
Issues []KeyboardIssue `json:"issues"`
TabOrderIssues []string `json:"tab_order_issues"`
Recommendation string `json:"recommendation"`
}
// GetKeyboardAudit performs a keyboard navigation assessment
// If tabID is empty, the current tab will be used
// checkFocusIndicators determines whether to check for visible focus indicators
// checkTabOrder determines whether to check tab order
// checkKeyboardTraps determines whether to check for keyboard traps
// timeout is in seconds, 0 means no timeout
func (c *Client) GetKeyboardAudit(tabID string, checkFocusIndicators, checkTabOrder, checkKeyboardTraps bool, timeout int) (*KeyboardAuditResult, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Include check flags
if checkFocusIndicators {
params["check_focus_indicators"] = "true"
}
if checkTabOrder {
params["check_tab_order"] = "true"
}
if checkKeyboardTraps {
params["check_keyboard_traps"] = "true"
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("keyboard-audit", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to get keyboard audit: %s", resp.Error)
}
// Parse the response data
var result KeyboardAuditResult
dataBytes, err := json.Marshal(resp.Data)
if err != nil {
return nil, fmt.Errorf("failed to marshal response data: %w", err)
}
err = json.Unmarshal(dataBytes, &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal keyboard audit: %w", err)
}
return &result, nil
}
// GetFormAccessibilityAudit performs a comprehensive form accessibility check
// If tabID is empty, the current tab will be used
// formSelector is an optional CSS selector for a specific form (defaults to all forms)
// timeout is in seconds, 0 means no timeout
func (c *Client) GetFormAccessibilityAudit(tabID, formSelector string, timeout int) (*FormSummary, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Only include form selector if it's provided
if formSelector != "" {
params["form_selector"] = formSelector
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("form-accessibility-audit", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to get form accessibility audit: %s", resp.Error)
}
// Parse the response data
var result FormSummary
dataBytes, err := json.Marshal(resp.Data)
if err != nil {
return nil, fmt.Errorf("failed to marshal response data: %w", err)
}
err = json.Unmarshal(dataBytes, &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal form accessibility audit: %w", err)
}
return &result, nil
}