Testland
Browse all skills & agents

puppeteer-testing

Authors browser automation scripts using Puppeteer - Chrome / Chromium-only headless / headed automation, Page object via `page.*` API, network interception, PDF generation, screenshot capture, scraping. Distinct from Playwright (Puppeteer's older sibling, Chrome-only) - use Puppeteer for Chrome-only browser automation tasks (scraping, generating PDFs from HTML, screenshot pipelines) where Playwright's multi-browser support is unneeded overhead. Use when a project already depends on `puppeteer` / `puppeteer-core`, or when a Chrome-only script must emit PDFs, screenshots, or scraped data rather than assert on a page.

Install with skills.sh (any agent)

npx skills add testland/qa --skill puppeteer-testing
View source

puppeteer-testing

Overview

Puppeteer controls Chrome / Chromium via the DevTools Protocol - a lightweight fit (no bundled test runner) for Chrome-only browser automation beyond E2E testing: scraping, PDF generation, screenshot pipelines, web crawling.

When to use

  • Chrome-only automation (no Firefox / WebKit needed).
  • Use cases beyond E2E testing: scraping, PDF generation, screenshot capture, automated browsing for crawlers.
  • Existing Puppeteer codebase; migration cost prohibitive.

For E2E testing specifically: Playwright is the recommended successor. Migration is mostly mechanical (similar API).

Step 1 - Install

npm install --save-dev puppeteer
# Auto-downloads matching Chromium

# Or for Chrome-bring-your-own:
npm install --save-dev puppeteer-core

puppeteer (full) bundles Chromium; puppeteer-core (lite) lets you point at an existing Chrome.

Verify: npm ls puppeteer lists the installed version. If you installed puppeteer-core, no Chromium is bundled - pass executablePath at launch or the next step throws Could not find Chromium.

Step 2 - Basic browser automation

// scripts/screenshot.js
import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch({ headless: 'new' });
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png', fullPage: true });
  await browser.close();
})();

The page.* API mirrors Playwright's: page.goto, page.click, page.type, page.evaluate, etc.

Verify: after running, example.png exists and is non-empty. If it is missing or the script threw Could not find Chromium, the bundled download was skipped - reinstall puppeteer or set executablePath, confirm headless: 'new', then re-run.

Step 3 - E2E test (with Jest)

// __tests__/checkout.test.js
import puppeteer from 'puppeteer';

let browser, page;

beforeAll(async () => {
  browser = await puppeteer.launch({ headless: 'new' });
});

beforeEach(async () => {
  page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 720 });
});

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

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

test('checkout flow', async () => {
  await page.goto('http://localhost:3000/login');
  await page.type('[data-testid=email]', 'user@example.com');
  await page.type('[data-testid=password]', 'pwd');
  await page.click('button[type=submit]');

  await page.waitForSelector('h1');
  const heading = await page.$eval('h1', el => el.textContent);
  expect(heading).toContain('Welcome');
}, 60000);

Step 4 - Use-case recipes

Beyond the core automation and E2E spine above, Puppeteer covers network interception, server-side PDF generation, multi-viewport screenshot pipelines, and scraping. Copy-paste recipes for each: references/use-cases.md.

Step 5 - Run

node scripts/screenshot.js

# Or as part of test suite
npx jest

Step 6 - Migration to Playwright

When ready to migrate:

// Puppeteer
const browser = await puppeteer.launch();
const page = await browser.newPage();

// Playwright equivalent
const browser = await chromium.launch();
const page = await browser.newPage();

The APIs are similar; mechanical find-replace covers most cases. Playwright adds: cross-browser, web-first assertions, trace viewer, codegen - net win unless Chrome-only is intentional.

Anti-patterns

Anti-patternWhy it failsFix
Using Puppeteer for cross-browser E2EChrome-only; misses Firefox / Safari regressions.Playwright for cross-browser.
Forgetting browser.close()Browser process leaks; CI runner OOM.afterAll cleanup (Step 3).
page.waitFor(2000)Flaky; deprecated.page.waitForSelector / waitForFunction.
Running headed in CINo display; crashes.headless: 'new' (Step 2).
puppeteer-core without specifying executable"browser not found" errors.Use puppeteer (bundled) OR specify executablePath.

Limitations

  • Chrome / Chromium only. No Firefox / WebKit.
  • No bundled test runner. Pair with Jest / Mocha / Vitest.
  • No web-first assertions. Manual waitForSelector patterns.
  • Maintenance pace slower than Playwright. Active but not expanding.

References

  • Puppeteer docs at pptr.dev.
  • playwright-testing - recommended successor.
  • testcafe-testing - alternative Chrome-friendly E2E.

Puppeteer use-case recipes

View source (opens in new window)

Puppeteer use-case recipes

Specialized page.* recipes beyond the core automation and E2E spine in SKILL.md (opens in new window). Each drives Chrome / Chromium over the DevTools Protocol and runs as a standalone script or test body.

Network interception

Stub third-party APIs or mock responses in tests:

await page.setRequestInterception(true);

page.on('request', request => {
  if (request.url().includes('/api/orders')) {
    request.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ orderId: 'TEST-1234', total: 24.99 }),
    });
  } else {
    request.continue();
  }
});

PDF generation

Common production use: server-side PDF from an HTML template.

import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch({ headless: 'new' });
  const page = await browser.newPage();
  await page.goto('https://example.com/invoice/123');
  await page.pdf({
    path: 'invoice-123.pdf',
    format: 'A4',
    margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
    printBackground: true,
  });
  await browser.close();
})();

Screenshot pipelines

Capture one page at multiple viewports:

const viewports = [
  { name: 'mobile', width: 375, height: 667 },
  { name: 'tablet', width: 768, height: 1024 },
  { name: 'desktop', width: 1280, height: 720 },
];

for (const vp of viewports) {
  await page.setViewport(vp);
  await page.goto('https://example.com');
  await page.screenshot({ path: `screenshots/${vp.name}.png`, fullPage: true });
}

Web scraping

Extract structured data with $$eval:

await page.goto('https://example.com/products');

const products = await page.$$eval('.product-card', cards =>
  cards.map(card => ({
    name: card.querySelector('.product-name')?.textContent.trim(),
    price: card.querySelector('.product-price')?.textContent.trim(),
    url: card.querySelector('a')?.href,
  }))
);

console.log(products);

Related skills

browserstack-automate

Author and run E2E tests on BrowserStack Automate - cloud grid covering 3000+ real device + browser combinations. Covers BROWSERSTACK_USERNAME + ACCESS_KEY auth, hub URL https://hub-cloud.browserstack.com/wd/hub, W3C capabilities + bstack:options (projectName, buildName, sessionName), BrowserStackLocal for testing against localhost / internal environments, parallel session limits, and CI integration. Use for cross-browser regression on real devices + browsers - distinct from running a single test framework locally, and from a matrix runner limited to the browser engines bundled on the local machine.

cypress-testing

Authors and improves Cypress E2E tests - installs Cypress, configures `cypress.config.ts`, authors `cy.*` command chains, refactors existing specs (`cy.wait(ms)` sleeps into assertions, repeated flows into `cy.session` custom commands), and debugs with the time-travel GUI; Cypress Cloud for parallel runs and recording. Use for both greenfield test authoring and improving hand-written specs already in the codebase. For automated refactor of raw Cypress Studio recordings specifically, use a dedicated codegen-review pass.

lambdatest-automate

Author and run E2E tests on LambdaTest - cloud grid for cross-browser + real-device testing with W3C WebDriver, Cypress, Playwright, and Appium support. Covers LT_USERNAME + LT_ACCESS_KEY auth, hub URL hub.lambdatest.com/wd/hub, W3C capabilities + LT:Options dict (build, name, project, smartUI, network, console, video, tunnel), LambdaTest Tunnel for internal apps. Use for cross-browser regression with LambdaTest as the cloud grid; complements BrowserStack + Sauce Labs.

playwright-testing

Authors and remediates Playwright E2E tests across Chromium, Firefox, WebKit - `npm init playwright@latest` scaffolding, `playwright.config.ts` browser projects, accessibility-first locators (`getByRole`/`getByLabelText`) to replace brittle CSS selectors, web-first assertions to eliminate `waitForTimeout` flakiness, Page Object pattern, trace viewer debugging, sharded parallel execution with merged HTML reporting, and GitHub Actions CI integration. Use for new test authoring, flakiness remediation, and CI setup; for reviewing codegen output specifically, use a dedicated codegen-review pass.

saucelabs-automate

Author and run E2E tests on Sauce Labs - cloud grid for cross-browser + real-device testing with W3C WebDriver, Cypress, Playwright, and Appium support. Covers SAUCE_USERNAME + SAUCE_ACCESS_KEY auth, regional hub URLs (us-west-1 / us-east-4 / eu-central-1), W3C capabilities, sauce:options dict (build, name, screenResolution, tunnelName), Sauce Connect Proxy for internal-environment testing. Use for cross-browser regression with Sauce Labs as the cloud grid; complements BrowserStack + LambdaTest as alternative providers.

selenium-testing

Authors Selenium WebDriver tests in any of its 6+ supported languages (Java, Python, JavaScript, C#, Ruby, Kotlin, PHP) - picks the appropriate language binding, configures WebDriver per browser, uses `By.*` locators with the team's accessibility-first preference where supported, runs locally + via Selenium Grid for distributed execution, parses results to JUnit XML. Use for legacy Selenium-locked stacks; new projects pick Playwright or Cypress.

testcafe-testing

Authors TestCafe E2E tests - `npm install testcafe`, fixture/test syntax, `Selector` API for queries, automatic-waits, no WebDriver required (TestCafe injects scripts via a proxy), supports any browser including remote / cloud farms. Use when the team prefers a no-WebDriver architecture and one of TestCafe's specific features (e.g., role-based auth) matters.

web-e2e-overview

Teaches web end-to-end testing from first principles: what browser-driven E2E covers and how it differs from unit and integration tests, a decision table for choosing between Playwright, Cypress, Selenium WebDriver, WebdriverIO, Puppeteer, TestCafe and the BrowserStack / Sauce Labs / LambdaTest cloud grids based on files already present in the repo, install and first-run commands for each, and the flakiness traps (fixed sleeps, CSS and XPath selectors, state shared between tests) that sink new suites. Use when a web application has no E2E coverage yet, when picking or replacing an E2E framework, or when a first browser test needs to go green end to end.

webdriverio-testing

Authors WebdriverIO E2E tests - `npm init wdio@latest` scaffolding, services architecture (sauce, browserstack, appium, devtools), reporters (spec, allure, junit), built-in Mocha/Jasmine/Cucumber framework integrations. WebdriverIO sits between Selenium (W3C protocol) and Playwright (modern API) - Selenium-protocol-compatible with rich plugin ecosystem. Use when the team needs WebDriver protocol + service-based device-farm integration.