This commit is contained in:
Josh at WLTechBlog
2025-10-03 10:19:06 -05:00
parent 741bd19bd9
commit a27273b581
27 changed files with 11258 additions and 14 deletions

View File

@@ -3497,6 +3497,542 @@ func (c *Client) CheckContrast(tabID, selector string, timeout int) (*ContrastCh
return &result, nil
}
// GradientContrastResult represents the result of gradient contrast checking
type GradientContrastResult struct {
Selector string `json:"selector"`
TextColor string `json:"text_color"`
DarkestBgColor string `json:"darkest_bg_color"`
LightestBgColor string `json:"lightest_bg_color"`
WorstContrast float64 `json:"worst_contrast"`
BestContrast float64 `json:"best_contrast"`
PassesAA bool `json:"passes_aa"`
PassesAAA bool `json:"passes_aaa"`
RequiredAA float64 `json:"required_aa"`
RequiredAAA float64 `json:"required_aaa"`
IsLargeText bool `json:"is_large_text"`
SamplePoints int `json:"sample_points"`
Error string `json:"error,omitempty"`
}
// CheckGradientContrast checks color contrast for text on gradient backgrounds using ImageMagick
// If tabID is empty, the current tab will be used
// selector is required CSS selector for element with gradient background
// timeout is in seconds, 0 means no timeout
func (c *Client) CheckGradientContrast(tabID, selector string, timeout int) (*GradientContrastResult, error) {
if selector == "" {
return nil, fmt.Errorf("selector parameter is required for gradient contrast check")
}
params := map[string]string{
"selector": selector,
}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("check-gradient-contrast", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to check gradient contrast: %s", resp.Error)
}
// Parse the response data
var result GradientContrastResult
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 gradient contrast results: %w", err)
}
return &result, nil
}
// MediaValidationResult represents the result of time-based media validation
type MediaValidationResult struct {
Videos []MediaElement `json:"videos"`
Audios []MediaElement `json:"audios"`
EmbeddedPlayers []MediaElement `json:"embedded_players"`
TranscriptLinks []string `json:"transcript_links"`
TotalViolations int `json:"total_violations"`
CriticalViolations int `json:"critical_violations"`
Warnings int `json:"warnings"`
}
// MediaElement represents a video or audio element
type MediaElement struct {
Type string `json:"type"` // "video", "audio", "youtube", "vimeo"
Src string `json:"src"`
HasCaptions bool `json:"has_captions"`
HasDescriptions bool `json:"has_descriptions"`
HasControls bool `json:"has_controls"`
Autoplay bool `json:"autoplay"`
CaptionTracks []Track `json:"caption_tracks"`
DescriptionTracks []Track `json:"description_tracks"`
Violations []string `json:"violations"`
Warnings []string `json:"warnings"`
}
// Track represents a text track (captions, descriptions, etc.)
type Track struct {
Kind string `json:"kind"`
Src string `json:"src"`
Srclang string `json:"srclang"`
Label string `json:"label"`
Accessible bool `json:"accessible"`
}
// ValidateMedia checks for video/audio captions, descriptions, and transcripts
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) ValidateMedia(tabID string, timeout int) (*MediaValidationResult, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("validate-media", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to validate media: %s", resp.Error)
}
// Parse the response data
var result MediaValidationResult
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 media validation results: %w", err)
}
return &result, nil
}
// HoverFocusTestResult represents the result of hover/focus content testing
type HoverFocusTestResult struct {
TotalElements int `json:"total_elements"`
ElementsWithIssues int `json:"elements_with_issues"`
PassedElements int `json:"passed_elements"`
Issues []HoverFocusIssue `json:"issues"`
TestedElements []HoverFocusElement `json:"tested_elements"`
}
// HoverFocusElement represents an element that shows content on hover/focus
type HoverFocusElement struct {
Selector string `json:"selector"`
Type string `json:"type"` // "tooltip", "dropdown", "popover", "custom"
Dismissible bool `json:"dismissible"`
Hoverable bool `json:"hoverable"`
Persistent bool `json:"persistent"`
PassesWCAG bool `json:"passes_wcag"`
Violations []string `json:"violations"`
}
// HoverFocusIssue represents a specific issue with hover/focus content
type HoverFocusIssue struct {
Selector string `json:"selector"`
Type string `json:"type"` // "not_dismissible", "not_hoverable", "not_persistent"
Severity string `json:"severity"` // "critical", "serious", "moderate"
Description string `json:"description"`
WCAG string `json:"wcag"` // "1.4.13"
}
// TestHoverFocusContent tests WCAG 1.4.13 compliance for content on hover or focus
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) TestHoverFocusContent(tabID string, timeout int) (*HoverFocusTestResult, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("test-hover-focus", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to test hover/focus content: %s", resp.Error)
}
// Parse the response data
var result HoverFocusTestResult
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 hover/focus test results: %w", err)
}
return &result, nil
}
// TextInImagesResult represents the result of text-in-images detection
type TextInImagesResult struct {
TotalImages int `json:"total_images"`
ImagesWithText int `json:"images_with_text"`
ImagesWithoutText int `json:"images_without_text"`
Violations int `json:"violations"`
Warnings int `json:"warnings"`
Images []ImageTextAnalysis `json:"images"`
}
// ImageTextAnalysis represents OCR analysis of a single image
type ImageTextAnalysis struct {
Src string `json:"src"`
Alt string `json:"alt"`
HasAlt bool `json:"has_alt"`
DetectedText string `json:"detected_text"`
TextLength int `json:"text_length"`
Confidence float64 `json:"confidence"`
IsViolation bool `json:"is_violation"`
ViolationType string `json:"violation_type"` // "missing_alt", "insufficient_alt", "decorative_with_text"
Recommendation string `json:"recommendation"`
}
// DetectTextInImages uses Tesseract OCR to detect text in images
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DetectTextInImages(tabID string, timeout int) (*TextInImagesResult, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("detect-text-in-images", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to detect text in images: %s", resp.Error)
}
// Parse the response data
var result TextInImagesResult
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 text-in-images results: %w", err)
}
return &result, nil
}
// CrossPageConsistencyResult represents the result of cross-page consistency checking
type CrossPageConsistencyResult struct {
PagesAnalyzed int `json:"pages_analyzed"`
ConsistencyIssues int `json:"consistency_issues"`
NavigationIssues int `json:"navigation_issues"`
StructureIssues int `json:"structure_issues"`
Pages []PageConsistencyAnalysis `json:"pages"`
CommonNavigation []string `json:"common_navigation"`
InconsistentPages []string `json:"inconsistent_pages"`
}
// PageConsistencyAnalysis represents consistency analysis of a single page
type PageConsistencyAnalysis struct {
URL string `json:"url"`
Title string `json:"title"`
HasHeader bool `json:"has_header"`
HasFooter bool `json:"has_footer"`
HasNavigation bool `json:"has_navigation"`
NavigationLinks []string `json:"navigation_links"`
MainLandmarks int `json:"main_landmarks"`
HeaderLandmarks int `json:"header_landmarks"`
FooterLandmarks int `json:"footer_landmarks"`
NavigationLandmarks int `json:"navigation_landmarks"`
Issues []string `json:"issues"`
}
// CheckCrossPageConsistency analyzes multiple pages for consistency
// If tabID is empty, the current tab will be used
// timeout is in seconds per page, 0 means no timeout
func (c *Client) CheckCrossPageConsistency(tabID string, urls []string, timeout int) (*CrossPageConsistencyResult, error) {
if len(urls) == 0 {
return nil, fmt.Errorf("no URLs provided for consistency check")
}
params := map[string]string{
"urls": strings.Join(urls, ","),
}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("check-cross-page-consistency", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to check cross-page consistency: %s", resp.Error)
}
// Parse the response data
var result CrossPageConsistencyResult
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 cross-page consistency results: %w", err)
}
return &result, nil
}
// SensoryCharacteristicsResult represents the result of sensory characteristics detection
type SensoryCharacteristicsResult struct {
TotalElements int `json:"total_elements"`
ElementsWithIssues int `json:"elements_with_issues"`
Violations int `json:"violations"`
Warnings int `json:"warnings"`
Elements []SensoryCharacteristicsElement `json:"elements"`
PatternMatches map[string]int `json:"pattern_matches"`
}
// SensoryCharacteristicsElement represents an element with potential sensory-only instructions
type SensoryCharacteristicsElement struct {
TagName string `json:"tag_name"`
Text string `json:"text"`
MatchedPatterns []string `json:"matched_patterns"`
Severity string `json:"severity"` // "violation", "warning"
Recommendation string `json:"recommendation"`
}
// DetectSensoryCharacteristics detects instructions that rely only on sensory characteristics
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DetectSensoryCharacteristics(tabID string, timeout int) (*SensoryCharacteristicsResult, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("detect-sensory-characteristics", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to detect sensory characteristics: %s", resp.Error)
}
// Parse the response data
var result SensoryCharacteristicsResult
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 sensory characteristics results: %w", err)
}
return &result, nil
}
// AnimationFlashResult represents the result of animation/flash detection
type AnimationFlashResult struct {
TotalAnimations int `json:"total_animations"`
FlashingContent int `json:"flashing_content"`
RapidAnimations int `json:"rapid_animations"`
AutoplayAnimations int `json:"autoplay_animations"`
Violations int `json:"violations"`
Warnings int `json:"warnings"`
Elements []AnimationFlashElement `json:"elements"`
}
// AnimationFlashElement represents an animated or flashing element
type AnimationFlashElement struct {
TagName string `json:"tag_name"`
Selector string `json:"selector"`
AnimationType string `json:"animation_type"` // "css", "gif", "video", "canvas", "svg"
FlashRate float64 `json:"flash_rate"` // Flashes per second
Duration float64 `json:"duration"` // Animation duration in seconds
IsAutoplay bool `json:"is_autoplay"`
HasControls bool `json:"has_controls"`
CanPause bool `json:"can_pause"`
IsViolation bool `json:"is_violation"`
ViolationType string `json:"violation_type"`
Recommendation string `json:"recommendation"`
}
// DetectAnimationFlash detects animations and flashing content
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) DetectAnimationFlash(tabID string, timeout int) (*AnimationFlashResult, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("detect-animation-flash", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to detect animation/flash: %s", resp.Error)
}
// Parse the response data
var result AnimationFlashResult
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 animation/flash results: %w", err)
}
return &result, nil
}
// EnhancedAccessibilityResult represents enhanced accessibility tree analysis
type EnhancedAccessibilityResult struct {
TotalElements int `json:"total_elements"`
ElementsWithIssues int `json:"elements_with_issues"`
ARIAViolations int `json:"aria_violations"`
RoleViolations int `json:"role_violations"`
RelationshipIssues int `json:"relationship_issues"`
LandmarkIssues int `json:"landmark_issues"`
Elements []EnhancedAccessibilityElement `json:"elements"`
}
// EnhancedAccessibilityElement represents an element with accessibility analysis
type EnhancedAccessibilityElement struct {
TagName string `json:"tag_name"`
Selector string `json:"selector"`
Role string `json:"role"`
AriaLabel string `json:"aria_label"`
AriaDescribedBy string `json:"aria_described_by"`
AriaLabelledBy string `json:"aria_labelled_by"`
AriaRequired bool `json:"aria_required"`
AriaInvalid bool `json:"aria_invalid"`
AriaHidden bool `json:"aria_hidden"`
TabIndex int `json:"tab_index"`
IsInteractive bool `json:"is_interactive"`
HasAccessibleName bool `json:"has_accessible_name"`
Issues []string `json:"issues"`
Recommendations []string `json:"recommendations"`
}
// AnalyzeEnhancedAccessibility performs enhanced accessibility tree analysis
// If tabID is empty, the current tab will be used
// timeout is in seconds, 0 means no timeout
func (c *Client) AnalyzeEnhancedAccessibility(tabID string, timeout int) (*EnhancedAccessibilityResult, error) {
params := map[string]string{}
// Only include tab ID if it's provided
if tabID != "" {
params["tab"] = tabID
}
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("analyze-enhanced-accessibility", params)
if err != nil {
return nil, err
}
if !resp.Success {
return nil, fmt.Errorf("failed to analyze enhanced accessibility: %s", resp.Error)
}
// Parse the response data
var result EnhancedAccessibilityResult
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 enhanced accessibility results: %w", err)
}
return &result, nil
}
// KeyboardTestResult represents the result of keyboard navigation testing
type KeyboardTestResult struct {
TotalInteractive int `json:"total_interactive"`