Monorepo for Aesthetic.Computer
aesthetic.computer
1// Chrome DevTools Performance Testing with Puppeteer
2// Automated performance analysis of aesthetic.computer boot sequence
3
4import puppeteer from 'puppeteer';
5import lighthouse from 'lighthouse';
6import { writeFileSync } from 'fs';
7import { join } from 'path';
8
9const TEST_URL = process.env.TEST_URL || 'https://localhost:8888';
10const OUTPUT_DIR = './tests/performance/reports';
11
12/**
13 * Measure boot performance with Chrome Performance API
14 */
15async function measureBootPerformance() {
16 console.log('🚀 Starting Chrome DevTools Performance Test\n');
17
18 const browser = await puppeteer.launch({
19 headless: true,
20 args: [
21 '--no-sandbox',
22 '--disable-setuid-sandbox',
23 '--ignore-certificate-errors', // For local SSL
24 '--disable-web-security', // For local CORS testing
25 ]
26 });
27
28 try {
29 const page = await browser.newPage();
30
31 // Enable Performance monitoring
32 await page.coverage.startJSCoverage();
33 const client = await page.target().createCDPSession();
34 await client.send('Performance.enable');
35
36 console.log('📊 Loading page and capturing metrics...\n');
37 const startTime = Date.now();
38
39 // Navigate and wait for network idle
40 await page.goto(TEST_URL, {
41 waitUntil: 'networkidle2',
42 timeout: 30000
43 });
44
45 // Wait for boot complete or timeout
46 try {
47 await page.waitForFunction(
48 () => window.acBOOT_START_TIME && document.readyState === 'complete',
49 { timeout: 10000 }
50 );
51 } catch (e) {
52 console.warn('⚠️ Boot completion markers not found, using basic timing');
53 }
54
55 const loadTime = Date.now() - startTime;
56
57 // Collect Performance metrics
58 const performanceMetrics = await page.evaluate(() => {
59 const perf = performance.getEntriesByType('navigation')[0];
60 const paint = performance.getEntriesByType('paint');
61
62 return {
63 // Navigation timing
64 dns: perf.domainLookupEnd - perf.domainLookupStart,
65 tcp: perf.connectEnd - perf.connectStart,
66 ttfb: perf.responseStart - perf.requestStart,
67 download: perf.responseEnd - perf.responseStart,
68 domInteractive: perf.domInteractive,
69 domComplete: perf.domComplete,
70 loadComplete: perf.loadEventEnd - perf.loadEventStart,
71
72 // Paint timing
73 firstPaint: paint.find(p => p.name === 'first-paint')?.startTime || 0,
74 firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0,
75
76 // Custom timing
77 bootStartTime: window.acBOOT_START_TIME,
78 currentTime: performance.now(),
79 };
80 });
81
82 // Get resource timing
83 const resources = await page.evaluate(() => {
84 return performance.getEntriesByType('resource').map(r => ({
85 name: r.name.split('/').pop() || r.name,
86 duration: r.duration,
87 size: r.transferSize,
88 type: r.initiatorType,
89 })).sort((a, b) => b.duration - a.duration).slice(0, 10); // Top 10 slowest
90 });
91
92 // Get JS coverage
93 const jsCoverage = await page.coverage.stopJSCoverage();
94 const totalBytes = jsCoverage.reduce((sum, entry) => sum + entry.text.length, 0);
95 const usedBytes = jsCoverage.reduce((sum, entry) => {
96 const used = entry.ranges.reduce((s, range) => s + (range.end - range.start), 0);
97 return sum + used;
98 }, 0);
99 const coveragePercent = ((usedBytes / totalBytes) * 100).toFixed(1);
100
101 // Print results
102 console.log('⏱️ Performance Metrics:');
103 console.log(` Total Load Time: ${loadTime}ms`);
104 console.log(` DNS Lookup: ${performanceMetrics.dns.toFixed(2)}ms`);
105 console.log(` TCP Connect: ${performanceMetrics.tcp.toFixed(2)}ms`);
106 console.log(` Time to First Byte: ${performanceMetrics.ttfb.toFixed(2)}ms`);
107 console.log(` Download: ${performanceMetrics.download.toFixed(2)}ms`);
108 console.log(` DOM Interactive: ${performanceMetrics.domInteractive.toFixed(2)}ms`);
109 console.log(` DOM Complete: ${performanceMetrics.domComplete.toFixed(2)}ms`);
110 console.log(` First Paint: ${performanceMetrics.firstPaint.toFixed(2)}ms`);
111 console.log(` First Contentful Paint: ${performanceMetrics.firstContentfulPaint.toFixed(2)}ms\n`);
112
113 console.log('📦 Top 10 Slowest Resources:');
114 resources.forEach((r, i) => {
115 const size = r.size ? `(${(r.size / 1024).toFixed(1)}KB)` : '';
116 console.log(` ${i + 1}. ${r.name} - ${r.duration.toFixed(2)}ms ${size}`);
117 });
118 console.log();
119
120 console.log('📊 JavaScript Coverage:');
121 console.log(` Total JS: ${(totalBytes / 1024).toFixed(1)}KB`);
122 console.log(` Used JS: ${(usedBytes / 1024).toFixed(1)}KB`);
123 console.log(` Coverage: ${coveragePercent}%`);
124 console.log(` Unused: ${((totalBytes - usedBytes) / 1024).toFixed(1)}KB\n`);
125
126 // Performance thresholds
127 const thresholds = {
128 fcp: 1500, // First Contentful Paint
129 tti: 3000, // Time to Interactive (approximated by domComplete)
130 load: 5000, // Total load time
131 };
132
133 console.log('✅ Performance Checks:');
134 console.log(` ${performanceMetrics.firstContentfulPaint < thresholds.fcp ? '✅' : '❌'} FCP under ${thresholds.fcp}ms: ${performanceMetrics.firstContentfulPaint.toFixed(0)}ms`);
135 console.log(` ${performanceMetrics.domComplete < thresholds.tti ? '✅' : '❌'} TTI under ${thresholds.tti}ms: ${performanceMetrics.domComplete.toFixed(0)}ms`);
136 console.log(` ${loadTime < thresholds.load ? '✅' : '❌'} Load under ${thresholds.load}ms: ${loadTime}ms\n`);
137
138 return {
139 loadTime,
140 metrics: performanceMetrics,
141 resources,
142 coverage: { totalBytes, usedBytes, percent: coveragePercent },
143 };
144
145 } finally {
146 await browser.close();
147 }
148}
149
150/**
151 * Run Lighthouse audit
152 */
153async function runLighthouseAudit() {
154 console.log('💡 Running Lighthouse Audit...\n');
155
156 const browser = await puppeteer.launch({
157 headless: true,
158 args: [
159 '--no-sandbox',
160 '--disable-setuid-sandbox',
161 '--ignore-certificate-errors',
162 ]
163 });
164
165 try {
166 const { lhr } = await lighthouse(TEST_URL, {
167 port: new URL(browser.wsEndpoint()).port,
168 output: 'json',
169 onlyCategories: ['performance'],
170 formFactor: 'desktop',
171 screenEmulation: { disabled: true },
172 });
173
174 const scores = {
175 performance: lhr.categories.performance.score * 100,
176 fcp: lhr.audits['first-contentful-paint'].numericValue,
177 lcp: lhr.audits['largest-contentful-paint'].numericValue,
178 tbt: lhr.audits['total-blocking-time'].numericValue,
179 cls: lhr.audits['cumulative-layout-shift'].numericValue,
180 speedIndex: lhr.audits['speed-index'].numericValue,
181 };
182
183 console.log('💡 Lighthouse Performance Score:');
184 console.log(` Overall: ${scores.performance.toFixed(0)}/100`);
185 console.log(` First Contentful Paint: ${scores.fcp.toFixed(0)}ms`);
186 console.log(` Largest Contentful Paint: ${scores.lcp.toFixed(0)}ms`);
187 console.log(` Total Blocking Time: ${scores.tbt.toFixed(0)}ms`);
188 console.log(` Cumulative Layout Shift: ${scores.cls.toFixed(3)}`);
189 console.log(` Speed Index: ${scores.speedIndex.toFixed(0)}ms\n`);
190
191 // Save full report
192 const reportPath = join(OUTPUT_DIR, `lighthouse-${Date.now()}.json`);
193 try {
194 writeFileSync(reportPath, JSON.stringify(lhr, null, 2));
195 console.log(`📄 Full report saved to: ${reportPath}\n`);
196 } catch (e) {
197 console.log('⚠️ Could not save report (directory may not exist)\n');
198 }
199
200 return scores;
201
202 } finally {
203 await browser.close();
204 }
205}
206
207/**
208 * Main test runner
209 */
210async function runTests() {
211 try {
212 console.log('🎯 Chrome DevTools Performance Testing\n');
213 console.log(`Testing URL: ${TEST_URL}\n`);
214 console.log('─'.repeat(60) + '\n');
215
216 // Run performance measurement
217 const perfResults = await measureBootPerformance();
218
219 console.log('─'.repeat(60) + '\n');
220
221 // Run Lighthouse audit (optional, can be slow)
222 if (process.env.RUN_LIGHTHOUSE !== 'false') {
223 const lighthouseResults = await runLighthouseAudit();
224 console.log('─'.repeat(60) + '\n');
225 }
226
227 console.log('✅ All tests completed!\n');
228
229 // Exit with error if performance is poor
230 if (perfResults.loadTime > 5000) {
231 console.error('❌ Performance threshold exceeded!');
232 process.exit(1);
233 }
234
235 } catch (error) {
236 console.error('❌ Test failed:', error.message);
237 process.exit(1);
238 }
239}
240
241// Run tests if called directly
242if (import.meta.url === `file://${process.argv[1]}`) {
243 runTests();
244}
245
246export { measureBootPerformance, runLighthouseAudit };