Playwright and Puppeteer are the two libraries people choose between to drive a browser from code, and for everyday jobs their code looks almost the same. So instead of a feature checklist, we ran the same jobs in both on one machine and compared the numbers, then went through the differences that actually decide the choice.
How we measured. On 2026-11-08: Puppeteer 25.7.0 in the official ghcr.io/puppeteer/puppeteer image (Chrome 152.0.7977.42) and Playwright 1.64.1 in the official mcr.microsoft.com/playwright:v1.64.1-noble image (Chromium 156.0.8078.80), on the same computer — an AMD Ryzen 9 7950X3D under WSL2 — against the same local test page. Each run: five cold launches, then ten rounds on one browser of open the page → viewport screenshot → full-page screenshot → A4 PDF → element screenshot; the numbers are medians, from three runs each. The machine was shared with other work (load average 10–66 during the runs), so read differences under about 20 % as noise. Browser versions differ too: each library ships the build it is tested with.
The numbers
| Median, ms | Puppeteer (default: new headless) | Puppeteer (headless: 'shell') |
Playwright (default: headless shell) | Playwright (channel: 'chromium') |
|---|---|---|---|---|
| Launch the browser | 246–315 | 118–137 | 43–61 | 90–98 |
Open page (load) |
30–31 | 23–30 | 17–20 | 26–28 |
| Viewport screenshot | 66–72 | 60–61 | 48–50 | 46–56 |
| Full-page screenshot | 129–142 | 96–113 | 113–128 | 115–120 |
| A4 PDF | 34–38 | 13–16 | 6–8 | 22–23 |
| Element screenshot | 70–73 | 57–68 | 87–93 | 84–85 |
| Whole job | 324–365 | 257–280 | 272–291 | 287–304 |
What the table says:
- Launch time is where they differ — and it is mostly the headless mode, not the library. Playwright's default for headless Chromium is the separate
chromium-headless-shellbuild, as its browsers page explains; Puppeteer's default is Chrome's new headless mode, which is the full browser. Put Puppeteer on the shell (headless: 'shell') and its launch fell from ~300 to ~130 ms; put Playwright on the full browser (channel: 'chromium') and its launch rose from ~50 to ~95 ms. - Per job, they are close. With the same kind of headless browser, a whole job took 257–280 ms (Puppeteer shell) and 272–291 ms (Playwright shell). Playwright was faster at viewport screenshots and PDFs, Puppeteer at full-page and element screenshots.
- If speed matters, reuse the browser. A launch costs as much as a job or more; both libraries let you keep one browser open and open pages in it. Our speed guide measured that pattern in detail.
Outputs were essentially the same size: a 13.7 KB and a 13.2 KB viewport PNG, 29.7 and 29.2 KB full pages, 28.8 and 22.8 KB PDFs.
The code, side by side
// Puppeteer
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto(url, { waitUntil: 'load' });
await page.screenshot({ path: 'page.png', fullPage: true });
await (await page.$('ul')).screenshot({ path: 'list.png' });
await browser.close();
// Playwright
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto(url, { waitUntil: 'load' });
await page.screenshot({ path: 'page.png', fullPage: true });
await page.locator('ul').screenshot({ path: 'list.png' });
await browser.close();
These are the scripts we timed, minus the timers. Moving a script from one to the other is mostly renaming:
| Puppeteer | Playwright |
|---|---|
puppeteer.launch() |
chromium.launch() (or firefox, webkit) |
page.setViewport({ width, height }) |
browser.newPage({ viewport: { width, height } }) |
page.$(selector) / page.$$(selector) |
page.locator(selector) |
page.waitForSelector(selector) |
page.locator(selector).waitFor() |
page.evaluate(fn) |
page.evaluate(fn) |
The waiting model is the one change that needs thought: Playwright's locators wait on every action, so explicit waits copied over from Puppeteer can often be deleted.
What actually differs
| Puppeteer | Playwright | |
|---|---|---|
| Languages | JavaScript / TypeScript | JavaScript / TypeScript, Python, Java, .NET |
| Browsers | Chrome, Firefox | Chromium, Firefox, WebKit |
| Default headless Chromium | New headless (full Chrome) | chromium-headless-shell |
| Waiting | waitForSelector, locators that wait, waitUntil |
Auto-waiting actions and locators, waitUntil |
| Testing | Bring your own test runner | Playwright Test: runner, parallelism, toHaveScreenshot |
- Languages. Playwright's docs: it "is available in multiple languages that share the same underlying implementation", with all core features in each. Puppeteer is JavaScript only; in Python people use Playwright or the unofficial pyppeteer (Python screenshots).
- Browsers. WebKit — the engine behind Safari — is a common reason to pick Playwright: of the two, only it gets close to how a page renders in Safari. Puppeteer drives Firefox over WebDriver BiDi, with some features missing (Puppeteer tutorial).
- Waiting. Playwright's auto-waiting runs "a range of actionability checks on the elements before making actions" and fails with a
TimeoutErrorif they do not pass. Puppeteer's locators wait too, but most Puppeteer code still waits explicitly. Neither knows when your data has arrived; see waiting for the page in Puppeteer. - Testing. Playwright Test, with screenshot comparison built in, is the strongest argument for Playwright in a test suite (visual regression).
Which to pick
- Pick Puppeteer if you work in Node.js, need only Chrome (screenshots, PDFs, scraping), and want the library maintained by the Chrome team. Use
headless: 'shell'when start-up time matters. - Pick Playwright if you need Python, Java or .NET, need WebKit or all three engines, or are writing tests and want the runner and screenshot assertions.
- Keep what you have if a working script already does the job: our numbers give no reason to rewrite it for speed.
When to use neither
Both are libraries: you also run the browsers — containers, fonts, memory, crashes, updates. If the job is screenshots of public pages or images from HTML, our screenshot API and HTML templates do it in one HTTP request, from any language, with cookie banners hidden and full-page captures handled. For tests, pages behind a login and local development, Playwright or Puppeteer on your own machine remain the right tools.