Puppeteer vs Playwright in practice
The two APIs are about 85% the same - both give you the same building blocks (page, frame, request, response) under similar method names. The differences that matter for scraping:
- Auto-waiting — Playwright waits for an element to be ready to act on before clicking or typing; Puppeteer only waits when you tell it to. So Puppeteer scripts end up with more explicit
waitForSelectorcalls. - Parallel contexts — A context is an isolated session (its own cookies and storage) inside one browser. Playwright's
browserContextis cleaner for running several of these at once. Puppeteer supports it too, but the API is older. - Languages — Puppeteer is Node-only. If your stack is Python, Playwright is the only choice.
- Stealth plugins — Puppeteer's stealth ecosystem is older and more mature.
puppeteer-extra-plugin-stealthhas more patches than its Playwright equivalent, though both lose to Function.toString() inspection equally.
For a brand-new scraping project in Node, default to Playwright unless your team already has Puppeteer code. Puppeteer is not deprecated, but the active feature investment has shifted to Playwright.
puppeteer-extra and the stealth plugin
The puppeteer-extra plugin system, paired with puppeteer-extra-plugin-stealth, is the most-cited anti-detection plugin ecosystem for Puppeteer. The stealth plugin runs ~17 separate patches: it hides navigator.webdriver (the flag that openly says "a script is driving this browser"), fixes the plugin array, patches WebGL parameters, normalises the User-Agent, masks the chrome.runtime object, and so on.
It addresses every detection a 2019 anti-bot system used. It does not hold up against 2024+ vendors that check via Function.toString() (see that entry) - a trick that reads back a function's source code - or that look for CDP runtime artifacts (traces left by that DevTools connection). Each of the 17 patches is a JS function whose source is visible to toString(); Kasada, recent Akamai, and PerimeterX flag this stack on first request.
For Puppeteer in production against hard targets, the modern approach is puppeteer-real-browser (driving a real Chrome rather than headless Chromium) or switching to a C++-patched variant like CloakBrowser.
When to actually use Puppeteer
Three situations where Puppeteer is the right pick over Playwright:
- The codebase is already on Puppeteer and switching would be busywork.
- The project depends on a Puppeteer-only library (
puppeteer-cluster,puppeteer-screen-recorder) that has no Playwright equivalent. - The team specifically wants the older, smaller API - Puppeteer does less than Playwright, which some teams find easier to reason about.
For everything else, the answer is Playwright. The CDP protocol, the Chromium binary, and the detection surface are identical - the real difference is how the API feels to use and which languages it reaches.
