Puppeteer provides rich Chrome DevTools Protocol (CDP) functionality, allowing developers to access browser-level debugging and performance analysis capabilities.
1. CDP Basics
Create CDP Session:
javascriptconst client = await page.target().createCDPSession();
Enable CDP Domains:
javascriptawait client.send('Performance.enable'); await client.send('Network.enable'); await client.send('Runtime.enable');
Send CDP Commands:
javascriptconst result = await client.send('Performance.getMetrics'); console.log(result);
Listen to CDP Events:
javascriptclient.on('Network.requestWillBeSent', (params) => { console.log('Request:', params.request.url); });
2. Performance Monitoring
Enable Performance Monitoring:
javascriptconst client = await page.target().createCDPSession(); await client.send('Performance.enable');
Get Performance Metrics:
javascriptconst metrics = await client.send('Performance.getMetrics'); console.log('Performance Metrics:', metrics.metrics);
Key Performance Metrics:
javascriptconst metrics = await client.send('Performance.getMetrics'); const metricMap = {}; metrics.metrics.forEach(m => metricMap[m.name] = m.value); console.log({ Timestamp: metricMap.Timestamp, Documents: metricMap.Documents, Frames: metricMap.Frames, JSEventListeners: metricMap.JSEventListeners, Nodes: metricMap.Nodes, LayoutCount: metricMap.LayoutCount, RecalcStyleCount: metricMap.RecalcStyleCount, LayoutDuration: metricMap.LayoutDuration, RecalcStyleDuration: metricMap.RecalcStyleDuration, ScriptDuration: metricMap.ScriptDuration, TaskDuration: metricMap.TaskDuration });
Performance Tracing:
javascript// Start tracing await client.send('Performance.enable'); await client.send('Tracing.start', { traceConfig: { includedCategories: ['devtools.timeline', 'blink.user_timing'] } }); // Execute operations await page.goto('https://example.com'); // Stop tracing const traceData = await client.send('Tracing.stop');
3. Network Monitoring
Enable Network Monitoring:
javascriptconst client = await page.target().createCDPSession(); await client.send('Network.enable');
Monitor Network Requests:
javascriptclient.on('Network.requestWillBeSent', (params) => { console.log('Request:', { url: params.request.url, method: params.request.method, type: params.type }); });
Monitor Network Responses:
javascriptclient.on('Network.responseReceived', (params) => { console.log('Response:', { url: params.response.url, status: params.response.status, mimeType: params.response.mimeType }); });
Get Request Body:
javascriptclient.on('Network.requestWillBeSent', async (params) => { if (params.request.postData) { console.log('Request body:', params.request.postData); } });
Get Response Body:
javascriptclient.on('Network.responseReceived', async (params) => { const responseBody = await client.send('Network.getResponseBody', { requestId: params.requestId }); console.log('Response body:', responseBody.body); });
4. Runtime Debugging
Enable Runtime Monitoring:
javascriptconst client = await page.target().createCDPSession(); await client.send('Runtime.enable');
Execute JavaScript:
javascriptconst result = await client.send('Runtime.evaluate', { expression: 'document.title' }); console.log('Result:', result.result.value);
Get Console Logs:
javascriptclient.on('Runtime.consoleAPICalled', (params) => { console.log('Console:', params.type, params.args); });
Listen to Exceptions:
javascriptclient.on('Runtime.exceptionThrown', (params) => { console.error('Exception:', params.exceptionDetails); });
5. DOM Monitoring
Enable DOM Monitoring:
javascriptconst client = await page.target().createCDPSession(); await client.send('DOM.enable');
Get Document Root Node:
javascriptconst root = await client.send('DOM.getDocument'); console.log('Root node:', root.root);
Query Node:
javascriptconst result = await client.send('DOM.querySelector', { nodeId: root.root.nodeId, selector: '.my-element' }); console.log('Node:', result.nodeId);
Get Node Attributes:
javascriptconst attributes = await client.send('DOM.getAttributes', { nodeId: result.nodeId }); console.log('Attributes:', attributes.attributes);
6. Page Monitoring
Enable Page Monitoring:
javascriptconst client = await page.target().createCDPSession(); await client.send('Page.enable');
Listen to Page Load:
javascriptclient.on('Page.loadEventFired', () => { console.log('Page loaded'); });
Listen to Navigation:
javascriptclient.on('Page.frameNavigated', (params) => { console.log('Navigated to:', params.frame.url); });
Get Page Resource Tree:
javascriptconst resourceTree = await client.send('Page.getResourceTree'); console.log('Resource tree:', resourceTree);
7. Practical Use Cases
Use Case 1: Performance Analysis Tool
javascriptasync function analyzePerformance(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); const client = await page.target().createCDPSession(); // Enable performance monitoring await client.send('Performance.enable'); await client.send('Network.enable'); const startTime = Date.now(); await page.goto(url, { waitUntil: 'networkidle2' }); const loadTime = Date.now() - startTime; // Get performance metrics const metrics = await client.send('Performance.getMetrics'); const metricMap = {}; metrics.metrics.forEach(m => metricMap[m.name] = m.value); // Collect network data const networkData = []; client.on('Network.requestWillBeSent', (params) => { networkData.push({ url: params.request.url, method: params.request.method, timestamp: params.timestamp }); }); const report = { url, loadTime, metrics: { layoutDuration: metricMap.LayoutDuration, recalcStyleDuration: metricMap.RecalcStyleDuration, scriptDuration: metricMap.ScriptDuration, taskDuration: metricMap.TaskDuration }, networkRequests: networkData.length }; await browser.close(); return report; } analyzePerformance('https://example.com').then(console.log);
Use Case 2: Network Request Analysis
javascriptasync function analyzeNetworkRequests(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); const client = await page.target().createCDPSession(); await client.send('Network.enable'); const requests = []; client.on('Network.requestWillBeSent', (params) => { requests.push({ requestId: params.requestId, url: params.request.url, method: params.request.method, type: params.type, timestamp: params.timestamp }); }); client.on('Network.responseReceived', (params) => { const request = requests.find(r => r.requestId === params.requestId); if (request) { request.status = params.response.status; request.mimeType = params.response.mimeType; request.size = params.response.encodedDataLength; } }); await page.goto(url, { waitUntil: 'networkidle2' }); // Analyze requests const analysis = { totalRequests: requests.length, byType: {}, byStatus: {}, totalSize: 0 }; requests.forEach(req => { // Count by type if (!analysis.byType[req.type]) { analysis.byType[req.type] = { count: 0, size: 0 }; } analysis.byType[req.type].count++; analysis.byType[req.type].size += req.size || 0; // Count by status if (!analysis.byStatus[req.status]) { analysis.byStatus[req.status] = 0; } analysis.byStatus[req.status]++; analysis.totalSize += req.size || 0; }); await browser.close(); return analysis; } analyzeNetworkRequests('https://example.com').then(console.log);
Use Case 3: Memory Analysis
javascriptasync function analyzeMemory(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); const client = await page.target().createCDPSession(); await client.send('Runtime.enable'); await client.send('HeapProfiler.enable'); await page.goto(url, { waitUntil: 'networkidle2' }); // Get heap snapshot const heapSnapshot = await client.send('HeapProfiler.takeHeapSnapshot', { reportProgress: false }); // Get memory usage const memoryMetrics = await client.send('Runtime.getHeapUsage'); const report = { totalSize: memoryMetrics.totalSize, usedSize: memoryMetrics.usedSize, heapSnapshot: heapSnapshot }; await browser.close(); return report; } analyzeMemory('https://example.com').then(console.log);
Use Case 4: JavaScript Execution Analysis
javascriptasync function analyzeJavaScript(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); const client = await page.target().createCDPSession(); await client.send('Runtime.enable'); await client.send('Debugger.enable'); const consoleLogs = []; const exceptions = []; client.on('Runtime.consoleAPICalled', (params) => { consoleLogs.push({ type: params.type, args: params.args.map(arg => arg.value) }); }); client.on('Runtime.exceptionThrown', (params) => { exceptions.push({ message: params.exceptionDetails.exception?.description, stackTrace: params.exceptionDetails.stackTrace }); }); await page.goto(url, { waitUntil: 'networkidle2' }); const report = { consoleLogs, exceptions, hasErrors: exceptions.length > 0 }; await browser.close(); return report; } analyzeJavaScript('https://example.com').then(console.log);
8. Advanced CDP Features
Code Coverage:
javascriptconst client = await page.target().createCDPSession(); await client.send('DOM.enable'); await client.send('CSS.enable'); // Enable code coverage await client.send('Profiler.enable'); await client.send('Profiler.startPreciseCoverage', { callCount: true, detailed: true }); // Execute operations await page.goto('https://example.com'); // Get coverage data const coverage = await client.send('Profiler.takePreciseCoverage'); console.log('Coverage:', coverage.result);
Monitor Long Tasks:
javascriptconst client = await page.target().createCDPSession(); await client.send('Performance.enable'); client.on('Performance.metrics', (params) => { params.metrics.forEach(metric => { if (metric.name === 'TaskDuration' && metric.value > 50) { console.warn('Long task detected:', metric.value, 'ms'); } }); });
Monitor Layout Shift:
javascriptconst client = await page.target().createCDPSession(); await client.send('Performance.enable'); const layoutShifts = []; client.on('Performance.metrics', (params) => { params.metrics.forEach(metric => { if (metric.name === 'LayoutShift') { layoutShifts.push(metric.value); } }); }); // Calculate cumulative layout shift const cls = layoutShifts.reduce((sum, shift) => sum + shift, 0); console.log('Cumulative Layout Shift:', cls);
9. Best Practices
1. Disable CDP Domains Promptly:
javascripttry { await client.send('Performance.enable'); // Operations } finally { await client.send('Performance.disable'); }
2. Batch Data Retrieval:
javascript// Get multiple metrics at once const [metrics, networkData] = await Promise.all([ client.send('Performance.getMetrics'), client.send('Network.getResponseBody', { requestId: 'xxx' }) ]);
3. Use Event Filtering:
javascriptclient.on('Network.requestWillBeSent', (params) => { // Only process specific requests if (params.request.url.includes('/api/')) { console.log('API Request:', params.request.url); } });
4. Error Handling:
javascripttry { await client.send('Performance.getMetrics'); } catch (error) { console.error('CDP error:', error); // Fallback handling }