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

244 lines (243 loc) 9.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CoreWebVitalsTest = void 0; const base_test_1 = require("../base-test"); class CoreWebVitalsTest extends base_test_1.BaseAccessibilityTest { constructor(budget) { super(); this.name = 'Core Web Vitals Test'; this.description = 'Tests Core Web Vitals performance metrics (LCP, FID, CLS, FCP, TTI, TBT)'; this.category = 'performance'; this.priority = 'high'; this.standards = ['WCAG2AA', 'WCAG2AAA']; this.budget = { lcp: 2500, // 2.5s - Good fid: 100, // 100ms - Good cls: 0.1, // 0.1 - Good fcp: 1800, // 1.8s - Good tti: 3800, // 3.8s - Good tbt: 200 // 200ms - Good }; if (budget) { this.budget = { ...this.budget, ...budget }; } } async run(context) { const startTime = Date.now(); try { // Navigate to page await context.page.goto(context.url, { waitUntil: 'networkidle' }); // Wait for page to be fully loaded await context.page.waitForTimeout(2000); // Collect Core Web Vitals metrics const metrics = await this.collectCoreWebVitals(context.page); // Calculate score and check budget const score = this.calculateScore(metrics); const budgetExceeded = this.checkBudget(metrics); const recommendations = this.generateRecommendations(metrics); const duration = Date.now() - startTime; return { passed: score >= 80, count: 1, errors: budgetExceeded ? ['Performance budget exceeded'] : [], warnings: score < 90 ? ['Performance score below excellent threshold'] : [], details: { score, metrics, budgetExceeded, recommendations, duration, url: context.url, testName: this.name, description: this.description } }; } catch (error) { return { passed: false, count: 0, errors: [`Error occurred during Core Web Vitals measurement: ${error}`], warnings: [], details: { score: 0, metrics: this.getDefaultMetrics(), budgetExceeded: true, duration: Date.now() - startTime, url: context.url, testName: this.name, description: this.description } }; } } async collectCoreWebVitals(page) { // Use Performance API to collect metrics const metrics = await page.evaluate(() => { return new Promise((resolve) => { const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); const lcpEntry = entries.find(entry => entry.entryType === 'largest-contentful-paint'); const fidEntry = entries.find(entry => entry.entryType === 'first-input'); // Get navigation timing const navigation = performance.getEntriesByType('navigation')[0]; // Calculate metrics const lcp = lcpEntry ? lcpEntry.startTime : 0; const fid = fidEntry ? fidEntry.processingStart - fidEntry.startTime : 0; const fcp = navigation ? navigation.firstContentfulPaint : 0; const tti = navigation ? navigation.domInteractive : 0; // Calculate CLS (simplified) let cls = 0; const layoutShiftEntries = performance.getEntriesByType('layout-shift'); if (layoutShiftEntries.length > 0) { cls = layoutShiftEntries.reduce((sum, entry) => sum + entry.value, 0); } // Calculate TBT (simplified) const longTasks = performance.getEntriesByType('longtask'); const tbt = longTasks.reduce((sum, task) => sum + task.duration, 0); // Calculate FMP and SI (simplified) const fmp = fcp; // Simplified const si = fcp * 1.2; // Simplified calculation resolve({ lcp, fid, cls, fcp, tti, tbt, fmp, si }); }); observer.observe({ entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift', 'longtask'] }); // Fallback if no metrics are collected setTimeout(() => { const navigation = performance.getEntriesByType('navigation')[0]; resolve({ lcp: navigation?.loadEventEnd || 0, fid: 0, cls: 0, fcp: navigation?.firstContentfulPaint || 0, tti: navigation?.domInteractive || 0, tbt: 0, fmp: navigation?.firstContentfulPaint || 0, si: (navigation?.firstContentfulPaint || 0) * 1.2 }); }, 5000); }); }); return metrics; } calculateScore(metrics) { let score = 100; // LCP scoring (0-25 points) if (metrics.lcp <= 2500) score -= 0; else if (metrics.lcp <= 4000) score -= 10; else score -= 25; // FID scoring (0-25 points) if (metrics.fid <= 100) score -= 0; else if (metrics.fid <= 300) score -= 10; else score -= 25; // CLS scoring (0-25 points) if (metrics.cls <= 0.1) score -= 0; else if (metrics.cls <= 0.25) score -= 10; else score -= 25; // FCP scoring (0-15 points) if (metrics.fcp <= 1800) score -= 0; else if (metrics.fcp <= 3000) score -= 5; else score -= 15; // TTI scoring (0-10 points) if (metrics.tti <= 3800) score -= 0; else if (metrics.tti <= 7300) score -= 5; else score -= 10; return Math.max(0, score); } checkBudget(metrics) { return !!((this.budget.lcp && metrics.lcp > this.budget.lcp) || (this.budget.fid && metrics.fid > this.budget.fid) || (this.budget.cls && metrics.cls > this.budget.cls) || (this.budget.fcp && metrics.fcp > this.budget.fcp) || (this.budget.tti && metrics.tti > this.budget.tti) || (this.budget.tbt && metrics.tbt > this.budget.tbt)); } generateRecommendations(metrics) { const recommendations = []; if (metrics.lcp > 2500) { recommendations.push('Optimize Largest Contentful Paint: Optimize images, use CDN, implement lazy loading'); } if (metrics.fid > 100) { recommendations.push('Reduce First Input Delay: Minimize JavaScript execution time, split code bundles'); } if (metrics.cls > 0.1) { recommendations.push('Improve Cumulative Layout Shift: Set explicit dimensions for images and ads'); } if (metrics.fcp > 1800) { recommendations.push('Optimize First Contentful Paint: Minimize critical resources, optimize CSS delivery'); } if (metrics.tti > 3800) { recommendations.push('Improve Time to Interactive: Reduce JavaScript execution time, optimize resource loading'); } if (metrics.tbt > 200) { recommendations.push('Reduce Total Blocking Time: Split long tasks, optimize JavaScript execution'); } if (recommendations.length === 0) { recommendations.push('All Core Web Vitals are within good thresholds'); } return recommendations; } generateDetails(metrics, score, budgetExceeded) { return ` Core Web Vitals Analysis: - Largest Contentful Paint (LCP): ${metrics.lcp.toFixed(0)}ms ${this.getStatus(metrics.lcp, 2500)} - First Input Delay (FID): ${metrics.fid.toFixed(0)}ms ${this.getStatus(metrics.fid, 100)} - Cumulative Layout Shift (CLS): ${metrics.cls.toFixed(3)} ${this.getStatus(metrics.cls, 0.1)} - First Contentful Paint (FCP): ${metrics.fcp.toFixed(0)}ms ${this.getStatus(metrics.fcp, 1800)} - Time to Interactive (TTI): ${metrics.tti.toFixed(0)}ms ${this.getStatus(metrics.tti, 3800)} - Total Blocking Time (TBT): ${metrics.tbt.toFixed(0)}ms ${this.getStatus(metrics.tbt, 200)} Performance Score: ${score}/100 Budget Exceeded: ${budgetExceeded ? 'Yes' : 'No'} `.trim(); } getStatus(value, threshold) { if (value <= threshold) return '✅ Good'; if (value <= threshold * 1.6) return '⚠️ Needs Improvement'; return '❌ Poor'; } getDefaultMetrics() { return { lcp: 0, fid: 0, cls: 0, fcp: 0, tti: 0, tbt: 0, fmp: 0, si: 0 }; } setBudget(budget) { this.budget = { ...this.budget, ...budget }; } getBudget() { return { ...this.budget }; } } exports.CoreWebVitalsTest = CoreWebVitalsTest; //# sourceMappingURL=core-web-vitals-test.js.map