Webfuse
High-level Node.js library that drives a real browser
over CDP or WebDriver BiDi - automation, scraping, PDF.
2026PuppeteerCheat Sheet
webfuse.com/puppeteer-cheat-sheet
Apache-2.0 · v24+
At a Glance — Why Modern Puppeteer Wins
v24
Maintained by the Chrome DevTools team. Apache 2.0.
Headless
Default new headless mode = real Chrome rendering pipeline.
Auto-wait
page.locator() waits for visible, enabled, stable, in-view.
CDP + BiDi
Drive Chrome over CDP, Firefox over WebDriver BiDi.
Pick Your Install
puppeteer
~170-282 MB
Bundles Chrome for Testing into ~/.cache/puppeteer. Pick for local dev + self-contained CI.
$ npm i puppeteer
puppeteer-core
no browser
Library only. Bring your own Chrome - system install, Lambda layer, container, or remote endpoint.
$ npm i puppeteer-core
Object Hierarchy — Reuse Heavy, Isolate Light
Browser
1
launch()
1-3s cost
Context
N
isolated
cookies + perms
Page
M
one tab
main API
Locator
lazy +
auto-wait
Pattern: one Browser, N BrowserContexts, M Pages per context. Reuse the Browser — launch() is the expensive part.
Locator API — Kill waitForSelector Forever
Auto-Waits For ↓
👁
Visible
non-zero size, not display:none
Enabled
no disabled attribute, aria-disabled false
Stable bounding box
not mid-animation or layout-shifting
In viewport
scrolls into view automatically
Actions — every one auto-waits
page.locator('button.submit').click()
page.locator('#email').fill('a@b.co')
page.locator('.tooltip').hover()
page.locator('#footer').scroll()
page.locator('.spinner').wait()
Race - first to resolve wins
await Locator.race([
  page.locator('::-p-text(Accept)'),
  page.locator('::-p-text(Allow)'),
]).click()
Configure per locator
page.locator('#late')
  .setTimeout(10_000)
  .setVisibility(null)
  .setWaitForEnabled(false)
  .click()
Filter by predicate
page.locator('button')
  .filter(el =>
    el.textContent === 'OK')
  .click()
P-Pseudo Selectors — Puppeteer-Only Superpowers
::-p-text
Visible text - pierces shadow DOM by default.
locator('::-p-text(Checkout)')
::-p-aria
ARIA role + accessible name. Resilient to refactors.
locator('::-p-aria(Submit)')
::-p-xpath
XPath inside a CSS-style selector.
locator('::-p-xpath(//h1[1])')
>>> / >>>>
Pierce shadow DOM. >>>> = open only (safer).
locator('my-app >>> .btn')
Cheat Facts — Every Critical Number
170-282
MB Chrome for Testing
1-3s
launch() cold start
2-5×
scrape speedup, asset block
4-8
max pages / CPU
30_000
default timeout ms
500ms
networkidle window
2gb
Docker --shm-size
Cooperative Request Interception — Block, Modify, Mock
Block heavy assets — 2-5× faster scraping
await page.setRequestInterception(true)

page.on('request', req => {
  const blocked = ['image', 'media',
                   'font', 'stylesheet']
  blocked.includes(req.resourceType())
    ? req.abort()
    : req.continue()
})
Mock a response — testing without backends
page.on('request', req => {
  if (req.url().endsWith('/api/me')) {
    return req.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ id: 1, name: 'Ada' }),
    })
  }
  req.continue()
})
Resource types →documentstylesheetimagemediafontscriptxhrfetchwebsocket· each handler calls continue / abort / respond exactly once
Output & Emulation — Capture Anything, Pretend to Be Anyone
Screenshots
page.screenshot({
  path: 'shot.png',
  fullPage: true,
  omitBackground: true
})
PNG · JPEG · clip · element-only · returns Buffer if no path.
PDF
page.pdf({
  format: 'A4',
  printBackground: true,
  waitForFonts: true,
  tagged: true
})
Chrome's print pipeline · accessible PDF · header / footer templates.
Emulate Device
import { KnownDevices }
from 'puppeteer'

await page.emulate(
  KnownDevices['iPhone 15 Pro'])
Viewport · UA · touch · scale factor — all in one call.
Geo · Network · CPU
page.setGeolocation({
  latitude: 52.52,
  longitude: 13.40
})
page.emulateCPUThrottling(4)
Timezone · locale · network throttle · CPU slow-down · permissions.
Performance Best Practices
♻ Reuse the Browser
launch() is the heavy call. Spin up BrowserContexts, not new browsers.
⛔ Block heavy assets
Images, fonts, media, CSS — 2–5× speedup for scraping.
📏 Cap concurrency
Each page is a real Chrome tab. 4–8 per CPU is a sane ceiling.
🧹 Close everything
page.close() · context.close() · dispose handles · wrap in try / finally.
🔁 Recycle long-running
Restart every N pages or M minutes — Chrome leaks slowly under load.
⏳ Wait on conditions
Use waitFor* + Locator — never setTimeout.
⚠ Pitfalls That Bite Everyone Once
Memory leaks from handles
Every $ / $$ / waitForSelector pins a JS object. Always dispose — or use Locator.
DOM nodes from evaluate
Results are JSON-serialised. Return primitives — or use evaluateHandle.
Closure variables in evaluate
Outer scope is invisible inside the page — pass values as args.
Docker /dev/shm too small
Chrome crashes on big pages. Pass --shm-size=2gb or --disable-dev-shm-usage.
Bulletproof Production Pattern
const browser = await puppeteer.launch({
  headless: true,
  args: ['--no-sandbox', '--disable-dev-shm-usage'],
})
try {
  const ctx  = await browser.createBrowserContext()
  const page = await ctx.newPage()
  await page.goto(url, { waitUntil: 'networkidle2', timeout: 30_000 })
  return await page.content()
} finally {
  await browser.close()  // runs on success AND failure
}
Hello World — 4 Lines
$ npm i puppeteer

import puppeteer from 'puppeteer'
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://pptr.dev')
await page.screenshot({ path: 'p.png', fullPage: true })
await browser.close()
Full cheat sheet:webfuse.com/puppeteer-cheat-sheetPuppeteer is a Google trademark. This is an independent reference.
Webfuse