ScreenshotNeo

BlogComparisons

Selenium vs. Puppeteer: Which Should You Use for Web Automation?

Choose Selenium for language choice, browser coverage, or remote WebDriver. Choose Puppeteer for JavaScript-first Chrome or Firefox automation. Compare setup, protocols, code, and tradeoffs.

By the ScreenshotNeo team4 October 202611 min read

Short answer: Choose Selenium when you need a language-neutral WebDriver interface, several target browsers, or execution through a remote Selenium Server. Choose Puppeteer when your codebase is JavaScript and your work centers on Chrome or on Puppeteer’s documented Chrome and Firefox support. If a particular browser protocol feature matters, check that feature’s current support in your chosen browser and binding before committing.

Both tools automate real browsers. The practical choice usually comes down to the language your team uses, the browsers you must support, and whether your existing infrastructure is built around WebDriver or Puppeteer. There is no universal speed winner established by the available evidence; measure your own equivalent workload if runtime is decisive.

1. What Selenium and Puppeteer are

Selenium WebDriver is a language-neutral browser automation interface. A language binding sends commands through a browser-specific driver. Selenium describes WebDriver as a W3C Recommendation and supports local browser sessions as well as sessions on a remote machine through Selenium Server.

Puppeteer is a JavaScript library for controlling Chrome or Firefox. It uses Chrome DevTools Protocol (CDP) for Chrome by default and WebDriver BiDi for Firefox by default. Puppeteer also supports BiDi with Chrome, though CDP remains the default there because not all CDP features are available through BiDi.

Decision Selenium Puppeteer
Language Language-neutral WebDriver API with language bindings. JavaScript library, suited to Node.js projects.
Browser approach Browser-specific WebDriver implementations; check support for your exact browser and feature. Officially documents Chrome and Firefox automation.
Protocol WebDriver; WebDriver BiDi is developing as a bidirectional, standards-based path. Chrome uses CDP by default; Firefox uses BiDi by default. BiDi is also available for Chrome.
Setup Install a language binding and have the target browser and its driver available. Current Selenium setup may manage drivers, but the browser and environment still matter. The puppeteer package downloads a compatible Chrome for Testing build by default. puppeteer-core leaves browser management to you.
Natural fit Mixed-language teams, browser diversity, remote WebDriver, or an existing Selenium/Grid setup. JavaScript-first automation focused on supported Chrome/Firefox capabilities.

2. Which should you use for web automation?

Choose Selenium when

  • Your automation needs to be written in a language other than JavaScript, or you want language choice to remain independent of the browser automation interface.
  • You must target several browsers and want to use their WebDriver implementations through a common interface. Confirm the exact browser, version, and feature support in the Selenium browser documentation.
  • You need to run sessions remotely through Selenium Server, including an existing Grid or remote execution setup.
  • Your project already has Selenium bindings, driver management, and test infrastructure that work.

Choose Puppeteer when

  • Your automation is already written in JavaScript or Node.js.
  • Chrome is your main target and CDP-specific capabilities are useful to the task.
  • Your target is Chrome or Firefox and Puppeteer’s current protocol feature coverage meets your needs.
  • You prefer a package that downloads a compatible Chrome build as part of its normal installation workflow.

Use a requirement checklist before choosing

  1. List target browsers and versions. Do not write “cross-browser” as a requirement without naming the browsers and important features.
  2. List required browser capabilities. Include downloads, permissions, network events, console messages, authentication, and any protocol-specific behavior your workflow depends on.
  3. Account for the team language. A JavaScript-only API is a constraint when the application or automation suite is maintained in another language.
  4. Check execution topology. Decide whether the browser runs beside the test process, in a managed container, or through a remote server.
  5. Prototype one representative flow. Include the slowest or most failure-prone page, not only a simple navigation.
  6. Compare operational work. Consider browser installation, version pinning, CI dependencies, parallel sessions, logs, screenshots, and failure diagnosis.

3. Protocols and browser support

WebDriver and CDP

WebDriver is Selenium’s standard browser-control interface. CDP is Chrome’s DevTools Protocol, which Puppeteer uses for Chrome by default. These are different protocol surfaces; an API or event available through one is not automatically available through the other.

What WebDriver BiDi changes

WebDriver BiDi adds bidirectional communication over a WebSocket, allowing automation to receive browser events such as network activity, console messages, and JavaScript errors. Selenium describes BiDi as a standards-based cross-browser path and says its implementation is transitioning from classic WebDriver while aiming to preserve compatibility. Treat availability as feature- and binding-specific.

Puppeteer’s FAQ documents production-ready BiDi support for Chrome and Firefox. Chrome still defaults to CDP because the BiDi path does not expose every CDP feature. Puppeteer documents BiDi-specific unsupported features as well, so “supports BiDi” does not mean complete parity across protocols.

When you depend on BiDi, verify the browser, protocol, library version, language binding, and feature together. Recheck the official support pages when upgrading: protocol coverage and defaults can change.

4. Runnable example: Selenium with Java

This minimal example opens a page, waits for the document title to be nonempty, prints it, and closes the browser even if an operation fails. It uses Selenium Manager through current Selenium Java releases to resolve a driver for a locally installed browser. Pin Selenium and browser versions in a reproducible project; consult the Selenium getting-started guide for current setup details.

// Maven dependency: org.seleniumhq.selenium:selenium-java
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

public class SeleniumExample {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        try {
            driver.get("https://example.com");
            new WebDriverWait(driver, Duration.ofSeconds(15))
                .until(d -> !d.getTitle().isBlank());
            System.out.println(driver.getTitle());
        } finally {
            driver.quit();
        }
    }
}

Run it with the Selenium Java dependency on the classpath, for example in a Maven project after adding the dependency. The browser must be installed and runnable in the environment. To use Firefox, replace ChromeDriver with FirefoxDriver and satisfy that browser’s environment requirements.

Remote Selenium session

For a Selenium Server endpoint, create a remote driver and pass browser options. Start and configure the server separately; the endpoint and credentials depend on your deployment.

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URI;

ChromeOptions options = new ChromeOptions();
WebDriver driver = new RemoteWebDriver(
    URI.create("http://localhost:4444").toURL(), options
);
try {
    driver.get("https://example.com");
    System.out.println(driver.getTitle());
} finally {
    driver.quit();
}

5. Runnable example: Puppeteer with Node.js

Create a project and install Puppeteer. The puppeteer package downloads a compatible Chrome for Testing build by default; if install scripts are blocked, follow the official installation guide.

npm init -y
npm install puppeteer

Save this as capture.mjs and run node capture.mjs. It navigates, waits for the page’s load event, prints the title, saves a screenshot, and closes the browser in a finally block.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', {
    waitUntil: 'load',
    timeout: 30_000,
  });
  console.log(await page.title());
  await page.screenshot({ path: 'example.png', fullPage: true });
} finally {
  await browser.close();
}

For a managed local browser or a remote browser connection, install puppeteer-core and provide the executable path or connection endpoint required by that environment. It does not download Chrome or assume the regular package’s browser defaults.

6. Waiting, sessions, and configuration

Most automation failures are synchronization problems. Choose a wait condition that describes the state your next action needs; a fixed sleep can be too short on a slow run and waste time on a fast one.

Need Selenium Puppeteer
Navigate driver.get(url); configure page-load strategy through browser options if needed. page.goto(url, { waitUntil, timeout }).
Wait for a target Use WebDriverWait with an explicit condition, such as element visibility or clickability. Use locator waits or page.waitForSelector().
Set viewport Use browser-specific options or the window management API; support depends on the browser. Use page.setViewport({ width, height, deviceScaleFactor }).
Run headless Set the browser’s headless option where supported. Set headless: true in puppeteer.launch().
Reuse or isolate state Manage browser profiles and sessions explicitly; quit sessions when finished. Create pages or browser contexts for isolation; close the browser when finished.
Remote browser Use RemoteWebDriver with the Selenium Server URL and capabilities. Use puppeteer-core and connect or launch against the managed browser using its supported endpoint.

Keep browser options explicit in CI: headless mode, window size, proxy and certificate settings where appropriate, download directories, permissions, and logging. Avoid sharing one mutable browser profile across unrelated jobs. Store credentials outside source code and avoid logging cookies or authorization headers.

7. Performance, reliability, and cost

Performance

No controlled, equivalent Selenium-versus-Puppeteer benchmark is established here, so a blanket claim that one is faster would be unsupported. Runtime depends on browser startup, page behavior, wait conditions, network, machine resources, protocol operations, and parallelization. If speed determines the decision, run the same flow against the same browser version and machine, with the same data, timeouts, and concurrency. Measure both total runtime and failure rate.

Reliability

  • Pin library and browser versions where reproducibility matters, and upgrade them deliberately.
  • Use explicit waits for page state; use bounded timeouts and report what condition timed out.
  • Always close the page, session, or browser in cleanup code.
  • Capture logs and a screenshot or other diagnostic artifact on failure, while removing secrets and personal data.
  • Run a representative flow in the same container or operating environment used by CI.
  • For BiDi or CDP features, verify support against the exact browser and library combination.

Cost and infrastructure

The libraries themselves are not the whole operating cost. Budget for CI or server compute, browser binaries, storage for artifacts, maintenance of browser versions, and engineering time for flaky flows. Selenium Server or a managed remote browser adds infrastructure or service cost; a locally launched Puppeteer browser consumes the resources of the machine running Node.js. Compare the cost of your actual deployment rather than assuming one library is cheaper.

8. Troubleshooting

Symptom Likely cause What to do
Selenium reports that it cannot locate or start a driver. The browser or driver is missing, incompatible, inaccessible, or blocked by environment configuration. Confirm the browser is installed and runnable; check Selenium’s driver troubleshooting documentation; inspect driver logs and permissions. For remote execution, verify the server URL and capabilities.
Puppeteer says “Could not find Chrome”. Package installation scripts were blocked, or the expected browser cache is unavailable. Install the browser with npx puppeteer browsers install, or configure the package manager to allow Puppeteer’s installation script. For a managed browser, use puppeteer-core and configure its executable or endpoint.
Navigation times out. The page is slow, the chosen lifecycle event never occurs, or the target remains busy. Check network reachability and page behavior. Choose a suitable navigation wait condition, then wait explicitly for the element or state the task needs. Raise the timeout only when the longer bound is intentional.
Element lookup fails intermittently. The DOM is not ready, the selector changed, or the element is inside a frame or shadow root. Wait for visibility or another required state, validate the selector against the current page, and switch to the correct frame or use an API that handles the relevant DOM boundary.
Browser launches locally but fails in CI. Missing system dependencies, sandbox restrictions, unavailable display, file permissions, or a different browser build. Use a CI image that meets browser system requirements, install required dependencies, use headless mode when appropriate, and inspect the browser’s stderr. Avoid copying local-only executable paths into CI.
BiDi event or command is unavailable. The feature is not implemented for that browser, protocol, binding, or version. Check the current support matrix and version-specific documentation. Use the supported protocol path for the feature or choose a tool/browser combination that exposes it.
Tests pass alone but fail in parallel. Shared profiles, ports, files, accounts, or rate limits are colliding. Isolate browser contexts and test data, allocate unique output paths, and cap concurrency to available CPU and memory.

9. Screenshoting without managing browser automation

If the job is specifically to capture a website screenshot or PDF, a browser automation library may be more machinery than the task requires. ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It accepts a URL in one GET request and returns PNG, JPEG, WebP, or PDF output. Its parameter names also work with those used by other screenshot APIs, which can make switching easier.

Or skip the browser setup

Use the [ScreenshotNeo API documentation](https://screenshotneo.com/docs/) for the full request options. Replace the example target with the page you need and provide an API key.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

In Node.js environments without Bun, write the response bytes with Node’s node:fs/promises module:

import { writeFile } from 'node:fs/promises';
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify page verdict and billing through headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every feature is on every plan; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

10. FAQ

Can Puppeteer automate Firefox?

Yes. Puppeteer’s official FAQ documents Chrome and Firefox support. Firefox uses WebDriver BiDi by default; confirm that the specific capabilities you need are supported.

Can Selenium be used with JavaScript?

Yes. Selenium offers language bindings, including JavaScript. Its advantage is that WebDriver is not tied to one programming language.

Does Puppeteer require a separate Chrome installation?

The regular puppeteer package downloads a compatible Chrome for Testing build by default. puppeteer-core does not download a browser and is intended for managed or remote browser setups.

Which is better for scraping?

Choose based on the language, browser, and interaction requirements of the site. Check the site’s terms and access rules; some sites restrict automated access.

Which is better for a single screenshot?

Use Selenium or Puppeteer if you need custom browser interactions as part of a larger automation workflow. For a URL-to-image or PDF capture, ScreenshotNeo provides a direct API and MCP tools without requiring you to operate a browser session.

Sources