All posts

Playwright vs Puppeteer for web scraping

Playwright or Puppeteer for scraping? Choose Playwright for Firefox, WebKit and auto-waiting, Puppeteer for Chrome-only scripts. Compare code and port scripts.

  • playwright
  • puppeteer
  • web scraping
  • browser automation
  • remote browser
On this page

Playwright vs Puppeteer comes down to your targets. Playwright is the better default for new scraping projects that need Firefox or WebKit, auto-waiting or parallel execution, according to Software Testing Tutorials Automation. Puppeteer is still a sound pick for Chrome-only scraping and automation, simple scripts and existing codebases, as Browserless’s comparison concludes.

You’re starting a scraper, or you have a Puppeteer script that works but now needs Firefox, WebKit or less hand-written waiting code.

Published comparisons disagree on whether Playwright is a Puppeteer fork and whether Puppeteer supports Firefox. The official documentation settles both. You’ll be able to pick a library per scenario, connect either one to a hosted browser over CDP, and port a script.

Key Takeaways

  • Over a hosted CDP endpoint, test every advanced Playwright feature you rely on before you build on it.
  • On a hosted CDP connection, the exit is fixed when you connect. A new context won’t change it, so each locale needs its own connection.
  • Porting from Puppeteer is mostly mechanical. Waiting behavior and request interception are where scripts break.
  • Speed and flakiness depend on your pages and pinned versions, so rerun the tasks yourself rather than trusting a published benchmark.

Playwright vs Puppeteer: which browsers and languages does each support?

Playwright drives Chromium, Firefox and WebKit from several languages, while Puppeteer’s README names Chrome and Firefox and describes a JavaScript library. Both projects ship often, so re-check the live docs before you rely on any cell below.

Image generated

PuppeteerPlaywright
Browsers namedChrome, FirefoxChromium, Firefox, WebKit
LanguagesJavaScript (Node.js)Node.js, Python, Java, .NET
ProtocolDevTools Protocol or WebDriver BiDiIts own protocol

The README says Puppeteer controls “Chrome or Firefox over the DevTools Protocol or WebDriver BiDi.” That sentence names two browsers and no others.

Ranking pages disagree because they describe typical use. Browserless calls Puppeteer “Chrome-focused”, and another page says it suits “simple Chrome-based automation”. The README is the documented position.

Playwright’s own repositories describe a Python library, a Java library and the “official language port” for .NET, next to Node.js. Playwright’s Python repository opens with “Playwright is a Python library”.

As our Wayfern vs Multilogin comparison lays out, our hosted browser has no engine other than Chromium and no mobile emulation, so no Firefox and no WebKit. If you need those engines, run Playwright’s own browsers and keep our hosted anti-detect sessions for Chromium work.

Myth vs documented fact

  • Playwright is a Puppeteer fork, or was built from scratch. Apify’s comparison says Playwright “began as a fork of Puppeteer but rapidly evolved into a library in and of itself.”
  • Puppeteer is Chrome only, or has experimental Firefox and Edge. The README names Chrome and Firefox. It names no Edge.
  • Puppeteer has a Python version. Puppeteer’s README calls it “a JavaScript library”. The pyppeteer repository calls itself an “Unofficial Python port”.

How do you run the same scraping tasks in Playwright and Puppeteer?

Playwright and Puppeteer run the same scraping tasks with near-identical code for navigation, interception, screenshots and PDFs. The real differences show up in form submission and in waiting for dynamic content.

The snippets target a placeholder URL. Each block shows Playwright first, then Puppeteer, so save them as separate files.

Record your own versions too. Run npm ls playwright puppeteer in your project and keep the output next to your results.

1. Navigation with a wait condition. Nothing changes here.

// Playwright
await page.goto("https://example.com/products", { waitUntil: "domcontentloaded" });

// Puppeteer
await page.goto("https://example.com/products", { waitUntil: "domcontentloaded" });

2. Form fill and submit. Playwright’s fill() and click() wait for the element. Puppeteer needs you to pair the click with a navigation wait.

// Playwright
await page.fill("#q", "laptops");
await page.click("button[type=submit]");

// Puppeteer
await page.type("#q", "laptops");
await Promise.all([page.waitForNavigation(), page.click("button[type=submit]")]);

3. Dynamic content. Playwright asserts on a locator, and Puppeteer waits on a selector.

// Playwright (expect comes from @playwright/test)
const rows = page.locator(".result-row");
await expect(rows.first()).toBeVisible();
const titles = await rows.allTextContents();

// Puppeteer
await page.waitForSelector(".result-row", { visible: true });
const titles = await page.$$eval(".result-row", els => els.map(e => e.textContent));

4. Blocking images. Register the handler before goto() in both.

// Playwright
await page.route("**/*", route =>
  route.request().resourceType() === "image" ? route.abort() : route.continue());

// Puppeteer
await page.setRequestInterception(true);
page.on("request", req =>
  req.resourceType() === "image" ? req.abort() : req.continue());

5. Screenshot and PDF. The calls match.

// Playwright and Puppeteer
await page.screenshot({ path: "page.png", fullPage: true });
await page.pdf({ path: "page.pdf", format: "A4" });

In Playwright, page.pdf() has a browser limit: the Playwright API docs say PDF generation is “only supported in Chromium headless.” Firefox and WebKit runs need another route.

Why the waiting code differs

Playwright’s “Auto-waiting” page says Playwright “performs a range of actionability checks on the elements before making actions to ensure these actions behave as expected.” That is why snippet 2 has no explicit wait.

Puppeteer’s classic page methods leave the wait to you, so check its docs for your version.

Auto-waiting accounts for fewer timing failures, but it is not a measured result. A fair comparison of speed or flakiness needs you to pin:

  • Library versions and the browser build each one launches.
  • Hardware, CPU count and memory.
  • Target pages, including whether they are static or script-heavy.
  • Cold start versus warm start, and how many runs you averaged.

To rerun these tasks against a hosted browser, our guide to connecting Playwright or Puppeteer over CDP shows the connection step.

How do you connect Playwright and Puppeteer to a remote browser?

You connect Playwright or Puppeteer to a remote browser through one of three paths: Playwright’s connectOverCDP(), Playwright’s browserType.connect(), or Puppeteer’s puppeteer.connect() with a browserWSEndpoint.

  1. Pick the path. browserType.connect() targets a Playwright server, and Playwright’s BrowserType docs say the client’s major and minor version must match the server’s. A hosted CDP endpoint such as Wayfern’s speaks only CDP, so connectOverCDP() and puppeteer.connect() are your two options.

  2. Connect Playwright. Reuse the default context instead of creating a new one. Set timeout explicitly, because Wayfern’s limits page gives a cold start of about 45 to 50 seconds, longer with a residential exit.

import { chromium } from "playwright";
const browser = await chromium.connectOverCDP(
  `wss://api.wayfern.com/ws?token=${process.env.LINK_TOKEN}`,
  { timeout: 120_000 },
);
const page = await browser.contexts()[0].newPage();
  1. Connect Puppeteer. Pass the same URL as browserWSEndpoint. Check the Puppeteer docs for which timeout option covers the initial connection.
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({
  browserWSEndpoint: `wss://api.wayfern.com/ws?token=${process.env.LINK_TOKEN}`,
});
const page = await browser.newPage();
  1. Confirm success. The first message on the socket is a JSON “connected” message with a connectionId, then plain CDP. There is no create-session REST call. If page.goto() loads a page, you’re connected.

What to expect over a CDP connection:

  • The browser presents a desktop device, so Playwright’s mobile device descriptors don’t apply.
  • The BrowserType page calls connectOverCDP() “significantly lower fidelity” than the native protocol. Test each native-protocol feature you rely on before you build on it.

To run both snippets against a real endpoint, mint a link token and set it as LINK_TOKEN.

How do browser contexts isolate cookies for multiple accounts or locales?

Browser contexts isolate cookies by giving each account or locale its own storage inside one shared browser instance. A browser context is what Playwright’s docs call an “isolated incognito-alike session” within a browser instance, with its own cookies, local storage and cache.

Playwright creates one with browser.newContext(), and Puppeteer uses browser.createBrowserContext(). Puppeteer’s BrowserContext reference says “Each context has isolated storage (cookies/localStorage/etc.)“.

Per-context options differ. Playwright’s newContext() takes locale, timezoneId, viewport and proxy.

In Puppeteer, createBrowserContext() takes a proxyServer option, while timezone and viewport are separate calls on the page: page.emulateTimezone() and page.setViewport().

A context doesn’t isolate the browser process or the network exit. Over remote CDP, the default context is browser.contexts()[0]. Whether you can create more depends on the endpoint, so test newContext() against yours before you build on it.

On Wayfern, the exit (country, region, city, isp and a sticky session) is set per connection on the /ws URL, as our guide to scraping behind logins and bot walls explains. Contexts don’t change it, so two locales mean two connections.

Cookies in one context stay out of the others, but both contexts ride the same connection and the same exit. For logins, the same guide covers holding one sticky residential exit.

Wayfern’s anti-detect sessions page points out that a cookie issued to one IP and replayed from another is an easy signal for a site to read. Keep each account’s context, cookies and exit together.

How do request interception, proxies and test runners differ?

Request interception, proxy scope and the test runner differ mainly in where each setting lives: Playwright can apply them to a whole browser context, while Puppeteer leans on the page and the launch flag.

CriterionPlaywrightPuppeteer
Interception APIpage.route(), context.route()setRequestInterception plus request events
Interception scopePage or whole contextPer page
Proxy setupLaunch option or per context--proxy-server launch flag
Proxy credentialsIn the proxy optionspage.authenticate
RunnerPlaywright Test, built inExternal: Jest, Mocha or a script
Retries and tracesBuilt inBring your own

Playwright’s BrowserContext docs say routing “provides the capability to modify network requests that are made by any page in the browser context.”

Puppeteer’s interception docs warn that once interception is on, “every request will stall unless it’s continued, responded or aborted.”

On a hosted browser, the proxy is set on the connection, not at launch. Wayfern’s CDP sessions take country, region, city and isp on the /ws URL, or your own http, https or socks5 proxy saved on a link. Credentials never go in the URL.

Playwright’s intro says Playwright Test “was created specifically to accommodate the needs of end-to-end testing.” Puppeteer’s README describes it as a JavaScript library, so you pick the runner.

Most scrapers need no runner: a plain script is enough. Retries and traces still help when your output is data. A trace recorded on a failed run shows which request or selector broke.

  • Playwright: best for scrapers that need per-context routing, retries and traces.
  • Puppeteer: best for small Chrome-only scripts that need only a launch flag and page.authenticate.

How do you migrate a Puppeteer scraper to Playwright?

You migrate a Puppeteer scraper to Playwright by swapping the launch call, converting selectors to locators, and re-running the script against the same targets to diff the output.

  1. Swap the import and launch call. Replace puppeteer.launch() with chromium.launch() from playwright. Stay on Chromium first so the library is the only variable.
  2. Switch to locators. Swap page.$ and waitForSelector for Playwright locators: page.locator(selector).click() waits for the element on its own. Delete your fixed sleeps.
  3. Move interception to route(). Drop setRequestInterception(true) and register a handler with page.route().
  4. Move per-session options to newContext(). Viewport, user agent, cookies and proxy settings belong on the context.
  5. Re-run against the same targets. Save both scripts’ output as JSON and diff it. You’re done when the record counts and field values match.
Puppeteer callPlaywright callNote
puppeteer.launchchromium.launchImport chromium from playwright
page.$page.locatorReturns a locator, not a handle
waitForSelectorlocator actionsThe action waits itself
setRequestInterceptionroute()No global switch to flip
createBrowserContextnewContextOptions go here
puppeteer.connectconnectOverCDP or connectPick by server type

Four pitfalls break most ports:

  • Strict locators. Playwright’s locator docs say locator operations “will throw an exception if more than one element matches.” Narrow the selector or use first() on purpose.
  • Leftover sleeps. Old setTimeout waits slow the port and hide real wait bugs.
  • evaluate signatures. Check how each library passes arguments into the page function.
  • Default timeouts. Set them explicitly instead of trusting either default.

Verify each call in the current Playwright API reference before you ship.

Playwright vs Puppeteer: which one fits your scraping scenario?

The right choice between Playwright and Puppeteer depends on your scraping scenario, and neither one wins every row below.

  1. QA cross-browser suite: Playwright. One test-oriented API covers Chromium, Firefox and WebKit. A tutorial from Software Testing Tutorials Automation says Playwright “supports multiple browsers (Chromium, Firefox, WebKit)“.
  2. Chrome-only scraping: Puppeteer. Browserless calls it “the simpler option for Chrome-only scraping and automation, with a slightly larger plugin ecosystem.” Stay on it unless a concrete limit appears.
  3. PDF and screenshot generation: either. Snippet 5 shows near-identical code in both libraries, so use whichever is already in your project.
  4. AI agent tooling: Playwright, with a check. Browserless describes Playwright’s MCP server as “an official Microsoft-maintained MCP server.” Read its README on GitHub for the tool list. We can’t point to a Puppeteer MCP server run by the Puppeteer project, so none is listed here.
  5. Existing Puppeteer codebase: stay. A working scraper costs nothing to keep. Migrate when you need Firefox or WebKit, or when hand-written waits keep breaking.
  6. Scraping through a hosted browser: match the host. On a Chromium host reached over CDP, Puppeteer is a natural fit, because connectOverCDP() is Playwright’s lower-fidelity path.

Agents that only need web data can skip browser control. Wayfern’s MCP server is one such route: see live web data for AI agents.

How to check maintenance health and speed

We publish no benchmark numbers, because we have no run to cite. Star counts say little about upkeep, so compare release dates and open issues on the Playwright and Puppeteer GitHub releases pages yourself.

Then rerun your own targets. Record library versions, browser build, hardware, run count and network, and publish the script with the results.

If you’re comparing tools by billing unit, read the web scraping buyer’s guide.

Frequently asked questions

Is Playwright better than Puppeteer for web scraping?

For most new scraping projects, Playwright is the safer default: it supports Chromium, Firefox and WebKit and auto-waits before actions, per Software Testing Tutorials Automation. Puppeteer still fits Chrome-only scraping, PDF and screenshot jobs, and existing codebases. The scenario section maps each case to a pick.

Does Puppeteer support Firefox?

Yes. Puppeteer’s README names Firefox alongside Chrome, even though comparison sites describe Puppeteer as Chrome-focused. Confirm Firefox support for your pinned version before you depend on it.

Is Playwright faster than Puppeteer?

No cited benchmark settles it, so we won’t name a winner. Run both against your own targets with pinned versions, and compare wall-clock time and failure rates. The scraping tasks section has code you can adapt.

Can I use Playwright and Puppeteer with a remote or hosted browser?

Both attach to a remote browser over a WebSocket endpoint, and the connect call is the only code that changes. Test advanced features on the remote connection first. Our guide to anti-detect browser sessions over CDP shows a hosted setup.

Can I migrate a Puppeteer script to Playwright without a rewrite?

You rarely need a full rewrite, but you do need edits. Swap the launch call, convert selectors to locators, then diff the output against the same targets. Hand-written waiting code needs the most attention.

What should you do next?

Today: Pick your row in the scenario list. Then pin your library and browser versions in package.json, so later results are comparable.

This week: Port one script with the mapping table. Or leave the code alone and point it at a remote browser with connectOverCDP() in Playwright or puppeteer.connect() in Puppeteer.

This month: Rerun both libraries against your real targets and keep the results. Playwright vs Puppeteer is a close call on paper, and your own pages settle it.

For the remote-browser step, try Wayfern’s hosted anti-detect Chromium over CDP with either library. Plans and credit rates are on the Wayfern pricing page.

The honest catch: plans start at $19 a month, and Wayfern is Chromium only. Firefox and WebKit runs stay on your own machines.

Sources

Playwright vs Puppeteer: Which Is Better in 2026? · Playwright vs. Puppeteer: An Honest Comparison - Browserless · puppeteer/puppeteer: README · microsoft/playwright-dotnet: README · BrowserType | Playwright · Wayfern API reference · Wayfern API reference · Live Web Data for AI Agents | Wayfern. Every other source is linked where the article cites it.

See us more often in Google

One click marks Wayfern as a preferred source in Google, so our posts can show up more often in your Top Stories, AI Mode and AI Overviews.