Error handling is essential for building robust Puppeteer applications. This guide covers all major error types and handling strategies you'll encounter when automating browsers.
Basic Error Handling with Try/Catch
All Puppeteer operations are asynchronous and return promises. Use try/catch
blocks to handle errors gracefully:
const puppeteer = require('puppeteer');
(async () => {
let browser;
try {
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});
} catch (error) {
console.error('Error occurred:', error.message);
console.error('Stack trace:', error.stack);
} finally {
// Always close browser to prevent resource leaks
if (browser) await browser.close();
}
})();
Handling Timeout Errors
Timeout errors are common in web automation. Handle them by adjusting timeouts or implementing retry logic:
const puppeteer = require('puppeteer');
(async () => {
let browser;
try {
browser = await puppeteer.launch();
const page = await browser.newPage();
// Configure timeouts
page.setDefaultNavigationTimeout(30000); // 30 seconds
page.setDefaultTimeout(10000); // 10 seconds for other operations
await page.goto('https://slow-loading-site.com');
// Wait for specific element with custom timeout
await page.waitForSelector('#content', { timeout: 5000 });
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('Operation timed out:', error.message);
// Implement retry logic here
} else {
console.error('Other error:', error.message);
}
} finally {
if (browser) await browser.close();
}
})();
Network Error Handling
Monitor and handle network failures, failed requests, and connection issues:
const puppeteer = require('puppeteer');
(async () => {
let browser;
try {
browser = await puppeteer.launch();
const page = await browser.newPage();
// Track failed requests
const failedRequests = [];
page.on('requestfailed', request => {
failedRequests.push({
url: request.url(),
errorText: request.failure().errorText
});
console.error(`Request failed: ${request.url()} - ${request.failure().errorText}`);
});
// Handle page errors
page.on('pageerror', error => {
console.error('Page error:', error.message);
});
// Handle console errors from the page
page.on('console', msg => {
if (msg.type() === 'error') {
console.error('Browser console error:', msg.text());
}
});
await page.goto('https://example.com');
// Check if critical requests failed
if (failedRequests.length > 0) {
console.warn(`${failedRequests.length} requests failed`);
}
} finally {
if (browser) await browser.close();
}
})();
Browser Crash Handling
Handle browser crashes and disconnections gracefully:
const puppeteer = require('puppeteer');
(async () => {
let browser;
try {
browser = await puppeteer.launch();
// Handle browser disconnection
browser.on('disconnected', () => {
console.error('Browser disconnected unexpectedly');
});
const page = await browser.newPage();
await page.goto('https://example.com');
} catch (error) {
if (error.message.includes('Target closed') || error.message.includes('Protocol error')) {
console.error('Browser crashed or was closed:', error.message);
} else {
console.error('Unexpected error:', error.message);
}
} finally {
try {
if (browser && browser.isConnected()) {
await browser.close();
}
} catch (closeError) {
console.error('Error closing browser:', closeError.message);
}
}
})();
Retry Logic Implementation
Implement robust retry mechanisms for handling transient failures:
const puppeteer = require('puppeteer');
async function retryOperation(operation, maxRetries = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error.message);
if (attempt === maxRetries) {
throw new Error(`Operation failed after ${maxRetries} attempts: ${error.message}`);
}
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delay * attempt));
}
}
}
(async () => {
let browser;
try {
browser = await puppeteer.launch();
await retryOperation(async () => {
const page = await browser.newPage();
await page.goto('https://unreliable-site.com');
const title = await page.title();
console.log('Page title:', title);
await page.close();
});
} catch (error) {
console.error('All retry attempts failed:', error.message);
} finally {
if (browser) await browser.close();
}
})();
Element Interaction Error Handling
Handle errors when interacting with page elements:
const puppeteer = require('puppeteer');
(async () => {
let browser;
try {
browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Safe element interaction
try {
await page.waitForSelector('#submit-button', { timeout: 5000 });
await page.click('#submit-button');
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('Submit button not found within timeout');
// Try alternative selector or action
const alternativeButton = await page.$('#alt-submit');
if (alternativeButton) {
await alternativeButton.click();
}
} else {
throw error; // Re-throw unexpected errors
}
}
// Safe text extraction with fallback
const getText = async (selector, fallback = 'N/A') => {
try {
return await page.$eval(selector, el => el.textContent.trim());
} catch (error) {
console.warn(`Could not extract text from ${selector}:`, error.message);
return fallback;
}
};
const title = await getText('h1', 'No title found');
console.log('Page title:', title);
} finally {
if (browser) await browser.close();
}
})();
Comprehensive Error Handling Pattern
Here's a complete example combining all error handling strategies:
const puppeteer = require('puppeteer');
class PuppeteerErrorHandler {
constructor(options = {}) {
this.maxRetries = options.maxRetries || 3;
this.timeout = options.timeout || 30000;
this.retryDelay = options.retryDelay || 1000;
}
async withErrorHandling(operation) {
let browser;
let attempt = 0;
while (attempt < this.maxRetries) {
try {
browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
browser.on('disconnected', () => {
console.warn('Browser disconnected');
});
const result = await operation(browser);
return result;
} catch (error) {
attempt++;
console.error(`Attempt ${attempt} failed:`, error.message);
if (this.isRetryableError(error) && attempt < this.maxRetries) {
console.log(`Retrying in ${this.retryDelay * attempt}ms...`);
await this.delay(this.retryDelay * attempt);
} else {
throw error;
}
} finally {
if (browser) {
try {
await browser.close();
} catch (closeError) {
console.error('Error closing browser:', closeError.message);
}
}
}
}
}
isRetryableError(error) {
const retryableErrors = [
'TimeoutError',
'net::ERR_NETWORK_CHANGED',
'net::ERR_CONNECTION_RESET',
'net::ERR_INTERNET_DISCONNECTED'
];
return retryableErrors.some(errType =>
error.name === errType || error.message.includes(errType)
);
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage example
(async () => {
const errorHandler = new PuppeteerErrorHandler({
maxRetries: 3,
timeout: 30000,
retryDelay: 2000
});
try {
const result = await errorHandler.withErrorHandling(async (browser) => {
const page = await browser.newPage();
page.setDefaultTimeout(30000);
await page.goto('https://example.com');
const title = await page.title();
return { title, success: true };
});
console.log('Operation successful:', result);
} catch (error) {
console.error('Final error after all retries:', error.message);
}
})();
Best Practices Summary
- Always use try/catch blocks around Puppeteer operations
- Implement proper cleanup in finally blocks to prevent resource leaks
- Set appropriate timeouts for different operations
- Monitor browser events for disconnections and crashes
- Implement retry logic for transient failures
- Log errors appropriately for debugging and monitoring
- Handle element interaction failures with fallback strategies
- Use specific error types to determine retry vs. fail-fast behavior
By implementing comprehensive error handling, your Puppeteer applications will be more reliable and maintainable in production environments.