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

382 lines (379 loc) 18.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.OutputGenerator = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const json_generator_1 = require("./json-generator"); const csv_generator_1 = require("./csv-generator"); class OutputGenerator { /** * Generiert eine KI-freundliche Output-Datei */ async generateOutput(summary, options) { const timestamp = new Date().toISOString(); const outputData = this.prepareOutputData(summary, timestamp, options); let content; let filename; switch (options.format) { case 'json': content = this.generateJSON(outputData, options); filename = options.outputFile || `accessibility-report-${Date.now()}.json`; break; case 'csv': content = this.generateCSV(outputData, options); filename = options.outputFile || `accessibility-report-${Date.now()}.csv`; break; case 'markdown': content = this.generateMarkdown(outputData, options); filename = options.outputFile || `accessibility-report-${Date.now()}.md`; break; case 'html': content = this.generateHTML(outputData, options); filename = options.outputFile || `accessibility-report-${Date.now()}.html`; break; default: throw new Error(`Unsupported format: ${options.format}`); } // Datei schreiben const outputPath = path.resolve(filename); fs.writeFileSync(outputPath, content, 'utf8'); return outputPath; } prepareOutputData(summary, timestamp, options) { return { metadata: { timestamp, tool: '@casoon/accessibility-test-cli', version: '1.0.1', totalPages: summary.totalPages, testedPages: summary.testedPages, passedPages: summary.passedPages, failedPages: summary.failedPages, totalErrors: summary.totalErrors, totalWarnings: summary.totalWarnings, totalDuration: summary.totalDuration, successRate: summary.testedPages > 0 ? (summary.passedPages / summary.testedPages * 100).toFixed(2) + '%' : '0%' }, summary: { overall: summary.passedPages === summary.testedPages ? 'PASSED' : 'FAILED', score: summary.testedPages > 0 ? Math.round((summary.passedPages / summary.testedPages) * 100) : 0, criticalIssues: summary.totalErrors, warnings: summary.totalWarnings, averageLoadTime: summary.testedPages > 0 ? Math.round(summary.totalDuration / summary.testedPages) : 0 }, pages: options.summaryOnly ? [] : summary.results.map(result => ({ url: result.url, title: result.title, status: result.passed ? 'PASSED' : 'FAILED', loadTime: result.duration, errors: result.errors.length, warnings: result.warnings.length, issues: options.includeDetails ? { imagesWithoutAlt: result.imagesWithoutAlt, buttonsWithoutLabel: result.buttonsWithoutLabel, headingsCount: result.headingsCount, pa11yIssues: options.includePa11yIssues ? result.pa11yIssues : undefined, pa11yScore: result.pa11yScore, performanceMetrics: result.performanceMetrics, keyboardNavigation: result.keyboardNavigation, colorContrastIssues: result.colorContrastIssues, focusManagementIssues: result.focusManagementIssues, screenshots: result.screenshots } : undefined, errorDetails: options.includeDetails ? result.errors : undefined, warningDetails: options.includeDetails ? result.warnings : undefined })), recommendations: this.generateRecommendations(summary) }; } generateJSON(data, options) { const jsonGenerator = new json_generator_1.JsonGenerator(); return jsonGenerator.generateJson(data, { includeDetails: options.includeDetails, pretty: true }); } generateCSV(data, options) { const csvGenerator = new csv_generator_1.CsvGenerator(); return csvGenerator.generateCsv(data, { includeDetails: options.includeDetails, includeHeaders: true }); } generateMarkdown(data, options) { const lines = []; // Header lines.push('# Accessibility Test Report'); lines.push(`Generated: ${data.metadata.timestamp}`); lines.push(''); // Summary lines.push('## Summary'); lines.push(`- **Overall Status**: ${data.summary.overall}`); lines.push(`- **Success Rate**: ${data.metadata.successRate}`); lines.push(`- **Score**: ${data.summary.score}/100`); lines.push(`- **Critical Issues**: ${data.summary.criticalIssues}`); lines.push(`- **Warnings**: ${data.summary.warnings}`); lines.push(`- **Average Load Time**: ${data.summary.averageLoadTime}ms`); // Pa11y Score if available const pagesWithPa11yScore = data.pages.filter((page) => page.issues?.pa11yScore && page.issues.pa11yScore !== 'N/A'); if (pagesWithPa11yScore.length > 0) { const avgPa11yScore = pagesWithPa11yScore.reduce((sum, page) => sum + page.issues.pa11yScore, 0) / pagesWithPa11yScore.length; lines.push(`- **Average Pa11y Score**: ${Math.round(avgPa11yScore)}/100`); } // Performance Metrics if available const pagesWithPerformance = data.pages.filter((page) => page.issues?.performanceMetrics); if (pagesWithPerformance.length > 0) { const avgLoadTime = pagesWithPerformance.reduce((sum, page) => sum + page.issues.performanceMetrics.loadTime, 0) / pagesWithPerformance.length; lines.push(`- **Average Load Time**: ${Math.round(avgLoadTime)}ms`); } // Keyboard Navigation if available const pagesWithKeyboard = data.pages.filter((page) => page.issues?.keyboardNavigation?.length > 0); if (pagesWithKeyboard.length > 0) { lines.push(`- **Pages with Keyboard Navigation**: ${pagesWithKeyboard.length}`); } // Color Contrast Issues if available const totalColorIssues = data.pages.reduce((sum, page) => sum + (page.issues?.colorContrastIssues?.length || 0), 0); if (totalColorIssues > 0) { lines.push(`- **Total Color Contrast Issues**: ${totalColorIssues}`); } // Focus Management Issues if available const totalFocusIssues = data.pages.reduce((sum, page) => sum + (page.issues?.focusManagementIssues?.length || 0), 0); if (totalFocusIssues > 0) { lines.push(`- **Total Focus Management Issues**: ${totalFocusIssues}`); } lines.push(''); // Pages if (data.pages.length > 0) { lines.push('## Page Results'); lines.push(''); lines.push('| URL | Title | Status | Load Time | Errors | Warnings | Pa11y Score | Performance | Keyboard | Contrast | Focus |'); lines.push('|-----|-------|--------|-----------|--------|----------|-------------|-------------|----------|----------|-------|'); data.pages.forEach((page) => { const pa11yScore = page.issues?.pa11yScore || 'N/A'; const performanceScore = page.issues?.performanceMetrics?.loadTime ? Math.max(0, 100 - Math.round(page.issues.performanceMetrics.loadTime / 100)) : 'N/A'; const keyboardCount = page.issues?.keyboardNavigation?.length || 0; const contrastCount = page.issues?.colorContrastIssues?.length || 0; const focusCount = page.issues?.focusManagementIssues?.length || 0; lines.push(`| ${page.url} | ${page.title} | ${page.status} | ${page.loadTime}ms | ${page.errors} | ${page.warnings} | ${pa11yScore} | ${performanceScore} | ${keyboardCount} | ${contrastCount} | ${focusCount} |`); }); lines.push(''); } // Detailed Pa11y Issues const pagesWithPa11yIssues = data.pages.filter((page) => page.issues?.pa11yIssues && page.issues.pa11yIssues.length > 0); if (pagesWithPa11yIssues.length > 0) { lines.push('## Detailed Pa11y Issues'); lines.push(''); pagesWithPa11yIssues.forEach((page) => { lines.push(`### ${page.title} (${page.url})`); lines.push(''); page.issues.pa11yIssues.forEach((issue) => { lines.push(`#### ${issue.code}`); lines.push(`- **Type**: ${issue.type}`); lines.push(`- **Impact**: ${issue.impact || 'Unknown'}`); lines.push(`- **Message**: ${issue.message}`); if (issue.selector) { lines.push(`- **Element**: \`${issue.selector}\``); } if (issue.context) { lines.push(`- **Context**: \`${issue.context}\``); } if (issue.help) { lines.push(`- **Help**: ${issue.help}`); } if (issue.helpUrl) { lines.push(`- **More Info**: ${issue.helpUrl}`); } lines.push(''); }); }); } // 🆕 Detailed Playwright Test Results const pagesWithPlaywrightTests = data.pages.filter((page) => page.issues?.keyboardNavigation?.length > 0 || page.issues?.colorContrastIssues?.length > 0 || page.issues?.focusManagementIssues?.length > 0 || page.issues?.performanceMetrics || page.issues?.screenshots); if (pagesWithPlaywrightTests.length > 0) { lines.push('## Detailed Playwright Test Results'); lines.push(''); pagesWithPlaywrightTests.forEach((page) => { lines.push(`### ${page.title} (${page.url})`); lines.push(''); // Performance Metrics if (page.issues?.performanceMetrics) { lines.push('#### Performance Metrics'); const metrics = page.issues.performanceMetrics; lines.push(`- **Load Time**: ${Math.round(metrics.loadTime)}ms`); lines.push(`- **DOM Content Loaded**: ${Math.round(metrics.domContentLoaded)}ms`); lines.push(`- **First Paint**: ${Math.round(metrics.firstPaint)}ms`); lines.push(`- **First Contentful Paint**: ${Math.round(metrics.firstContentfulPaint)}ms`); lines.push(`- **Largest Contentful Paint**: ${Math.round(metrics.largestContentfulPaint)}ms`); lines.push(''); } // Keyboard Navigation if (page.issues?.keyboardNavigation?.length > 0) { lines.push('#### Keyboard Navigation Elements'); page.issues.keyboardNavigation.forEach((element, index) => { lines.push(`${index + 1}. ${element}`); }); lines.push(''); } // Color Contrast Issues if (page.issues?.colorContrastIssues?.length > 0) { lines.push('#### Color Contrast Issues'); page.issues.colorContrastIssues.forEach((issue, index) => { lines.push(`${index + 1}. ${issue}`); }); lines.push(''); } // Focus Management Issues if (page.issues?.focusManagementIssues?.length > 0) { lines.push('#### Focus Management Issues'); page.issues.focusManagementIssues.forEach((issue, index) => { lines.push(`${index + 1}. ${issue}`); }); lines.push(''); } // Screenshots if (page.issues?.screenshots) { lines.push('#### Screenshots'); if (page.issues.screenshots.desktop) { lines.push(`- **Desktop**: ${page.issues.screenshots.desktop}`); } if (page.issues.screenshots.mobile) { lines.push(`- **Mobile**: ${page.issues.screenshots.mobile}`); } lines.push(''); } }); } // Recommendations if (data.recommendations.length > 0) { lines.push('## Recommendations'); lines.push(''); data.recommendations.forEach((rec) => { lines.push(`- ${rec}`); }); lines.push(''); } return lines.join('\n'); } generateHTML(data, options) { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Accessibility Test Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .summary { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; } .passed { color: green; } .failed { color: red; } .warning { color: orange; } table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } </style> </head> <body> <h1>Accessibility Test Report</h1> <p>Generated: ${data.metadata.timestamp}</p> <div class="summary"> <h2>Summary</h2> <p><strong>Overall Status:</strong> <span class="${data.summary.overall === 'PASSED' ? 'passed' : 'failed'}">${data.summary.overall}</span></p> <p><strong>Success Rate:</strong> ${data.metadata.successRate}</p> <p><strong>Score:</strong> ${data.summary.score}/100</p> <p><strong>Critical Issues:</strong> ${data.summary.criticalIssues}</p> <p><strong>Warnings:</strong> ${data.summary.warnings}</p> </div> ${data.pages.length > 0 ? ` <h2>Page Results</h2> <table> <tr> <th>URL</th> <th>Title</th> <th>Status</th> <th>Load Time</th> <th>Errors</th> <th>Warnings</th> </tr> ${data.pages.map((page) => ` <tr> <td>${page.url}</td> <td>${page.title}</td> <td class="${page.status === 'PASSED' ? 'passed' : 'failed'}">${page.status}</td> <td>${page.loadTime}ms</td> <td>${page.errors}</td> <td>${page.warnings}</td> </tr> `).join('')} </table> ` : ''} ${data.recommendations.length > 0 ? ` <h2>Recommendations</h2> <ul> ${data.recommendations.map((rec) => `<li>${rec}</li>`).join('')} </ul> ` : ''} </body> </html>`; } generateRecommendations(summary) { const recommendations = []; if (summary.totalErrors > 0) { recommendations.push(`Fix ${summary.totalErrors} critical accessibility errors to meet WCAG standards.`); } if (summary.totalWarnings > 0) { recommendations.push(`Address ${summary.totalWarnings} accessibility warnings to improve user experience.`); } if (summary.failedPages > 0) { recommendations.push(`Review and fix issues on ${summary.failedPages} pages that failed accessibility tests.`); } const successRate = summary.testedPages > 0 ? (summary.passedPages / summary.testedPages) * 100 : 0; if (successRate < 80) { recommendations.push(`Improve overall accessibility score (currently ${successRate.toFixed(1)}%) to reach at least 80%.`); } if (summary.totalDuration > 30000) { recommendations.push(`Optimize page load times - average load time is ${Math.round(summary.totalDuration / summary.testedPages)}ms.`); } return recommendations; } } exports.OutputGenerator = OutputGenerator; //# sourceMappingURL=output-generator.js.map