Skip to content
📚 5 min read

Puppeteer Guide

Puppeteer is a Node.js library that provides a high-level API to control Chrome/Chromium over the DevTools Protocol. It's particularly useful for automated testing, web scraping, and generating screenshots/PDFs.

Key Features

  • Chrome DevTools Protocol support
  • Headless browser automation
  • Network interception
  • Performance monitoring
  • PDF generation
  • Screenshot capture
  • Keyboard/mouse simulation
  • Mobile device emulation

Getting Started

bash
# Install Puppeteer
npm install puppeteer

Basic Test Structure

javascript
const puppeteer = require('puppeteer');

describe('Homepage', () => {
  let browser;
  let page;

  beforeAll(async () => {
    browser = await puppeteer.launch();
  });

  beforeEach(async () => {
    page = await browser.newPage();
    await page.goto('https://example.com');
  });

  afterAll(async () => {
    await browser.close();
  });

  test('title should be correct', async () => {
    const title = await page.title();
    expect(title).toBe('Example Domain');
  });
});

Common Operations

javascript
// Navigation
await page.goto('https://example.com');
await page.goBack();
await page.reload();

// Selectors and interactions
await page.click('button');
await page.type('input[name="search"]', 'query');
await page.select('select#options', 'value');

// Waiting
await page.waitForSelector('.element');
await page.waitForNavigation();
await page.waitForFunction(() => document.readyState === 'complete');

// Network
await page.setRequestInterception(true);
page.on('request', (request) => {
  if (request.resourceType() === 'image') request.abort();
  else request.continue();
});

// Screenshots and PDF
await page.screenshot({ path: 'screenshot.png' });
await page.pdf({ path: 'page.pdf', format: 'A4' });

Core Concepts

Browser Setup

javascript
const puppeteer = require('puppeteer');

// Launch browser
const browser = await puppeteer.launch({
  headless: 'new',
  defaultViewport: { width: 1920, height: 1080 },
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
});

// Create new page
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });

// Enable request interception
await page.setRequestInterception(true);
javascript
// Basic navigation
await page.goto('https://example.com', {
  waitUntil: 'networkidle0',
  timeout: 30000,
});

// Wait for navigation
await Promise.all([page.waitForNavigation(), page.click('a.link')]);

// History navigation
await page.goBack();
await page.goForward();
await page.reload();

Element Selection

javascript
// Selectors
const button = await page.$('.button');
const buttons = await page.$$('.button');
const input = await page.$('#search');

// Evaluate in page context
const text = await page.$eval('.content', (el) => el.textContent);
const texts = await page.$$eval('.item', (els) =>
  els.map((el) => el.textContent)
);

// Wait for elements
await page.waitForSelector('.dynamic-content');
await page.waitForXPath('//button[contains(text(), "Submit")]');

Advanced Features

Page Interactions

javascript
// Mouse events
await page.mouse.move(100, 200);
await page.mouse.down();
await page.mouse.up();
await page.mouse.click(100, 200);

// Keyboard events
await page.keyboard.type('Hello World');
await page.keyboard.press('Enter');
await page.keyboard.down('Control');
await page.keyboard.press('A');
await page.keyboard.up('Control');

// File upload
const input = await page.$('input[type="file"]');
await input.uploadFile('path/to/file.jpg');

Network Monitoring

javascript
// Monitor requests
page.on('request', (request) => {
  console.log(`Request: ${request.url()}`);
  request.continue();
});

// Monitor responses
page.on('response', (response) => {
  console.log(`Response: ${response.url()}: ${response.status()}`);
});

// Mock responses
page.on('request', (request) => {
  if (request.url().endsWith('/api/data')) {
    request.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ data: 'mocked' }),
    });
  } else {
    request.continue();
  }
});

JavaScript Execution

javascript
// Execute in page context
const dimensions = await page.evaluate(() => {
  return {
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
    devicePixelRatio: window.devicePixelRatio,
  };
});

// Expose function to page
await page.exposeFunction('md5', (text) =>
  crypto.createHash('md5').update(text).digest('hex')
);

// Add script tag
await page.addScriptTag({
  url: 'https://code.jquery.com/jquery-3.6.0.min.js',
});

Testing Patterns

Screenshot Capture

javascript
// Full page screenshot
await page.screenshot({
  path: 'screenshot.png',
  fullPage: true,
});

// Element screenshot
const element = await page.$('.card');
await element.screenshot({
  path: 'element.png',
});

// PDF generation
await page.pdf({
  path: 'page.pdf',
  format: 'A4',
  printBackground: true,
});

Performance Monitoring

javascript
// Enable CPU and memory profiling
const client = await page.target().createCDPSession();
await client.send('Performance.enable');

Advanced Testing

Mobile Emulation

javascript
// Emulate device
const iPhone = puppeteer.devices['iPhone 12'];
await page.emulate(iPhone);

// Set geolocation
await page.setGeolocation({
  latitude: 51.5074,
  longitude: -0.1278,
});

// Emulate network conditions
await page.emulateNetworkConditions({
  offline: false,
  latency: 100,
  download: (1000 * 1024) / 8,
  upload: (500 * 1024) / 8,
});

Visual Testing

javascript
const { toMatchImageSnapshot } = require('jest-image-snapshot');
expect.extend({ toMatchImageSnapshot });

describe('Visual regression', () => {
  it('should match screenshot', async () => {
    await page.goto('/');

    // Compare full page
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot();

    // Compare element
    const logo = await page.$('.logo');
    const logoImage = await logo.screenshot();
    expect(logoImage).toMatchImageSnapshot({
      failureThreshold: 0.01,
      failureThresholdType: 'percent',
    });
  });
});

API Testing

javascript
describe('API integration', () => {
  it('should intercept API calls', async () => {
    // Mock API response
    await page.setRequestInterception(true);
    page.on('request', (request) => {
      if (request.url().includes('/api')) {
        request.respond({
          status: 200,
          contentType: 'application/json',
          body: JSON.stringify({ success: true }),
        });
      } else {
        request.continue();
      }
    });

    // Verify UI updates
    await page.goto('/');
    await page.waitForSelector('.success-message');
  });
});

Best Practices

1. Element Selection

javascript
// ❌ Avoid
await page.$('.button:nth-child(2)');
await page.$('div.btn');

// ✅ Prefer
await page.$('[data-testid="submit"]');
await page.$('#login-button');
await page.$('button[aria-label="Submit"]');

2. Waiting Strategies

javascript
// ❌ Avoid
await page.waitFor(5000);

// ✅ Prefer
await page.waitForSelector('.content');
await page.waitForFunction(
  'document.querySelector(".dynamic").textContent.includes("loaded")'
);
await page.waitForNavigation({
  waitUntil: 'networkidle0',
});

3. Error Handling

javascript
try {
  await page.click('.non-existent');
} catch (error) {
  // Take screenshot
  await page.screenshot({
    path: `error-${Date.now()}.png`,
  });

  // Log error details
  console.error({
    message: error.message,
    stack: error.stack,
    url: page.url(),
  });

  throw error;
}

4. Resource Management

javascript
let browser;
let page;

beforeAll(async () => {
  browser = await puppeteer.launch();
});

beforeEach(async () => {
  page = await browser.newPage();
  await page.setDefaultTimeout(10000);
  await page.setDefaultNavigationTimeout(20000);
});

afterEach(async () => {
  await page.close();
});

afterAll(async () => {
  await browser.close();
});