@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
JavaScript
"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