UNPKG

@casoon/auditmysite

Version:

A comprehensive command-line tool for automated accessibility, security, performance, and SEO testing using Playwright and pa11y, based on sitemap URLs

440 lines (398 loc) โ€ข 20.9 kB
#!/usr/bin/env node const { Command } = require('commander'); const { StandardPipeline } = require('../dist/core/standard-pipeline'); const inquirer = require('inquirer').default; const path = require('path'); const program = new Command(); program .name('auditmysite') .description('A powerful command-line tool for automated accessibility testing using Playwright and pa11y, based on sitemap URLs') .version('1.0.0') .argument('<sitemapUrl>', 'URL of the sitemap.xml to test') .option('-m, --max-pages <number>', 'Maximum number of pages to test') .option('-t, --timeout <number>', 'Timeout in milliseconds', '10000') .option('-w, --wait-until <string>', 'Wait until (domcontentloaded|load|networkidle)', 'domcontentloaded') .option('-f, --filter <patterns>', 'Exclude URL patterns (comma-separated)') .option('-i, --include <patterns>', 'Include URL patterns (comma-separated)') .option('-v, --verbose', 'Verbose output') .option('--standard <standard>', 'Accessibility standard (WCAG2A|WCAG2AA|WCAG2AAA|Section508)', 'WCAG2AA') .option('--output-dir <dir>', 'Output directory for reports', './reports') .option('--include-details', 'Include detailed information in output') .option('--include-pa11y', 'Include pa11y issues in output') .option('--pa11y-standard <standard>', 'Pa11y standard (WCAG2A|WCAG2AA|WCAG2AAA|Section508)', 'WCAG2AA') .option('--hide-elements <selectors>', 'CSS selectors to hide from pa11y tests') .option('--include-notices', 'Include pa11y notices in output') .option('--include-warnings', 'Include pa11y warnings in output', true) .option('--pa11y-wait <ms>', 'Wait time after page load for pa11y tests', '1000') .option('--performance-metrics', 'Collect performance metrics') .option('--screenshots', 'Capture desktop and mobile screenshots') .option('--keyboard-tests', 'Test keyboard navigation') .option('--color-contrast', 'Test color contrast (simplified)') .option('--focus-management', 'Test focus management') .option('--block-images', 'Block images for faster testing') .option('--block-css', 'Block CSS for faster testing') .option('--mobile-emulation', 'Enable mobile emulation') .option('--viewport <size>', 'Set viewport size (e.g., 1920x1080)', '1920x1080') .option('--user-agent <agent>', 'Set custom user agent (default: auditmysite/1.0)') .option('--detailed-report', 'Generate detailed error report for automated fixes') .option('--performance-report', 'Generate performance report with PageSpeed/Lightspeed analysis') .option('--no-performance-report', 'Disable performance report generation') .option('--seo-report', 'Generate SEO report with search engine optimization analysis') .option('--no-seo-report', 'Disable SEO report generation') .option('--security-scan', 'Run comprehensive security scan (headers, HTTPS, CSP, vulnerabilities)') .option('--no-security-scan', 'Disable security scan') .option('--security-report', 'Generate detailed security report') .option('--no-security-report', 'Disable security report generation') .option('--skip-csp-localhost', 'Skip CSP test for localhost (faster development testing)') // ๐Ÿ†• pa11y-Optionen .option('--use-pa11y', 'Use pa11y for detailed accessibility testing (slower, more memory)') .option('--no-pa11y', 'Disable pa11y, use only Playwright tests (faster, less memory)') // ๐Ÿ†• Lighthouse-Optionen .option('--lighthouse', 'Run Lighthouse tests for performance, accessibility, best practices, and SEO') .option('--core-web-vitals', 'Test Core Web Vitals performance metrics (LCP, FID, CLS, FCP, TTI, TBT)') .option('--touch-targets', 'Test touch target sizes for mobile accessibility (44px minimum)') .option('--pwa', 'Test Progressive Web App features (manifest, service worker, installability)') // ๐Ÿš€ Parallele Test-Optionen (Queue ist jetzt Standard) .option('--max-concurrent <number>', 'Number of parallel workers (default: 3)', '3') .option('--concurrency <number>', 'Alias for --max-concurrent') .option('--max-workers <number>', 'Alias for --max-concurrent') .option('--max-retries <number>', 'Maximum retry attempts for failed tests (default: 3)', '3') .option('--retry-delay <ms>', 'Delay between retry attempts in milliseconds (default: 2000)', '2000') .option('--no-progress-bar', 'Disable live progress bar') .option('--progress-interval <ms>', 'Progress update interval in milliseconds (default: 1000)', '1000') .option('--no-resource-monitoring', 'Disable resource monitoring (memory/CPU)') .option('--max-memory <mb>', 'Maximum memory usage in MB (default: 512)', '512') .option('--max-cpu <percent>', 'Maximum CPU usage percentage (default: 80)', '80') // ๐Ÿ†• Legacy-Option fรผr sequenzielle Tests .option('--sequential', 'Use sequential testing (legacy mode, slower)') // ๐Ÿ†• Output-Format-Optionen .option('--html', 'Generate HTML report instead of Markdown') .option('--no-copy-buttons', 'Disable copy-to-clipboard buttons in HTML report') // ๐Ÿ†• Non-Interactive Modus fรผr automatisierte Tests .option('--non-interactive', 'Skip all interactive prompts and use defaults (for CI/CD)') .action(async (sitemapUrl, options) => { console.log('๐Ÿš€ Starte Accessibility-Test...'); console.log(`๐Ÿ“„ Sitemap: ${sitemapUrl}`); // Handle alias options for parallel workers let maxConcurrent = options.maxConcurrent; if (options.concurrency) maxConcurrent = options.concurrency; if (options.maxWorkers) maxConcurrent = options.maxWorkers; // Interactive prompts for all options if not specified let maxPages = options.maxPages; let standard = options.standard || 'WCAG2AA'; // Default standard let generateDetailedReport = options.detailedReport; let generatePerformanceReport = options.performanceReport; let generateSeoReport = options.seoReport; let generateSecurityReport = options.securityReport; // Handle negative flags (--no-* options) if (options.noDetailedReport) generateDetailedReport = false; if (options.noPerformanceReport) generatePerformanceReport = false; if (options.noSeoReport) generateSeoReport = false; if (options.noSecurityReport) generateSecurityReport = false; // Set sensible defaults for all parameters if not provided if (generateDetailedReport === undefined) generateDetailedReport = true; if (generatePerformanceReport === undefined) generatePerformanceReport = true; if (generateSeoReport === undefined) generateSeoReport = true; if (generateSecurityReport === undefined) generateSecurityReport = false; // Show prompts for parameters that are not set via CLI // Only skip prompts for parameters that are explicitly provided const maxPagesChoices = [ { name: '5 pages (Quick test)', value: 5 }, { name: '10 pages (Standard test)', value: 10 }, { name: '20 pages (Comprehensive test)', value: 20 }, { name: '50 pages (Full audit)', value: 50 }, { name: '100 pages (Complete analysis)', value: 100 }, { name: 'All pages (Maximum coverage)', value: 1000 } ]; const standardChoices = [ { name: 'WCAG 2.0 Level A (Basic)', value: 'WCAG2A' }, { name: 'WCAG 2.0 Level AA (Recommended)', value: 'WCAG2AA' }, { name: 'WCAG 2.0 Level AAA (Strict)', value: 'WCAG2AAA' }, { name: 'Section 508 (US Federal)', value: 'Section508' } ]; const prompts = []; // Only ask for maxPages if not provided via CLI if (!options.maxPages) { prompts.push({ type: 'list', name: 'maxPages', message: 'How many pages would you like to test?', choices: maxPagesChoices, default: 20 }); } // Only ask for standard if not provided via CLI if (!options.standard) { prompts.push({ type: 'list', name: 'standard', message: 'Which accessibility standard would you like to test against?', choices: standardChoices, default: standard }); } // Only ask for detailed report if not provided via CLI if (options.detailedReport === undefined && !options.noDetailedReport) { prompts.push({ type: 'confirm', name: 'generateDetailedReport', message: 'Would you like to generate a detailed error report for automated fixes?', default: true }); } // Only ask for performance report if not provided via CLI if (options.performanceReport === undefined && !options.noPerformanceReport) { prompts.push({ type: 'confirm', name: 'generatePerformanceReport', message: 'Would you like to generate a performance report with PageSpeed/Lightspeed analysis?', default: true }); } // Only ask for SEO report if not provided via CLI if (options.seoReport === undefined && !options.noSeoReport) { prompts.push({ type: 'confirm', name: 'generateSeoReport', message: 'Would you like to generate an SEO report with search engine optimization analysis?', default: true }); } // Only ask for security report if not provided via CLI if (options.securityReport === undefined && !options.noSecurityReport) { prompts.push({ type: 'confirm', name: 'generateSecurityReport', message: 'Would you like to run a comprehensive security scan (headers, HTTPS, CSP, vulnerabilities)?', default: false }); } // Nur Prompts anzeigen, wenn welche vorhanden sind und nicht im non-interactive Modus if (prompts.length > 0 && !options.nonInteractive) { const answers = await inquirer.prompt(prompts); // Werte aus Prompts aktualisieren (nur fรผr Parameter, die abgefragt wurden) if (!options.maxPages) maxPages = answers.maxPages; if (!options.standard) standard = answers.standard; if (options.detailedReport === undefined && !options.noDetailedReport) generateDetailedReport = answers.generateDetailedReport; if (options.performanceReport === undefined && !options.noPerformanceReport) generatePerformanceReport = answers.generatePerformanceReport; if (options.seoReport === undefined && !options.noSeoReport) generateSeoReport = answers.generateSeoReport; if (options.securityReport === undefined && !options.noSecurityReport) generateSecurityReport = answers.generateSecurityReport; } else if (options.nonInteractive && prompts.length > 0) { // Im non-interactive Modus Standardwerte fรผr alle Prompts verwenden console.log('๐Ÿค– Non-interactive Modus: Verwende Standardwerte fรผr alle Prompts'); if (!options.maxPages) maxPages = 20; // Standard: umfassender Test if (!options.standard) standard = 'WCAG2AA'; // Standard-Standard // Andere Standardwerte sind bereits oben gesetzt } // Set default for maxPages if not provided via CLI or prompts if (!maxPages) maxPages = 20; // Setze Standardwerte fรผr Output-Format let outputFormat = options.html ? 'html' : 'markdown'; let includeCopyButtons = !options.noCopyButtons; // Prompt fรผr Output-Format (falls nicht gesetzt und nicht non-interactive) if (!options.html && !options.noCopyButtons && !options.nonInteractive) { const formatAnswer = await inquirer.prompt([{ type: 'list', name: 'outputFormat', message: 'Welches Ausgabeformat mรถchten Sie verwenden?', choices: [ { name: 'Markdown (Standard)', value: 'markdown' }, { name: 'HTML (Interaktiv mit Copy-Buttons)', value: 'html' } ], default: 'markdown' }]); outputFormat = formatAnswer.outputFormat; if (outputFormat === 'html') { const copyButtonsAnswer = await inquirer.prompt([{ type: 'confirm', name: 'includeCopyButtons', message: 'Copy-to-Clipboard Buttons fรผr AI-kompatible Daten aktivieren?', default: true }]); includeCopyButtons = copyButtonsAnswer.includeCopyButtons; } } else if (options.nonInteractive && !options.html && !options.noCopyButtons) { // Im non-interactive Modus Markdown als Standard verwenden console.log('๐Ÿค– Non-interactive Modus: Verwende Markdown-Ausgabeformat'); outputFormat = 'markdown'; includeCopyButtons = false; // Nicht anwendbar fรผr Markdown } // Ensure maxPages is a number maxPages = parseInt(maxPages); console.log(`๐Ÿงช Max Pages: ${maxPages}`); console.log(`โฑ๏ธ Timeout: ${options.timeout}ms`); console.log(`๐Ÿ“‹ Standard: ${standard}`); console.log(`๐Ÿ“‹ Detailed Report: ${generateDetailedReport ? 'Yes' : 'No'}`); console.log(`๐Ÿ“‹ Performance Report: ${generatePerformanceReport ? 'Yes' : 'No'}`); console.log(`๐Ÿ“‹ SEO Report: ${generateSeoReport ? 'Yes' : 'No'}`); console.log(`๐Ÿ”’ Security Report: ${generateSecurityReport ? 'Yes' : 'No'}`); console.log(`๐Ÿš€ Queue Processing: ${options.sequential ? 'No (sequential)' : 'Yes (standard)'}`); console.log(`๐Ÿ“„ Output Format: ${outputFormat.toUpperCase()}`); if (outputFormat === 'html') { console.log(`๐Ÿ“‹ Copy Buttons: ${includeCopyButtons ? 'Yes' : 'No'}`); } if (!options.sequential) { console.log(`๐Ÿ”ง Parallel Workers: ${maxConcurrent}`); console.log(`๐Ÿ”„ Max Retries: ${options.maxRetries}`); console.log(`โฑ๏ธ Retry Delay: ${options.retryDelay}ms`); } try { // Extract domain and create subdirectory const url = new URL(sitemapUrl); const domain = url.hostname.replace(/\./g, '-'); // Verwende lokale Zeitzone statt UTC const now = new Date(); const currentTimestamp = now.toLocaleString('de-DE', { timeZone: 'Europe/Berlin', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/(\d{2})\.(\d{2})\.(\d{4}), (\d{2}):(\d{2}):(\d{2})/, '$3-$2-$1T$4:$5:$6+02:00'); const dateOnly = now.toLocaleDateString('de-DE', { timeZone: 'Europe/Berlin', year: 'numeric', month: '2-digit', day: '2-digit' }).split('.').reverse().join('-'); // DD.MM.YYYY -> YYYY-MM-DD // Create subdirectory based on domain const fs = require('fs'); const subDir = path.join(options.outputDir, domain); if (!fs.existsSync(subDir)) { fs.mkdirSync(subDir, { recursive: true }); } // Use simple filenames without domain const filename = `accessibility-report-${dateOnly}.md`; const outputPath = path.join(subDir, filename); // Run standard pipeline const pipeline = new StandardPipeline(); // Parse viewport size if provided let viewportSize; if (options.viewport) { const [width, height] = options.viewport.split('x').map(Number); if (width && height) { viewportSize = { width, height }; } } const pipelineOptions = { sitemapUrl, maxPages: maxPages, timeout: parseInt(options.timeout), pa11yStandard: standard, outputDir: subDir, // Use subdirectory instead of main output directory includeDetails: options.includeDetails, includePa11yIssues: options.includePa11y, generateDetailedReport: generateDetailedReport, generatePerformanceReport: generatePerformanceReport, generateSeoReport: generateSeoReport, generateSecurityReport: generateSecurityReport, skipCspForLocalhost: options.skipCspLocalhost, hideElements: options.hideElements, includeNotices: options.includeNotices, includeWarnings: options.includeWarnings, wait: parseInt(options.pa11yWait), // ๐Ÿ†• Neue Playwright-Optionen collectPerformanceMetrics: options.performanceMetrics || generatePerformanceReport, captureScreenshots: options.screenshots, testKeyboardNavigation: options.keyboardTests, testColorContrast: options.colorContrast, testFocusManagement: options.focusManagement, blockImages: options.blockImages, blockCSS: options.blockCSS, mobileEmulation: options.mobileEmulation, viewportSize, userAgent: options.userAgent, // ๐Ÿš€ Parallele Test-Optionen useSequentialTesting: options.sequential, maxConcurrent: parseInt(maxConcurrent), maxRetries: parseInt(options.maxRetries), retryDelay: parseInt(options.retryDelay), enableProgressBar: !options.noProgressBar, progressUpdateInterval: parseInt(options.progressInterval), enableResourceMonitoring: !options.noResourceMonitoring, maxMemoryUsage: parseInt(options.maxMemory), maxCpuUsage: parseInt(options.maxCpu), // ๐Ÿ†• Output-Format-Optionen outputFormat: outputFormat, includeCopyButtons: includeCopyButtons, // ๐Ÿ• Timestamp fรผr Report-Generierung timestamp: currentTimestamp }; console.log('๐Ÿงช Fรผhre Accessibility-Tests aus...'); const { summary, outputFiles } = await pipeline.run(pipelineOptions); // Rename the output file to use domain-based naming if (outputFiles.length > 0 && options.markdown !== false) { const originalFile = outputFiles[0]; // Stelle sicher, dass die Datei mit aktuellem Timestamp neu generiert wird if (fs.existsSync(originalFile)) { const content = fs.readFileSync(originalFile, 'utf8'); const updatedContent = content.replace( /Generated: .*/, `Generated: ${currentTimestamp}` ); fs.writeFileSync(originalFile, updatedContent, 'utf8'); } fs.renameSync(originalFile, outputPath); console.log(''); console.log('โœ… Test erfolgreich abgeschlossen!'); console.log(`๐Ÿ“Š Ergebnisse:`); console.log(` - Getestete Seiten: ${summary.testedPages}`); console.log(` - Bestanden: ${summary.passedPages}`); console.log(` - Fehlgeschlagen: ${summary.failedPages}`); console.log(` - Fehler: ${summary.totalErrors}`); console.log(` - Warnungen: ${summary.totalWarnings}`); console.log(` - Erfolgsrate: ${summary.testedPages > 0 ? (summary.passedPages / summary.testedPages * 100).toFixed(1) : 0}%`); console.log(`๐Ÿ“„ Markdown-Bericht: ${outputPath}`); // Zeige alle generierten Dateien an if (outputFiles.length > 0) { console.log(`๐Ÿ“ Generierte Dateien:`); outputFiles.forEach(file => { const filename = path.basename(file); if (filename.includes('detailed-errors')) { console.log(` ๐Ÿ“‹ Detaillierter Fehlerbericht: ${file}`); } else if (filename.includes('performance-report')) { console.log(` ๐Ÿ“Š Performance-Bericht: ${file}`); } else if (filename.includes('seo-report')) { console.log(` ๐Ÿ” SEO-Bericht: ${file}`); } else if (filename.includes('security-report')) { console.log(` ๐Ÿ”’ Security-Bericht: ${file}`); } else { console.log(` ๐Ÿ“„ Markdown-Bericht: ${file}`); } }); } if (summary.failedPages > 0) { console.log(`โš ๏ธ ${summary.failedPages} Seiten haben die Accessibility-Tests nicht bestanden`); process.exit(1); } } else { console.log(''); console.log('โœ… Test erfolgreich abgeschlossen!'); console.log(`๐Ÿ“Š Ergebnisse:`); console.log(` - Getestete Seiten: ${summary.testedPages}`); console.log(` - Bestanden: ${summary.passedPages}`); console.log(` - Fehlgeschlagen: ${summary.failedPages}`); console.log(` - Fehler: ${summary.totalErrors}`); console.log(` - Warnungen: ${summary.totalWarnings}`); // Zeige alle generierten Dateien an (auch ohne Markdown) if (outputFiles.length > 0) { console.log(`๐Ÿ“ Generierte Dateien:`); outputFiles.forEach(file => { const filename = path.basename(file); if (filename.includes('detailed-errors')) { console.log(` ๐Ÿ“‹ Detaillierter Fehlerbericht: ${file}`); } else if (filename.includes('performance-report')) { console.log(` ๐Ÿ“Š Performance-Bericht: ${file}`); } }); } } } catch (error) { console.error('โŒ Fehler wรคhrend des Tests:', error.message); process.exit(1); } }); program.parse();