Puppeteer 提供了多种错误处理和调试技巧,帮助开发者快速定位和解决问题,提高开发效率。
1. 基本错误处理
try-catch 模式:
javascriptconst puppeteer = require('puppeteer'); async function safeExecution() { const browser = await puppeteer.launch(); const page = await browser.newPage(); try { await page.goto('https://example.com'); await page.click('#button'); } catch (error) { console.error('Error occurred:', error.message); // 错误处理逻辑 } finally { await browser.close(); } } safeExecution();
超时处理:
javascripttry { await page.goto('https://example.com', { timeout: 5000 }); } catch (error) { if (error.name === 'TimeoutError') { console.log('Page load timeout'); } }
2. 调试模式
启用调试模式:
javascript// 方法 1:使用 headless: false const browser = await puppeteer.launch({ headless: false, slowMo: 100 // 减慢操作速度 }); // 方法 2:使用 devtools const browser = await puppeteer.launch({ headless: false, devtools: true });
使用 slowMo:
javascriptconst browser = await puppeteer.launch({ headless: false, slowMo: 50 // 每个操作延迟 50ms });
3. 日志记录
控制台日志:
javascriptpage.on('console', msg => { console.log('Browser console:', msg.text()); }); // 捕获不同类型的日志 page.on('console', msg => { const type = msg.type(); const text = msg.text(); if (type === 'error') { console.error('Browser error:', text); } else if (type === 'warning') { console.warn('Browser warning:', text); } else { console.log('Browser log:', text); } });
页面错误日志:
javascriptpage.on('pageerror', error => { console.error('Page error:', error.message); });
请求失败日志:
javascriptpage.on('requestfailed', request => { console.log('Request failed:', request.url()); console.log('Failure:', request.failure()); });
4. 截图和视频录制
错误时截图:
javascriptasync function withErrorScreenshot(page, operation) { try { await operation(); } catch (error) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); await page.screenshot({ path: `error-${timestamp}.png`, fullPage: true }); throw error; } } // 使用示例 await withErrorScreenshot(page, async () => { await page.goto('https://example.com'); await page.click('#button'); });
视频录制:
javascriptconst { spawn } = require('child_process'); async function recordVideo(page, outputPath, operation) { // 使用 ffmpeg 录制屏幕 const ffmpeg = spawn('ffmpeg', [ '-f', 'x11grab', '-r', '30', '-s', '1920x1080', '-i', ':99', '-c:v', 'libx264', '-preset', 'ultrafast', outputPath ]); try { await operation(); } finally { ffmpeg.kill('SIGINT'); } }
5. 网络调试
监控网络请求:
javascriptpage.on('request', request => { console.log('Request:', request.url()); }); page.on('response', response => { console.log('Response:', response.url(), response.status()); }); page.on('requestfinished', request => { console.log('Request finished:', request.url()); });
捕获请求和响应数据:
javascriptconst requests = []; page.on('request', request => { requests.push({ url: request.url(), method: request.method(), headers: request.headers() }); }); page.on('response', async response => { const request = requests.find(r => r.url === response.url()); if (request) { request.status = response.status(); request.headers = response.headers(); try { request.body = await response.text(); } catch (error) { request.body = null; } } });
6. 性能追踪
启用性能追踪:
javascriptconst client = await page.target().createCDPSession(); await client.send('Performance.enable'); await client.send('Network.enable'); // 获取性能指标 const metrics = await client.send('Performance.getMetrics'); console.log('Performance metrics:', metrics);
追踪时间线:
javascriptawait page.tracing.start({ path: 'trace.json' }); // 执行操作 await page.goto('https://example.com'); await page.tracing.stop();
7. 元素调试
高亮元素:
javascriptasync function highlightElement(page, selector) { await page.evaluate(selector => { const element = document.querySelector(selector); if (element) { element.style.border = '3px solid red'; element.style.backgroundColor = 'yellow'; } }, selector); }
检查元素状态:
javascriptasync function checkElement(page, selector) { const isVisible = await page.isVisible(selector); const isEnabled = await page.isDisabled(selector); const isClickable = await page.isClickable(selector); console.log('Element state:', { selector, isVisible, isEnabled, isClickable }); }
获取元素位置:
javascriptconst position = await page.evaluate(selector => { const element = document.querySelector(selector); if (element) { const rect = element.getBoundingClientRect(); return { x: rect.left, y: rect.top, width: rect.width, height: rect.height }; } }, '.element');
8. 调试工具函数
等待并调试:
javascriptasync function waitForAndDebug(page, selector, options = {}) { console.log(`Waiting for selector: ${selector}`); try { await page.waitForSelector(selector, { timeout: options.timeout || 30000, visible: options.visible !== false }); console.log(`Found selector: ${selector}`); } catch (error) { console.error(`Failed to find selector: ${selector}`); await page.screenshot({ path: 'debug-failed.png' }); throw error; } }
点击并调试:
javascriptasync function clickAndDebug(page, selector) { console.log(`Attempting to click: ${selector}`); try { // 检查元素是否存在 const element = await page.$(selector); if (!element) { throw new Error(`Element not found: ${selector}`); } // 检查元素是否可见 const isVisible = await element.isIntersectingViewport(); if (!isVisible) { console.warn('Element is not visible, scrolling to it'); await element.scrollIntoView(); } await element.click(); console.log(`Successfully clicked: ${selector}`); } catch (error) { console.error(`Failed to click: ${selector}`, error); await page.screenshot({ path: 'debug-click-failed.png' }); throw error; } }
9. 常见错误及解决方案
错误 1:元素未找到
javascript// 问题:元素选择器错误 await page.click('.wrong-selector'); // 解决方案:使用正确的选择器 await page.click('.correct-selector'); // 或者等待元素出现 await page.waitForSelector('.correct-selector'); await page.click('.correct-selector');
错误 2:元素不可点击
javascript// 问题:元素被遮挡或不可见 await page.click('.hidden-button'); // 解决方案:滚动到元素 await page.evaluate(selector => { document.querySelector(selector).scrollIntoView(); }, '.hidden-button'); await page.click('.hidden-button');
错误 3:超时错误
javascript// 问题:页面加载超时 await page.goto('https://slow-website.com'); // 解决方案:增加超时时间 await page.goto('https://slow-website.com', { timeout: 60000 }); // 或使用更宽松的等待条件 await page.goto('https://slow-website.com', { waitUntil: 'domcontentloaded' });
错误 4:内存泄漏
javascript// 问题:未关闭浏览器实例 const browser = await puppeteer.launch(); // 忘记关闭 // 解决方案:使用 finally 确保关闭 const browser = await puppeteer.launch(); try { // 操作 } finally { await browser.close(); }
10. 调试最佳实践
1. 使用描述性日志:
javascriptconsole.log(`[INFO] Navigating to ${url}`); console.log(`[DEBUG] Found ${elements.length} elements`); console.log(`[ERROR] Failed to click button: ${error.message}`);
2. 保存调试信息:
javascriptconst debugInfo = { url: page.url(), timestamp: new Date().toISOString(), screenshot: await page.screenshot({ encoding: 'base64' }), html: await page.content(), cookies: await page.cookies() }; require('fs').writeFileSync('debug.json', JSON.stringify(debugInfo, null, 2));
3. 使用条件断点:
javascriptawait page.evaluate(() => { debugger; // 在浏览器中暂停 });
4. 分步调试:
javascript// 使用 slowMo 减慢操作 const browser = await puppeteer.launch({ slowMo: 100 }); // 或在关键步骤添加延迟 await new Promise(resolve => setTimeout(resolve, 1000));
5. 使用调试器:
javascript// 在代码中添加 debugger debugger; // 使用 Node.js 调试器运行 node --inspect-brk script.js
11. 测试和验证
单元测试示例:
javascriptconst assert = require('assert'); async function testPageLoad() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com'); const title = await page.title(); assert.strictEqual(title, 'Example Domain'); await browser.close(); } testPageLoad().catch(console.error);
集成测试示例:
javascriptasync function testUserFlow() { const browser = await puppeteer.launch(); const page = await browser.newPage(); // 测试登录流程 await page.goto('https://example.com/login'); await page.type('#username', 'testuser'); await page.type('#password', 'password'); await page.click('#login-button'); // 验证登录成功 await page.waitForSelector('.user-profile'); const isLoggedIn = await page.$('.user-profile') !== null; assert(isLoggedIn, 'Login failed'); await browser.close(); } testUserFlow().catch(console.error);