UNPKG

@casoon/auditmysite

Version:

Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe

351 lines 15.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AriaRulesAnalyzer = exports.AriaImpactLevel = void 0; /** * ARIA Impact Levels (aligned with axe-core v4.10) */ var AriaImpactLevel; (function (AriaImpactLevel) { AriaImpactLevel["CRITICAL"] = "critical"; AriaImpactLevel["SERIOUS"] = "serious"; AriaImpactLevel["MODERATE"] = "moderate"; AriaImpactLevel["MINOR"] = "minor"; })(AriaImpactLevel || (exports.AriaImpactLevel = AriaImpactLevel = {})); /** * Enhanced ARIA Rules Analyzer * Leverages axe-core v4.10 improved ARIA attribute support */ class AriaRulesAnalyzer { /** * Analyze ARIA usage on a page with enhanced axe-core v4.10 features */ async analyzeAriaUsage(page) { try { const analysis = await page.evaluate(() => { const results = { totalAriaElements: 0, validAriaUsage: 0, invalidAriaUsage: 0, ariaLabelIssues: 0, ariaDescribedByIssues: 0, ariaRequiredIssues: 0, ariaMultilineIssues: 0, roleIssues: 0, landmarkRoles: [], interactiveRoles: [], impactBreakdown: { critical: 0, serious: 0, moderate: 0, minor: 0 }, ariaScore: 0, enhancedFeatures: { permissiveAriaHandling: false, descendantLabeling: false, modernAriaSupport: false }, recommendations: [] }; // Find all elements with ARIA attributes or roles const allElements = document.querySelectorAll('*'); const ariaElements = []; allElements.forEach(element => { const hasAriaAttributes = Array.from(element.attributes).some(attr => attr.name.startsWith('aria-') || attr.name === 'role'); if (hasAriaAttributes) { ariaElements.push(element); } }); results.totalAriaElements = ariaElements.length; // Enhanced ARIA Analysis with axe-core v4.10 improvements ariaElements.forEach(element => { this.analyzeElementAria(element, results); }); // Landmark and Interactive Role Analysis this.analyzeLandmarkRoles(results); this.analyzeInteractiveRoles(results); // Calculate ARIA Score results.ariaScore = this.calculateAriaScore(results); // Detect Enhanced Features (new in axe-core v4.10) results.enhancedFeatures = this.detectEnhancedAriaFeatures(); return results; }); // Generate enhanced recommendations analysis.recommendations = this.generateAriaRecommendations(analysis); return analysis; } catch (error) { console.warn('ARIA analysis failed:', error); return this.getDefaultAriaAnalysis(); } } /** * Analyze individual element ARIA usage (runs in browser context) */ analyzeElementAria(element, results) { const role = element.getAttribute('role'); const ariaLabel = element.getAttribute('aria-label'); const ariaLabelledBy = element.getAttribute('aria-labelledby'); const ariaDescribedBy = element.getAttribute('aria-describedby'); const ariaRequired = element.getAttribute('aria-required'); const ariaMultiline = element.getAttribute('aria-multiline'); let hasValidAria = true; let impactLevel = 'minor'; // 1. Role validation if (role) { const validRoles = [ 'alert', 'alertdialog', 'application', 'article', 'banner', 'button', 'cell', 'checkbox', 'columnheader', 'combobox', 'complementary', 'contentinfo', 'definition', 'dialog', 'directory', 'document', 'feed', 'figure', 'form', 'grid', 'gridcell', 'group', 'heading', 'img', 'link', 'list', 'listbox', 'listitem', 'log', 'main', 'marquee', 'math', 'menu', 'menubar', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'navigation', 'none', 'note', 'option', 'presentation', 'progressbar', 'radio', 'radiogroup', 'region', 'row', 'rowgroup', 'rowheader', 'scrollbar', 'search', 'searchbox', 'separator', 'slider', 'spinbutton', 'status', 'switch', 'tab', 'table', 'tablist', 'tabpanel', 'term', 'textbox', 'timer', 'toolbar', 'tooltip', 'tree', 'treegrid', 'treeitem' ]; if (!validRoles.includes(role)) { results.roleIssues++; hasValidAria = false; impactLevel = 'serious'; } } // 2. ARIA Label validation (enhanced in axe-core v4.10) if (ariaLabelledBy) { // Check if referenced elements exist const ids = ariaLabelledBy.split(/\s+/); const missingIds = ids.filter(id => !document.getElementById(id)); if (missingIds.length > 0) { results.ariaLabelIssues++; hasValidAria = false; impactLevel = 'serious'; } } // 3. ARIA DescribedBy validation if (ariaDescribedBy) { const ids = ariaDescribedBy.split(/\s+/); const missingIds = ids.filter(id => !document.getElementById(id)); if (missingIds.length > 0) { results.ariaDescribedByIssues++; hasValidAria = false; impactLevel = 'moderate'; } } // 4. ARIA Required validation (improved permissive handling in v4.10) if (ariaRequired) { const validValues = ['true', 'false']; if (!validValues.includes(ariaRequired.toLowerCase())) { // axe-core v4.10 is more permissive, but still flag obvious errors if (!['yes', 'no', '1', '0'].includes(ariaRequired.toLowerCase())) { results.ariaRequiredIssues++; hasValidAria = false; impactLevel = 'moderate'; } } } // 5. ARIA Multiline validation (improved in axe-core v4.10) if (ariaMultiline) { const validValues = ['true', 'false']; if (!validValues.includes(ariaMultiline.toLowerCase())) { // More permissive handling if (!['yes', 'no', '1', '0'].includes(ariaMultiline.toLowerCase())) { results.ariaMultilineIssues++; hasValidAria = false; impactLevel = 'minor'; } } } // 6. Check for descendant labeling (new feature detection) if (ariaLabel || ariaLabelledBy) { const hasDescendantLabels = element.querySelector('[aria-label], [aria-labelledby]'); if (hasDescendantLabels) { // This is now better supported in axe-core v4.10 results.enhancedFeatures.descendantLabeling = true; } } // Update counters if (hasValidAria) { results.validAriaUsage++; } else { results.invalidAriaUsage++; // Update impact breakdown switch (impactLevel) { case 'critical': results.impactBreakdown.critical++; break; case 'serious': results.impactBreakdown.serious++; break; case 'moderate': results.impactBreakdown.moderate++; break; case 'minor': results.impactBreakdown.minor++; break; } } } /** * Analyze landmark roles usage */ analyzeLandmarkRoles(results) { const landmarkElements = document.querySelectorAll('[role="banner"], [role="main"], [role="complementary"], [role="contentinfo"], [role="navigation"], [role="search"], [role="form"], [role="region"]'); const landmarks = new Set(); landmarkElements.forEach(element => { const role = element.getAttribute('role'); if (role) landmarks.add(role); }); results.landmarkRoles = Array.from(landmarks); } /** * Analyze interactive roles usage */ analyzeInteractiveRoles(results) { const interactiveElements = document.querySelectorAll('[role="button"], [role="link"], [role="menuitem"], [role="option"], [role="radio"], [role="checkbox"], [role="textbox"], [role="combobox"], [role="listbox"], [role="slider"], [role="spinbutton"], [role="switch"], [role="tab"]'); const interactive = new Set(); interactiveElements.forEach(element => { const role = element.getAttribute('role'); if (role) interactive.add(role); }); results.interactiveRoles = Array.from(interactive); } /** * Detect enhanced ARIA features support (axe-core v4.10) */ detectEnhancedAriaFeatures() { // Check for permissive ARIA handling const permissiveElements = document.querySelectorAll('[aria-required="yes"], [aria-required="1"], [aria-multiline="yes"], [aria-multiline="1"]'); const permissiveAriaHandling = permissiveElements.length > 0; // Check for descendant labeling const descendantLabeling = document.querySelectorAll('*[aria-label] [aria-label], *[aria-labelledby] [aria-labelledby]').length > 0; // Check for modern ARIA support (ARIA 1.2+ features) const modernElements = document.querySelectorAll('[aria-current], [aria-details], [aria-keyshortcuts], [aria-roledescription]'); const modernAriaSupport = modernElements.length > 0; return { permissiveAriaHandling, descendantLabeling, modernAriaSupport }; } /** * Calculate overall ARIA score (0-100) */ calculateAriaScore(results) { if (results.totalAriaElements === 0) return 100; // No ARIA = no issues let score = 100; // Deduct points based on impact score -= results.impactBreakdown.critical * 15; score -= results.impactBreakdown.serious * 10; score -= results.impactBreakdown.moderate * 5; score -= results.impactBreakdown.minor * 2; // Bonus for good practices if (results.landmarkRoles.length > 0) score += 5; if (results.enhancedFeatures.modernAriaSupport) score += 5; return Math.max(0, Math.min(100, score)); } /** * Generate enhanced ARIA recommendations */ generateAriaRecommendations(analysis) { const recommendations = []; // Critical issues if (analysis.impactBreakdown.critical > 0) { recommendations.push(`Fix ${analysis.impactBreakdown.critical} critical ARIA issues immediately`); } // Serious issues if (analysis.impactBreakdown.serious > 0) { recommendations.push(`Address ${analysis.impactBreakdown.serious} serious ARIA issues affecting accessibility`); } // Specific issue recommendations if (analysis.roleIssues > 0) { recommendations.push(`Fix ${analysis.roleIssues} invalid ARIA role(s) - use valid ARIA 1.2 roles`); } if (analysis.ariaLabelIssues > 0) { recommendations.push(`Resolve ${analysis.ariaLabelIssues} aria-labelledby reference(s) to non-existent elements`); } if (analysis.ariaDescribedByIssues > 0) { recommendations.push(`Fix ${analysis.ariaDescribedByIssues} aria-describedby reference(s) to missing elements`); } if (analysis.ariaRequiredIssues > 0) { recommendations.push(`Correct ${analysis.ariaRequiredIssues} aria-required attribute(s) - use "true" or "false"`); } // Landmark recommendations if (analysis.landmarkRoles.length === 0) { recommendations.push('Add ARIA landmarks (main, navigation, banner, contentinfo) for better structure'); } // Enhancement suggestions if (!analysis.enhancedFeatures.modernAriaSupport && analysis.totalAriaElements > 5) { recommendations.push('Consider using modern ARIA 1.2 features like aria-current and aria-details'); } // Score-based recommendations if (analysis.ariaScore < 70) { recommendations.push('ARIA implementation needs significant improvement - review all ARIA usage'); } else if (analysis.ariaScore < 90) { recommendations.push('Good ARIA usage overall - address remaining minor issues for best practices'); } return recommendations; } /** * Get default ARIA analysis in case of errors */ getDefaultAriaAnalysis() { return { totalAriaElements: 0, validAriaUsage: 0, invalidAriaUsage: 0, ariaLabelIssues: 0, ariaDescribedByIssues: 0, ariaRequiredIssues: 0, ariaMultilineIssues: 0, roleIssues: 0, landmarkRoles: [], interactiveRoles: [], impactBreakdown: { critical: 0, serious: 0, moderate: 0, minor: 0 }, ariaScore: 0, enhancedFeatures: { permissiveAriaHandling: false, descendantLabeling: false, modernAriaSupport: false }, recommendations: ['ARIA analysis unavailable - ensure page loads completely'] }; } /** * Get human-readable impact level description */ getImpactDescription(level) { const descriptions = { [AriaImpactLevel.CRITICAL]: 'Blocks access for users with disabilities', [AriaImpactLevel.SERIOUS]: 'Significantly affects accessibility', [AriaImpactLevel.MODERATE]: 'Some impact on accessibility', [AriaImpactLevel.MINOR]: 'Minor accessibility concern' }; return descriptions[level]; } /** * Check if page has comprehensive ARIA implementation */ hasComprehensiveAria(analysis) { return analysis.ariaScore >= 90 && analysis.landmarkRoles.length >= 3 && analysis.impactBreakdown.critical === 0 && analysis.impactBreakdown.serious === 0; } } exports.AriaRulesAnalyzer = AriaRulesAnalyzer; //# sourceMappingURL=aria-rules-analyzer.js.map