乐闻世界logo
搜索文章和话题

How does Puppeteer use Chrome DevTools Protocol (CDP) for advanced debugging and performance analysis?

2月19日 19:40

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:

javascript
const client = await page.target().createCDPSession();

Enable CDP Domains:

javascript
await client.send('Performance.enable'); await client.send('Network.enable'); await client.send('Runtime.enable');

Send CDP Commands:

javascript
const result = await client.send('Performance.getMetrics'); console.log(result);

Listen to CDP Events:

javascript
client.on('Network.requestWillBeSent', (params) => { console.log('Request:', params.request.url); });

2. Performance Monitoring

Enable Performance Monitoring:

javascript
const client = await page.target().createCDPSession(); await client.send('Performance.enable');

Get Performance Metrics:

javascript
const metrics = await client.send('Performance.getMetrics'); console.log('Performance Metrics:', metrics.metrics);

Key Performance Metrics:

javascript
const 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:

javascript
const client = await page.target().createCDPSession(); await client.send('Network.enable');

Monitor Network Requests:

javascript
client.on('Network.requestWillBeSent', (params) => { console.log('Request:', { url: params.request.url, method: params.request.method, type: params.type }); });

Monitor Network Responses:

javascript
client.on('Network.responseReceived', (params) => { console.log('Response:', { url: params.response.url, status: params.response.status, mimeType: params.response.mimeType }); });

Get Request Body:

javascript
client.on('Network.requestWillBeSent', async (params) => { if (params.request.postData) { console.log('Request body:', params.request.postData); } });

Get Response Body:

javascript
client.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:

javascript
const client = await page.target().createCDPSession(); await client.send('Runtime.enable');

Execute JavaScript:

javascript
const result = await client.send('Runtime.evaluate', { expression: 'document.title' }); console.log('Result:', result.result.value);

Get Console Logs:

javascript
client.on('Runtime.consoleAPICalled', (params) => { console.log('Console:', params.type, params.args); });

Listen to Exceptions:

javascript
client.on('Runtime.exceptionThrown', (params) => { console.error('Exception:', params.exceptionDetails); });

5. DOM Monitoring

Enable DOM Monitoring:

javascript
const client = await page.target().createCDPSession(); await client.send('DOM.enable');

Get Document Root Node:

javascript
const root = await client.send('DOM.getDocument'); console.log('Root node:', root.root);

Query Node:

javascript
const result = await client.send('DOM.querySelector', { nodeId: root.root.nodeId, selector: '.my-element' }); console.log('Node:', result.nodeId);

Get Node Attributes:

javascript
const attributes = await client.send('DOM.getAttributes', { nodeId: result.nodeId }); console.log('Attributes:', attributes.attributes);

6. Page Monitoring

Enable Page Monitoring:

javascript
const client = await page.target().createCDPSession(); await client.send('Page.enable');

Listen to Page Load:

javascript
client.on('Page.loadEventFired', () => { console.log('Page loaded'); });

Listen to Navigation:

javascript
client.on('Page.frameNavigated', (params) => { console.log('Navigated to:', params.frame.url); });

Get Page Resource Tree:

javascript
const resourceTree = await client.send('Page.getResourceTree'); console.log('Resource tree:', resourceTree);

7. Practical Use Cases

Use Case 1: Performance Analysis Tool

javascript
async 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

javascript
async 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

javascript
async 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

javascript
async 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:

javascript
const 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:

javascript
const 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:

javascript
const 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:

javascript
try { 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:

javascript
client.on('Network.requestWillBeSent', (params) => { // Only process specific requests if (params.request.url.includes('/api/')) { console.log('API Request:', params.request.url); } });

4. Error Handling:

javascript
try { await client.send('Performance.getMetrics'); } catch (error) { console.error('CDP error:', error); // Fallback handling }
标签:Puppeteer