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

464 lines 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MainAccessibilityChecker = void 0; const accessibility_checker_1 = require("./core/accessibility/accessibility-checker"); const content_weight_analyzer_1 = require("./analyzers/content-weight-analyzer"); const performance_collector_1 = require("./analyzers/performance-collector"); const seo_analyzer_1 = require("./analyzers/seo-analyzer"); const mobile_friendliness_analyzer_1 = require("./analyzers/mobile-friendliness-analyzer"); const playwright_1 = require("playwright"); /** * @deprecated FULLY OBSOLETE - DO NOT USE * * MainAccessibilityChecker has been REPLACED by enhanced AccessibilityChecker with comprehensive analysis. * * ✅ NEW: Use AccessibilityChecker with enableComprehensiveAnalysis: true * ❌ OLD: MainAccessibilityChecker (sequential, obsolete) * * Migration: * ```typescript * // OLD (deprecated) * const checker = new MainAccessibilityChecker(options); * await checker.analyze(html, url); * * // NEW (recommended) * const checker = new AccessibilityChecker({ * enableComprehensiveAnalysis: true, * qualityAnalysisOptions: options * }); * await checker.testPage(url, testOptions); * ``` * * The new enhanced AccessibilityChecker provides: * - Event-driven parallel browser architecture * - URL normalization (fixes sitemap parser issues) * - All analyzers integrated (performance, SEO, content-weight, mobile) * - Better error handling and resource management * - Compatible with existing report generators * * This class will be REMOVED in v3.0.0 */ class MainAccessibilityChecker { constructor(options = {}) { this.browser = null; this.accessibilityChecker = new accessibility_checker_1.AccessibilityChecker(); this.contentWeightAnalyzer = new content_weight_analyzer_1.ContentWeightAnalyzer(); this.performanceCollector = new performance_collector_1.PerformanceCollector(options); this.seoAnalyzer = new seo_analyzer_1.SEOAnalyzer(options); this.mobileFriendlinessAnalyzer = new mobile_friendliness_analyzer_1.MobileFriendlinessAnalyzer(); } /** * Initialize all analyzers and launch browser if needed */ async initialize() { try { // Launch browser for analysis this.browser = await playwright_1.chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); // Initialize accessibility checker await this.accessibilityChecker.initialize(); } catch (error) { console.error('Error initializing accessibility checker:', error); throw error; } } /** * @deprecated OBSOLETE: Sequential analysis pattern * Use event-driven parallel browser processing instead * Run comprehensive analysis including accessibility, performance, SEO, and content weight */ async analyze(html, url) { if (!this.browser) { throw new Error('Accessibility checker not initialized. Call initialize() first.'); } try { // Extract URL string from object if needed const urlString = typeof url === 'string' ? url : (url?.loc || url?.url || String(url)); console.log(`🔍 Testing: ${JSON.stringify(url)}`); console.log('Running accessibility analysis...'); // For HTML content analysis, we'll use a data URI const dataUri = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`; // Run standard accessibility analysis using the extracted URL string (simplified to prevent stack overflow) const accessibilityResults = { url: urlString, title: '', imagesWithoutAlt: 0, buttonsWithoutLabel: 0, headingsCount: 0, errors: [], warnings: [], passed: true, duration: 0 }; // Try to run actual accessibility check, but fail gracefully try { const realResults = await this.accessibilityChecker.testPage(urlString, { verbose: true, // Enable verbose for debugging collectPerformanceMetrics: false, timeout: 15000, // Increased timeout for pa11y wait: 3000, // Wait for content to load includeWarnings: true, includeNotices: true, pa11yStandard: 'WCAG2AA' }); console.log('🔍 Pa11y Results Debug:', { url: realResults.url, pa11yScore: realResults.pa11yScore, pa11yIssues: realResults.pa11yIssues?.length || 0, errors: realResults.errors?.length || 0, warnings: realResults.warnings?.length || 0, passed: realResults.passed }); Object.assign(accessibilityResults, realResults); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.warn('Accessibility check failed, using fallback result:', errorMessage); console.warn('Stack trace:', error); // Create fallback result with at least some accessibility score accessibilityResults.errors = ['Pa11y analysis failed: ' + errorMessage]; accessibilityResults.passed = false; accessibilityResults.pa11yScore = 50; // Fallback score when pa11y fails } // Create a page for analysis const page = await this.browser.newPage(); try { // Set content and wait for page to be ready await page.setContent(html, { waitUntil: 'domcontentloaded' }); // Run analyses using the extracted URL string console.log('Running content weight analysis...'); const contentWeight = await this.analyzeContentWeight(page, urlString); console.log('Running performance analysis...'); const enhancedPerformance = await this.analyzePerformance(page, urlString); console.log('Running SEO analysis...'); const enhancedSEO = await this.analyzeSEO(page, urlString); console.log('Running mobile-friendliness analysis...'); const mobileFriendliness = await this.analyzeMobileFriendliness(page, urlString); console.log('Calculating quality score...'); const qualityScore = this.calculateQualityScore(contentWeight, enhancedPerformance, enhancedSEO, mobileFriendliness, accessibilityResults); return { ...accessibilityResults, contentWeight, enhancedPerformance, enhancedSEO, mobileFriendliness: mobileFriendliness || undefined, qualityScore }; } finally { await page.close(); } } catch (error) { console.error('Error during analysis:', error); throw error; } } /** * Analyze content weight using the ContentWeightAnalyzer */ async analyzeContentWeight(page, url) { try { const result = await this.contentWeightAnalyzer.analyze(page, url); const { contentWeight, contentAnalysis } = result; const score = this.calculateContentScore(contentWeight, contentAnalysis); const grade = this.calculateGrade(score); return { contentScore: score, grade, resourceAnalysis: { html: { size: contentWeight.html, count: 1 }, css: { size: contentWeight.css, count: 0 }, javascript: { size: contentWeight.javascript, count: 0 }, images: { size: contentWeight.images, count: contentAnalysis.imageCount }, fonts: { size: contentWeight.fonts, count: 0 } }, contentMetrics: { textToCodeRatio: contentAnalysis.textToCodeRatio, totalSize: contentWeight.total, contentSize: contentAnalysis.textContent } }; } catch (error) { console.warn('Content weight analysis failed:', error); return this.getDefaultContentWeightResult(); } } /** * Analyze performance using the PerformanceCollector */ async analyzePerformance(page, url) { try { const metrics = await this.performanceCollector.collectEnhancedMetrics(page, url); return { performanceScore: metrics.performanceScore, grade: metrics.performanceGrade, coreWebVitals: { fcp: { value: metrics.firstContentfulPaint, rating: this.rateMetric(metrics.firstContentfulPaint, 'fcp') }, lcp: { value: metrics.lcp, rating: this.rateMetric(metrics.lcp, 'lcp') }, cls: { value: metrics.cls, rating: this.rateMetric(metrics.cls, 'cls') }, inp: { value: metrics.inp, rating: this.rateMetric(metrics.inp, 'inp') } }, metrics: { ttfb: { value: metrics.ttfb, rating: this.rateMetric(metrics.ttfb, 'ttfb') }, fid: { value: metrics.fid, rating: this.rateMetric(metrics.fid, 'fid') }, tbt: { value: metrics.tbt, rating: this.rateMetric(metrics.tbt, 'tbt') }, si: { value: metrics.speedIndex, rating: this.rateMetric(metrics.speedIndex, 'si') } } }; } catch (error) { console.warn('Performance analysis failed:', error); return this.getDefaultPerformanceResult(); } } /** * Analyze SEO using the EnhancedSEOAnalyzer */ async analyzeSEO(page, url) { try { const seoMetrics = await this.seoAnalyzer.analyzeSEO(page, url); return { seoScore: seoMetrics.overallSEOScore, grade: seoMetrics.seoGrade, metaData: { title: seoMetrics.metaTags?.title?.content || '', titleLength: seoMetrics.metaTags?.title?.length || 0, description: seoMetrics.metaTags?.description?.content || '', descriptionLength: seoMetrics.metaTags?.description?.length || 0, keywords: seoMetrics.metaTags?.keywords?.content || '' }, headingStructure: { h1: seoMetrics.headingStructure.h1Count, h2: seoMetrics.headingStructure.h2Count, h3: seoMetrics.headingStructure.h3Count, h4: seoMetrics.headingStructure.h4Count, h5: seoMetrics.headingStructure.h5Count, h6: seoMetrics.headingStructure.h6Count }, contentAnalysis: { wordCount: seoMetrics.wordCount, readabilityScore: seoMetrics.readabilityScore, textToCodeRatio: 0 // Will be filled from content analysis }, socialTags: { openGraph: Object.keys(seoMetrics.socialTags?.openGraph || {}).length, twitterCard: Object.keys(seoMetrics.socialTags?.twitterCard || {}).length }, technicalSEO: { internalLinks: seoMetrics.technicalSEO?.linkAnalysis?.internalLinks || 0, externalLinks: seoMetrics.technicalSEO?.linkAnalysis?.externalLinks || 0, altTextCoverage: 0 // Calculate based on image analysis } }; } catch (error) { console.warn('Enhanced SEO analysis failed:', error); return this.getDefaultSEOResult(); } } /** * Analyze mobile-friendliness using the MobileFriendlinessAnalyzer */ async analyzeMobileFriendliness(page, url) { try { return await this.mobileFriendlinessAnalyzer.analyzeMobileFriendliness(page, url); } catch (error) { console.warn('Mobile-friendliness analysis failed:', error); return null; } } /** * Calculate overall quality score based on all analysis results */ calculateQualityScore(contentWeight, performance, seo, mobileFriendliness, accessibility) { // Calculate individual scores (0-100) const contentScore = contentWeight?.contentScore || 0; const performanceScore = performance?.performanceScore || 0; const seoScore = seo?.seoScore || 0; const mobileScore = mobileFriendliness?.overallScore || 0; // Calculate accessibility score from errors const accessibilityIssues = accessibility.errors?.length || 0; const accessibilityScore = Math.max(0, 100 - (accessibilityIssues * 10)); // Weighted average calculation (including mobile-friendliness) const weights = { performance: 0.25, seo: 0.2, accessibility: 0.25, content: 0.15, mobile: 0.15 }; const combinedScore = Math.round(performanceScore * weights.performance + seoScore * weights.seo + accessibilityScore * weights.accessibility + contentScore * weights.content + mobileScore * weights.mobile); // Determine grade let grade = 'F'; if (combinedScore >= 90) grade = 'A'; else if (combinedScore >= 80) grade = 'B'; else if (combinedScore >= 70) grade = 'C'; else if (combinedScore >= 60) grade = 'D'; return { score: combinedScore, grade, breakdown: { performance: performanceScore, seo: seoScore, accessibility: accessibilityScore, content: contentScore, mobile: mobileScore } }; } /** * Helper methods */ calculateContentScore(weight, analysis) { // Simple scoring based on text-to-code ratio and content size const ratioScore = Math.min(analysis.textToCodeRatio * 100, 50); const sizeScore = weight.total < 1000000 ? 50 : Math.max(0, 50 - (weight.total - 1000000) / 100000); return Math.round(ratioScore + sizeScore); } calculateGrade(score) { if (score >= 90) return 'A'; if (score >= 80) return 'B'; if (score >= 70) return 'C'; if (score >= 60) return 'D'; return 'F'; } rateMetric(value, metricType) { // Simplified rating system - you can make this more sophisticated const thresholds = { fcp: { good: 1800, poor: 3000 }, lcp: { good: 2500, poor: 4000 }, cls: { good: 0.1, poor: 0.25 }, inp: { good: 200, poor: 500 }, ttfb: { good: 800, poor: 1800 }, fid: { good: 100, poor: 300 }, tbt: { good: 200, poor: 600 }, si: { good: 3400, poor: 5800 } }; const threshold = thresholds[metricType]; if (!threshold) return 'unknown'; if (metricType === 'cls') { return value <= threshold.good ? 'good' : (value <= threshold.poor ? 'needs-improvement' : 'poor'); } else { return value <= threshold.good ? 'good' : (value <= threshold.poor ? 'needs-improvement' : 'poor'); } } /** * Get default content weight result for fallback */ getDefaultContentWeightResult() { return { contentScore: 0, grade: 'N/A', resourceAnalysis: { html: { size: 0, count: 1 }, css: { size: 0, count: 0 }, javascript: { size: 0, count: 0 }, images: { size: 0, count: 0 }, fonts: { size: 0, count: 0 } }, contentMetrics: { textToCodeRatio: 0, totalSize: 0, contentSize: 0 } }; } /** * Get default performance result for fallback */ getDefaultPerformanceResult() { return { performanceScore: 0, grade: 'N/A', coreWebVitals: { fcp: { value: 0, rating: 'poor' }, lcp: { value: 0, rating: 'poor' }, cls: { value: 0, rating: 'poor' }, inp: { value: 0, rating: 'poor' } }, metrics: { ttfb: { value: 0, rating: 'poor' }, fid: { value: 0, rating: 'poor' }, tbt: { value: 0, rating: 'poor' }, si: { value: 0, rating: 'poor' } } }; } /** * Get default SEO result for fallback */ getDefaultSEOResult() { return { seoScore: 0, grade: 'N/A', metaData: { title: '', titleLength: 0, description: '', descriptionLength: 0, keywords: '' }, headingStructure: { h1: 0, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 }, contentAnalysis: { wordCount: 0, readabilityScore: 0, textToCodeRatio: 0 }, socialTags: { openGraph: 0, twitterCard: 0 }, technicalSEO: { internalLinks: 0, externalLinks: 0, altTextCoverage: 0 } }; } /** * Clean up resources */ async cleanup() { try { // Cleanup accessibility checker await this.accessibilityChecker.cleanup(); // Close browser if (this.browser) { await this.browser.close(); this.browser = null; } } catch (error) { console.error('Error during cleanup:', error); } } } exports.MainAccessibilityChecker = MainAccessibilityChecker; //# sourceMappingURL=accessibility-checker-main.js.map