954 lines
19 KiB
Markdown
954 lines
19 KiB
Markdown
# Puppeteer Quick Reference
|
|
|
|
Complete guide to browser automation with Puppeteer - a high-level API over Chrome DevTools Protocol.
|
|
|
|
## Table of Contents
|
|
|
|
- [Setup](#setup)
|
|
- [Browser & Page Management](#browser--page-management)
|
|
- [Navigation](#navigation)
|
|
- [Element Interaction](#element-interaction)
|
|
- [JavaScript Execution](#javascript-execution)
|
|
- [Screenshots & PDFs](#screenshots--pdfs)
|
|
- [Network Interception](#network-interception)
|
|
- [Device Emulation](#device-emulation)
|
|
- [Performance](#performance)
|
|
- [Common Patterns](#common-patterns)
|
|
|
|
---
|
|
|
|
## Setup
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
# Install Puppeteer
|
|
npm install puppeteer
|
|
|
|
# Install core only (bring your own Chrome)
|
|
npm install puppeteer-core
|
|
```
|
|
|
|
### Basic Usage
|
|
|
|
```javascript
|
|
import puppeteer from 'puppeteer';
|
|
|
|
// Launch browser
|
|
const browser = await puppeteer.launch({
|
|
headless: true,
|
|
args: ['--no-sandbox']
|
|
});
|
|
|
|
// Open page
|
|
const page = await browser.newPage();
|
|
|
|
// Navigate
|
|
await page.goto('https://example.com');
|
|
|
|
// Do work...
|
|
|
|
// Cleanup
|
|
await browser.close();
|
|
```
|
|
|
|
---
|
|
|
|
## Browser & Page Management
|
|
|
|
### Launch Browser
|
|
|
|
```javascript
|
|
const browser = await puppeteer.launch({
|
|
// Visibility
|
|
headless: false, // Show browser UI
|
|
headless: 'new', // New headless mode (Chrome 112+)
|
|
|
|
// Chrome location
|
|
executablePath: '/path/to/chrome',
|
|
channel: 'chrome', // or 'chrome-canary', 'chrome-beta'
|
|
|
|
// Browser context
|
|
userDataDir: './user-data', // Persistent profile
|
|
|
|
// Window size
|
|
defaultViewport: {
|
|
width: 1920,
|
|
height: 1080,
|
|
deviceScaleFactor: 1,
|
|
isMobile: false
|
|
},
|
|
|
|
// Advanced options
|
|
args: [
|
|
'--no-sandbox',
|
|
'--disable-setuid-sandbox',
|
|
'--disable-dev-shm-usage',
|
|
'--disable-web-security',
|
|
'--disable-features=IsolateOrigins',
|
|
'--disable-site-isolation-trials',
|
|
'--start-maximized'
|
|
],
|
|
|
|
// Debugging
|
|
devtools: true, // Open DevTools automatically
|
|
slowMo: 250, // Slow down by 250ms per action
|
|
|
|
// Network
|
|
proxy: {
|
|
server: 'http://proxy.com:8080'
|
|
}
|
|
});
|
|
```
|
|
|
|
### Connect to Running Browser
|
|
|
|
```javascript
|
|
// Launch Chrome with debugging
|
|
// google-chrome --remote-debugging-port=9222
|
|
|
|
const browser = await puppeteer.connect({
|
|
browserURL: 'http://localhost:9222',
|
|
// or browserWSEndpoint: 'ws://localhost:9222/devtools/browser/...'
|
|
});
|
|
```
|
|
|
|
### Page Management
|
|
|
|
```javascript
|
|
// Create new page
|
|
const page = await browser.newPage();
|
|
|
|
// Get all pages
|
|
const pages = await browser.pages();
|
|
|
|
// Close page
|
|
await page.close();
|
|
|
|
// Multiple pages
|
|
const page1 = await browser.newPage();
|
|
const page2 = await browser.newPage();
|
|
|
|
// Switch between pages
|
|
await page1.bringToFront();
|
|
```
|
|
|
|
### Browser Context (Incognito)
|
|
|
|
```javascript
|
|
// Create isolated context
|
|
const context = await browser.createBrowserContext();
|
|
const page = await context.newPage();
|
|
|
|
// Cleanup context
|
|
await context.close();
|
|
```
|
|
|
|
---
|
|
|
|
## Navigation
|
|
|
|
### Basic Navigation
|
|
|
|
```javascript
|
|
// Navigate to URL
|
|
await page.goto('https://example.com');
|
|
|
|
// Navigate with options
|
|
await page.goto('https://example.com', {
|
|
waitUntil: 'networkidle2', // or 'load', 'domcontentloaded', 'networkidle0'
|
|
timeout: 30000 // Max wait time (ms)
|
|
});
|
|
|
|
// Reload page
|
|
await page.reload({ waitUntil: 'networkidle2' });
|
|
|
|
// Navigation history
|
|
await page.goBack();
|
|
await page.goForward();
|
|
|
|
// Wait for navigation
|
|
await page.waitForNavigation({
|
|
waitUntil: 'networkidle2'
|
|
});
|
|
```
|
|
|
|
### Wait Until Options
|
|
|
|
- `load` - Wait for load event
|
|
- `domcontentloaded` - Wait for DOMContentLoaded event
|
|
- `networkidle0` - Wait until no network connections for 500ms
|
|
- `networkidle2` - Wait until max 2 network connections for 500ms
|
|
|
|
---
|
|
|
|
## Element Interaction
|
|
|
|
### Selectors
|
|
|
|
```javascript
|
|
// CSS selectors
|
|
await page.$('#id');
|
|
await page.$('.class');
|
|
await page.$('div > p');
|
|
|
|
// XPath
|
|
await page.$x('//button[text()="Submit"]');
|
|
|
|
// Get all matching elements
|
|
await page.$$('.item');
|
|
await page.$$x('//div[@class="item"]');
|
|
```
|
|
|
|
### Click Elements
|
|
|
|
```javascript
|
|
// Click by selector
|
|
await page.click('.button');
|
|
|
|
// Click with options
|
|
await page.click('.button', {
|
|
button: 'left', // or 'right', 'middle'
|
|
clickCount: 1, // 2 for double-click
|
|
delay: 100 // Delay between mousedown and mouseup
|
|
});
|
|
|
|
// ElementHandle click
|
|
const button = await page.$('.button');
|
|
await button.click();
|
|
```
|
|
|
|
### Type Text
|
|
|
|
```javascript
|
|
// Type into input
|
|
await page.type('#search', 'query text');
|
|
|
|
// Type with delay
|
|
await page.type('#search', 'slow typing', { delay: 100 });
|
|
|
|
// Clear and type
|
|
await page.$eval('#search', el => el.value = '');
|
|
await page.type('#search', 'new text');
|
|
```
|
|
|
|
### Form Interaction
|
|
|
|
```javascript
|
|
// Fill input
|
|
await page.type('#username', 'john@example.com');
|
|
await page.type('#password', 'secret123');
|
|
|
|
// Select dropdown option
|
|
await page.select('#country', 'US'); // By value
|
|
await page.select('#country', 'USA', 'UK'); // Multiple
|
|
|
|
// Check/uncheck checkbox
|
|
await page.click('input[type="checkbox"]');
|
|
|
|
// Choose radio button
|
|
await page.click('input[value="option2"]');
|
|
|
|
// Upload file
|
|
const input = await page.$('input[type="file"]');
|
|
await input.uploadFile('/path/to/file.pdf');
|
|
|
|
// Submit form
|
|
await page.click('button[type="submit"]');
|
|
await page.waitForNavigation();
|
|
```
|
|
|
|
### Hover & Focus
|
|
|
|
```javascript
|
|
// Hover over element
|
|
await page.hover('.menu-item');
|
|
|
|
// Focus element
|
|
await page.focus('#input');
|
|
|
|
// Blur
|
|
await page.$eval('#input', el => el.blur());
|
|
```
|
|
|
|
### Drag & Drop
|
|
|
|
```javascript
|
|
const source = await page.$('.draggable');
|
|
const target = await page.$('.drop-zone');
|
|
|
|
await source.drag(target);
|
|
await source.drop(target);
|
|
```
|
|
|
|
---
|
|
|
|
## JavaScript Execution
|
|
|
|
### Evaluate in Page Context
|
|
|
|
```javascript
|
|
// Execute JavaScript
|
|
const title = await page.evaluate(() => document.title);
|
|
|
|
// With arguments
|
|
const text = await page.evaluate(
|
|
(selector) => document.querySelector(selector).textContent,
|
|
'.heading'
|
|
);
|
|
|
|
// Return complex data
|
|
const data = await page.evaluate(() => ({
|
|
title: document.title,
|
|
url: location.href,
|
|
cookies: document.cookie
|
|
}));
|
|
|
|
// With ElementHandle
|
|
const element = await page.$('.button');
|
|
const text = await page.evaluate(el => el.textContent, element);
|
|
```
|
|
|
|
### Query & Modify DOM
|
|
|
|
```javascript
|
|
// Get element property
|
|
const value = await page.$eval('#input', el => el.value);
|
|
|
|
// Get multiple elements
|
|
const items = await page.$$eval('.item', elements =>
|
|
elements.map(el => el.textContent)
|
|
);
|
|
|
|
// Modify element
|
|
await page.$eval('#input', (el, value) => {
|
|
el.value = value;
|
|
}, 'new value');
|
|
|
|
// Add class
|
|
await page.$eval('.element', el => el.classList.add('active'));
|
|
```
|
|
|
|
### Expose Functions
|
|
|
|
```javascript
|
|
// Expose Node.js function to page
|
|
await page.exposeFunction('md5', (text) =>
|
|
crypto.createHash('md5').update(text).digest('hex')
|
|
);
|
|
|
|
// Call from page context
|
|
const hash = await page.evaluate(async () => {
|
|
return await window.md5('hello world');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Screenshots & PDFs
|
|
|
|
### Screenshots
|
|
|
|
```javascript
|
|
// Full page screenshot
|
|
await page.screenshot({
|
|
path: 'screenshot.png',
|
|
fullPage: true
|
|
});
|
|
|
|
// Viewport screenshot
|
|
await page.screenshot({
|
|
path: 'viewport.png',
|
|
fullPage: false
|
|
});
|
|
|
|
// Element screenshot
|
|
const element = await page.$('.chart');
|
|
await element.screenshot({
|
|
path: 'chart.png'
|
|
});
|
|
|
|
// Screenshot options
|
|
await page.screenshot({
|
|
path: 'page.png',
|
|
type: 'png', // or 'jpeg', 'webp'
|
|
quality: 80, // JPEG quality (0-100)
|
|
clip: { // Crop region
|
|
x: 0,
|
|
y: 0,
|
|
width: 500,
|
|
height: 500
|
|
},
|
|
omitBackground: true // Transparent background
|
|
});
|
|
|
|
// Screenshot to buffer
|
|
const buffer = await page.screenshot();
|
|
```
|
|
|
|
### PDF Generation
|
|
|
|
```javascript
|
|
// Generate PDF
|
|
await page.pdf({
|
|
path: 'page.pdf',
|
|
format: 'A4', // or 'Letter', 'Legal', etc.
|
|
printBackground: true,
|
|
margin: {
|
|
top: '1cm',
|
|
right: '1cm',
|
|
bottom: '1cm',
|
|
left: '1cm'
|
|
}
|
|
});
|
|
|
|
// Custom page size
|
|
await page.pdf({
|
|
path: 'custom.pdf',
|
|
width: '8.5in',
|
|
height: '11in',
|
|
landscape: true
|
|
});
|
|
|
|
// Header and footer
|
|
await page.pdf({
|
|
path: 'report.pdf',
|
|
displayHeaderFooter: true,
|
|
headerTemplate: '<div style="font-size:10px;">Header</div>',
|
|
footerTemplate: '<div style="font-size:10px;">Page <span class="pageNumber"></span></div>'
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Network Interception
|
|
|
|
### Request Interception
|
|
|
|
```javascript
|
|
// Enable request interception
|
|
await page.setRequestInterception(true);
|
|
|
|
// Intercept requests
|
|
page.on('request', (request) => {
|
|
// Block specific resource types
|
|
if (request.resourceType() === 'image') {
|
|
request.abort();
|
|
}
|
|
// Block URLs
|
|
else if (request.url().includes('ads')) {
|
|
request.abort();
|
|
}
|
|
// Modify request
|
|
else if (request.url().includes('api')) {
|
|
request.continue({
|
|
headers: {
|
|
...request.headers(),
|
|
'Authorization': 'Bearer token'
|
|
}
|
|
});
|
|
}
|
|
// Continue normally
|
|
else {
|
|
request.continue();
|
|
}
|
|
});
|
|
```
|
|
|
|
### Mock Responses
|
|
|
|
```javascript
|
|
await page.setRequestInterception(true);
|
|
|
|
page.on('request', (request) => {
|
|
if (request.url().includes('/api/user')) {
|
|
request.respond({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
id: 1,
|
|
name: 'Mock User'
|
|
})
|
|
});
|
|
} else {
|
|
request.continue();
|
|
}
|
|
});
|
|
```
|
|
|
|
### Monitor Network
|
|
|
|
```javascript
|
|
// Track requests
|
|
page.on('request', (request) => {
|
|
console.log('Request:', request.method(), request.url());
|
|
});
|
|
|
|
// Track responses
|
|
page.on('response', (response) => {
|
|
console.log('Response:', response.status(), response.url());
|
|
});
|
|
|
|
// Track failed requests
|
|
page.on('requestfailed', (request) => {
|
|
console.log('Failed:', request.failure().errorText, request.url());
|
|
});
|
|
|
|
// Get response body
|
|
page.on('response', async (response) => {
|
|
if (response.url().includes('/api/data')) {
|
|
const json = await response.json();
|
|
console.log('API Data:', json);
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Device Emulation
|
|
|
|
### Predefined Devices
|
|
|
|
```javascript
|
|
import { devices } from 'puppeteer';
|
|
|
|
// Emulate iPhone
|
|
const iPhone = devices['iPhone 13 Pro'];
|
|
await page.emulate(iPhone);
|
|
|
|
// Common devices
|
|
const iPad = devices['iPad Pro'];
|
|
const pixel = devices['Pixel 5'];
|
|
const galaxy = devices['Galaxy S9+'];
|
|
|
|
// Navigate after emulation
|
|
await page.goto('https://example.com');
|
|
```
|
|
|
|
### Custom Device
|
|
|
|
```javascript
|
|
await page.emulate({
|
|
viewport: {
|
|
width: 375,
|
|
height: 812,
|
|
deviceScaleFactor: 3,
|
|
isMobile: true,
|
|
hasTouch: true,
|
|
isLandscape: false
|
|
},
|
|
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)...'
|
|
});
|
|
```
|
|
|
|
### Viewport Only
|
|
|
|
```javascript
|
|
await page.setViewport({
|
|
width: 1920,
|
|
height: 1080,
|
|
deviceScaleFactor: 1
|
|
});
|
|
```
|
|
|
|
### Geolocation
|
|
|
|
```javascript
|
|
// Set geolocation
|
|
await page.setGeolocation({
|
|
latitude: 37.7749,
|
|
longitude: -122.4194,
|
|
accuracy: 100
|
|
});
|
|
|
|
// Grant permissions
|
|
const context = browser.defaultBrowserContext();
|
|
await context.overridePermissions('https://example.com', ['geolocation']);
|
|
```
|
|
|
|
### Timezone & Locale
|
|
|
|
```javascript
|
|
// Set timezone
|
|
await page.emulateTimezone('America/New_York');
|
|
|
|
// Set locale
|
|
await page.emulateMediaType('screen');
|
|
await page.evaluateOnNewDocument(() => {
|
|
Object.defineProperty(navigator, 'language', {
|
|
get: () => 'en-US'
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Performance
|
|
|
|
### CPU & Network Throttling
|
|
|
|
```javascript
|
|
// CPU throttling
|
|
const client = await page.createCDPSession();
|
|
await client.send('Emulation.setCPUThrottlingRate', { rate: 4 });
|
|
|
|
// Network throttling
|
|
await page.emulateNetworkConditions({
|
|
offline: false,
|
|
downloadThroughput: 1.5 * 1024 * 1024 / 8, // 1.5 Mbps
|
|
uploadThroughput: 750 * 1024 / 8, // 750 Kbps
|
|
latency: 40 // 40ms RTT
|
|
});
|
|
|
|
// Predefined profiles
|
|
await page.emulateNetworkConditions(
|
|
puppeteer.networkConditions['Fast 3G']
|
|
);
|
|
|
|
// Disable throttling
|
|
await page.emulateNetworkConditions({
|
|
offline: false,
|
|
downloadThroughput: -1,
|
|
uploadThroughput: -1,
|
|
latency: 0
|
|
});
|
|
```
|
|
|
|
### Performance Metrics
|
|
|
|
```javascript
|
|
// Get metrics
|
|
const metrics = await page.metrics();
|
|
console.log(metrics);
|
|
// {
|
|
// Timestamp, Documents, Frames, JSEventListeners,
|
|
// Nodes, LayoutCount, RecalcStyleCount,
|
|
// LayoutDuration, RecalcStyleDuration,
|
|
// ScriptDuration, TaskDuration,
|
|
// JSHeapUsedSize, JSHeapTotalSize
|
|
// }
|
|
```
|
|
|
|
### Performance Tracing
|
|
|
|
```javascript
|
|
// Start tracing
|
|
await page.tracing.start({
|
|
path: 'trace.json',
|
|
categories: [
|
|
'devtools.timeline',
|
|
'disabled-by-default-devtools.timeline'
|
|
]
|
|
});
|
|
|
|
// Navigate
|
|
await page.goto('https://example.com');
|
|
|
|
// Stop tracing
|
|
await page.tracing.stop();
|
|
|
|
// Analyze trace in chrome://tracing
|
|
```
|
|
|
|
### Coverage (Code Usage)
|
|
|
|
```javascript
|
|
// Start JS coverage
|
|
await page.coverage.startJSCoverage();
|
|
|
|
// Start CSS coverage
|
|
await page.coverage.startCSSCoverage();
|
|
|
|
// Navigate
|
|
await page.goto('https://example.com');
|
|
|
|
// Stop and get coverage
|
|
const jsCoverage = await page.coverage.stopJSCoverage();
|
|
const cssCoverage = await page.coverage.stopCSSCoverage();
|
|
|
|
// Calculate unused bytes
|
|
let totalBytes = 0;
|
|
let usedBytes = 0;
|
|
for (const entry of [...jsCoverage, ...cssCoverage]) {
|
|
totalBytes += entry.text.length;
|
|
for (const range of entry.ranges) {
|
|
usedBytes += range.end - range.start - 1;
|
|
}
|
|
}
|
|
|
|
console.log(`Used: ${usedBytes / totalBytes * 100}%`);
|
|
```
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Wait for Elements
|
|
|
|
```javascript
|
|
// Wait for selector
|
|
await page.waitForSelector('.element', {
|
|
visible: true,
|
|
timeout: 5000
|
|
});
|
|
|
|
// Wait for XPath
|
|
await page.waitForXPath('//button[text()="Submit"]');
|
|
|
|
// Wait for function
|
|
await page.waitForFunction(
|
|
() => document.querySelector('.loading') === null,
|
|
{ timeout: 10000 }
|
|
);
|
|
|
|
// Wait for timeout
|
|
await page.waitForTimeout(2000);
|
|
```
|
|
|
|
### Handle Dialogs
|
|
|
|
```javascript
|
|
// Alert, confirm, prompt
|
|
page.on('dialog', async (dialog) => {
|
|
console.log(dialog.type(), dialog.message());
|
|
|
|
// Accept
|
|
await dialog.accept();
|
|
// or reject
|
|
// await dialog.dismiss();
|
|
// or provide input for prompt
|
|
// await dialog.accept('input text');
|
|
});
|
|
```
|
|
|
|
### Handle Downloads
|
|
|
|
```javascript
|
|
// Set download path
|
|
const client = await page.createCDPSession();
|
|
await client.send('Page.setDownloadBehavior', {
|
|
behavior: 'allow',
|
|
downloadPath: '/path/to/downloads'
|
|
});
|
|
|
|
// Trigger download
|
|
await page.click('a[download]');
|
|
```
|
|
|
|
### Multiple Pages (Tabs)
|
|
|
|
```javascript
|
|
// Listen for new pages
|
|
browser.on('targetcreated', async (target) => {
|
|
if (target.type() === 'page') {
|
|
const newPage = await target.page();
|
|
console.log('New page opened:', newPage.url());
|
|
}
|
|
});
|
|
|
|
// Click link that opens new tab
|
|
const [newPage] = await Promise.all([
|
|
new Promise(resolve => browser.once('targetcreated', target => resolve(target.page()))),
|
|
page.click('a[target="_blank"]')
|
|
]);
|
|
|
|
console.log('New page URL:', newPage.url());
|
|
```
|
|
|
|
### Frames (iframes)
|
|
|
|
```javascript
|
|
// Get all frames
|
|
const frames = page.frames();
|
|
|
|
// Find frame by name
|
|
const frame = page.frames().find(f => f.name() === 'myframe');
|
|
|
|
// Find frame by URL
|
|
const frame = page.frames().find(f => f.url().includes('example.com'));
|
|
|
|
// Main frame
|
|
const mainFrame = page.mainFrame();
|
|
|
|
// Interact with frame
|
|
await frame.click('.button');
|
|
await frame.type('#input', 'text');
|
|
```
|
|
|
|
### Infinite Scroll
|
|
|
|
```javascript
|
|
async function autoScroll(page) {
|
|
await page.evaluate(async () => {
|
|
await new Promise((resolve) => {
|
|
let totalHeight = 0;
|
|
const distance = 100;
|
|
const timer = setInterval(() => {
|
|
const scrollHeight = document.body.scrollHeight;
|
|
window.scrollBy(0, distance);
|
|
totalHeight += distance;
|
|
|
|
if (totalHeight >= scrollHeight) {
|
|
clearInterval(timer);
|
|
resolve();
|
|
}
|
|
}, 100);
|
|
});
|
|
});
|
|
}
|
|
|
|
await autoScroll(page);
|
|
```
|
|
|
|
### Cookies
|
|
|
|
```javascript
|
|
// Get cookies
|
|
const cookies = await page.cookies();
|
|
|
|
// Set cookies
|
|
await page.setCookie({
|
|
name: 'session',
|
|
value: 'abc123',
|
|
domain: 'example.com',
|
|
path: '/',
|
|
httpOnly: true,
|
|
secure: true,
|
|
sameSite: 'Strict'
|
|
});
|
|
|
|
// Delete cookies
|
|
await page.deleteCookie({ name: 'session' });
|
|
```
|
|
|
|
### Local Storage
|
|
|
|
```javascript
|
|
// Set localStorage
|
|
await page.evaluate(() => {
|
|
localStorage.setItem('key', 'value');
|
|
});
|
|
|
|
// Get localStorage
|
|
const value = await page.evaluate(() => {
|
|
return localStorage.getItem('key');
|
|
});
|
|
|
|
// Clear localStorage
|
|
await page.evaluate(() => localStorage.clear());
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
```javascript
|
|
try {
|
|
await page.goto('https://example.com', {
|
|
waitUntil: 'networkidle2',
|
|
timeout: 30000
|
|
});
|
|
} catch (error) {
|
|
if (error.name === 'TimeoutError') {
|
|
console.error('Page load timeout');
|
|
} else {
|
|
console.error('Navigation failed:', error);
|
|
}
|
|
|
|
// Take screenshot on error
|
|
await page.screenshot({ path: 'error.png' });
|
|
}
|
|
```
|
|
|
|
### Stealth Mode (Avoid Detection)
|
|
|
|
```javascript
|
|
// Hide automation indicators
|
|
await page.evaluateOnNewDocument(() => {
|
|
// Override navigator.webdriver
|
|
Object.defineProperty(navigator, 'webdriver', {
|
|
get: () => false
|
|
});
|
|
|
|
// Mock chrome object
|
|
window.chrome = {
|
|
runtime: {}
|
|
};
|
|
|
|
// Mock permissions
|
|
const originalQuery = window.navigator.permissions.query;
|
|
window.navigator.permissions.query = (parameters) => (
|
|
parameters.name === 'notifications' ?
|
|
Promise.resolve({ state: 'granted' }) :
|
|
originalQuery(parameters)
|
|
);
|
|
});
|
|
|
|
// Set realistic user agent
|
|
await page.setUserAgent(
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging Tips
|
|
|
|
### Take Screenshots on Error
|
|
|
|
```javascript
|
|
page.on('pageerror', async (error) => {
|
|
console.error('Page error:', error);
|
|
await page.screenshot({ path: `error-${Date.now()}.png` });
|
|
});
|
|
```
|
|
|
|
### Console Logging
|
|
|
|
```javascript
|
|
// Forward console to Node
|
|
page.on('console', (msg) => {
|
|
console.log('PAGE LOG:', msg.text());
|
|
});
|
|
```
|
|
|
|
### Slow Down Execution
|
|
|
|
```javascript
|
|
const browser = await puppeteer.launch({
|
|
slowMo: 250 // 250ms delay between actions
|
|
});
|
|
```
|
|
|
|
### Keep Browser Open
|
|
|
|
```javascript
|
|
const browser = await puppeteer.launch({
|
|
headless: false,
|
|
devtools: true
|
|
});
|
|
|
|
// Prevent auto-close
|
|
await page.evaluate(() => debugger);
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Always close browser:** Use try/finally or process cleanup
|
|
2. **Wait appropriately:** Use waitForSelector, not setTimeout
|
|
3. **Handle errors:** Wrap navigation in try/catch
|
|
4. **Optimize selectors:** Use specific selectors for reliability
|
|
5. **Avoid race conditions:** Wait for navigation after clicks
|
|
6. **Reuse pages:** Don't create new pages unnecessarily
|
|
7. **Set timeouts:** Always specify reasonable timeouts
|
|
8. **Clean up:** Close unused pages and contexts
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- [Puppeteer Documentation](https://pptr.dev/)
|
|
- [Puppeteer API](https://pptr.dev/api)
|
|
- [Puppeteer Examples](https://github.com/puppeteer/puppeteer/tree/main/examples)
|
|
- [Awesome Puppeteer](https://github.com/transitive-bullshit/awesome-puppeteer)
|