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-testingpuppeteer-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
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-corepuppeteer (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 jestStep 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-pattern | Why it fails | Fix |
|---|---|---|
| Using Puppeteer for cross-browser E2E | Chrome-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 CI | No display; crashes. | headless: 'new' (Step 2). |
puppeteer-core without specifying executable | "browser not found" errors. | Use puppeteer (bundled) OR specify executablePath. |
Limitations
References
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.