Puppeteer vs Selenium: choose for your automation workflow

Compare a direct Node.js browser library with Selenium's WebDriver languages and Grid ecosystem, using a complete inline extraction example.


Choose Puppeteer when a direct Node.js Chrome/Firefox workflow fits your task and service. Choose Selenium when its WebDriver languages, browser drivers or established Grid/testing infrastructure are the stronger fit. Neither is universally faster or more reliable.

Browser, language and infrastructure

Puppeteer’s JavaScript/TypeScript APIs use its supported browser/protocol combinations. Check pairings and limitations, not just whether a binary launches.

Selenium WebDriver supports local/remote browser control through its language bindings and driver ecosystem. Grid is distributed execution infrastructure, not simply a pool of browser-library pages. Existing multi-language/Grid investment may be a real reason to stay.

Compare Chrome sessions step by step

Run npm install puppeteer selenium-webdriver in your project. We will give both libraries the same delayed page, wait for Ready, and read its text. Selenium uses Puppeteer’s Chrome binary here, while Selenium Manager handles driver matching. The complete script below combines the two separate sessions.

1. Define the shared test page

Import the two libraries and define HTML whose result changes from Loading to Ready. This keeps the comparison focused on API usage rather than different sites.

import puppeteer from "puppeteer";
import { Builder, By, until } from "selenium-webdriver";
import chrome from "selenium-webdriver/chrome.js";

const html = `
    <p id="result">Loading</p>

    <script>
        setTimeout(() => {
            const result =
                document.querySelector("#result");

            result.textContent = "Ready";
        }, 100);
    </script>
`;

2. Read the result with Puppeteer

Create a page, set its content, wait for Ready, and extract the result. Keep browser cleanup in finally.

const browser = await puppeteer.launch({
    headless: true,
});

try {
    const page = await browser.newPage();

    await page.setContent(html);

    await page.waitForFunction(
        () => {
            const result =
                document.querySelector("#result");

            return result?.textContent === "Ready";
        },
        { timeout: 5000 },
    );

    const result = await page.$eval(
        "#result",
        (element) => element.textContent,
    );

    console.log(result); // Ready
} finally {
    await browser.close();
}

3. Configure Selenium’s Chrome session

Choose the same Chrome executable and enable headless mode. Selenium creates its own driver session; it does not reuse Puppeteer’s page object.

const options = new chrome.Options()
    .setChromeBinaryPath(await puppeteer.executablePath())
    .addArguments("--headless=new");

const driver = await new Builder()
    .forBrowser("chrome")
    .setChromeOptions(options)
    .build();

4. Load the page and wait with Selenium

Navigate to the inline data URL, find the result element, and wait for its text. Quit the driver in finally so the session is released on failure too.

try {
    await driver.get(
        "data:text/html;charset=utf-8," +
            encodeURIComponent(html),
    );

    const element = await driver.findElement(
        By.id("result"),
    );

    await driver.wait(
        until.elementTextIs(element, "Ready"),
        5000,
    );

    console.log(await element.getText()); // Ready
} finally {
    await driver.quit();
}

Complete script

Here are all the pieces together, including setup and cleanup. Save this as compare.mjs and run node compare.mjs.

import puppeteer from "puppeteer";
import { Builder, By, until } from "selenium-webdriver";
import chrome from "selenium-webdriver/chrome.js";

const html = `
    <p id="result">Loading</p>

    <script>
        setTimeout(() => {
            const result =
                document.querySelector("#result");

            result.textContent = "Ready";
        }, 100);
    </script>
`;

const browser = await puppeteer.launch({
    headless: true,
});

try {
    const page = await browser.newPage();

    await page.setContent(html);

    await page.waitForFunction(
        () => {
            const result =
                document.querySelector("#result");

            return result?.textContent === "Ready";
        },
        { timeout: 5000 },
    );

    const result = await page.$eval(
        "#result",
        (element) => element.textContent,
    );

    console.log(result); // Ready
} finally {
    await browser.close();
}

const options = new chrome.Options()
    .setChromeBinaryPath(await puppeteer.executablePath())
    .addArguments("--headless=new");

const driver = await new Builder()
    .forBrowser("chrome")
    .setChromeOptions(options)
    .build();

try {
    await driver.get(
        "data:text/html;charset=utf-8," +
            encodeURIComponent(html),
    );

    const element = await driver.findElement(
        By.id("result"),
    );

    await driver.wait(
        until.elementTextIs(element, "Ready"),
        5000,
    );

    console.log(await element.getText()); // Ready
} finally {
    await driver.quit();
}

The sessions are separate: do not interchange page and driver. The inline data URL gives each browser the same small page. This compares how the APIs read content, not their speed. Selenium Manager handles driver discovery and installation; its cache must be available to your runtime user.

Waits and failure policy

Both need a meaningful result condition. Navigation completion does not prove delayed data arrived, and a completed action does not prove a side effect succeeded. Compare Puppeteer locators and Selenium explicit waits against the actual supported contracts.

Stale references and disconnected sessions require fresh ownership checks with either tool. Bound timeouts and retries; do not duplicate a submission after an uncertain outcome. See waiting.

Service versus test organization

A small capture/extraction service may benefit from Puppeteer’s direct lifecycle. A multi-language testing organization may benefit more from Selenium’s existing bindings, reporting and driver expertise. Neither supplies every operational requirement alone.

With either tool, manage browser updates, fonts, sandbox support, session state and cleanup. Selenium adds driver management, while Puppeteer still needs a compatible browser. Test your workflow in the environment where it will run.

Choose requirements and existing infrastructure first. Measure representative tasks only when browser versions, workload, warmup, concurrency and platform are controlled. For a new test-runner workflow read Playwright; for static HTML read Cheerio.

Keep Reading

Puppeteer Guides

All Guides →