playwright vs puppeteer vs selenium-webdriver
Browser Automation and End-to-End Testing in JavaScript
playwrightpuppeteerselenium-webdriverSimilar Packages:
Browser Automation and End-to-End Testing in JavaScript

playwright, puppeteer, and selenium-webdriver are all browser automation libraries used primarily for end-to-end testing, web scraping, and automated UI interaction. They allow developers to control real browsers programmatically, simulate user actions, and verify application behavior. While they share similar goals, they differ significantly in architecture, supported browsers, API design, and ecosystem maturity.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
playwright25,548,05180,1393.89 MB54517 days agoApache-2.0
puppeteer6,812,05193,03962.8 kB292a day agoApache-2.0
selenium-webdriver2,184,30833,76418.4 MB2286 days agoApache-2.0

Browser Automation Showdown: Playwright vs Puppeteer vs Selenium WebDriver

When you need to automate real browsers — whether for end-to-end tests, visual regression, or scraping dynamic sites — three major players dominate the JavaScript ecosystem: Playwright, Puppeteer, and Selenium WebDriver. All let you script browser interactions, but their architectures, capabilities, and developer experiences diverge sharply. Let’s compare them head-to-head.

🌐 Browser Support: One Engine vs Three vs Everything

playwright supports three rendering engines out of the box: Chromium, Firefox, and WebKit (Safari’s engine). This means you can test how your app behaves across all major desktop browsers using a single API.

// playwright: Switch browsers with one config change
const { chromium, firefox, webkit } = require('playwright');

const browser = await firefox.launch(); // or chromium / webkit
const page = await browser.newPage();
await page.goto('https://example.com');

puppeteer only controls Chromium-based browsers (Chrome, Edge, Chromium). There’s no official support for Firefox or Safari.

// puppeteer: Chromium-only
const puppeteer = require('puppeteer');

const browser = await puppeteer.launch(); // always Chromium
const page = await browser.newPage();
await page.goto('https://example.com');

selenium-webdriver supports virtually any browser via vendor-provided drivers (ChromeDriver, GeckoDriver, etc.). You can even test legacy IE if needed — though it requires separate driver binaries and complex setup.

// selenium-webdriver: Explicit driver per browser
const { Builder, By, until } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');

let driver = await new Builder()
  .forBrowser('chrome')
  .setChromeOptions(new chrome.Options())
  .build();

await driver.get('https://example.com');

💡 Key takeaway: Need WebKit or Firefox? Only Playwright delivers first-party, zero-config support. Selenium gives broader coverage but at the cost of complexity.

⏳ Waiting and Reliability: Auto-Wait vs Manual Sync

Flaky tests often stem from poor synchronization — clicking before an element loads, for example. How each library handles this is critical.

playwright has built-in auto-waiting. Methods like click(), fill(), and goto() automatically wait for elements to be actionable (visible, enabled, stable).

// playwright: No explicit waits needed
await page.goto('/login');
await page.fill('#email', 'user@example.com'); // Waits for #email to be ready
await page.click('#submit'); // Waits for button to be clickable

puppeteer requires manual waiting. You must use waitForSelector(), waitForFunction(), or similar to avoid race conditions.

// puppeteer: Manual sync required
await page.goto('/login');
await page.waitForSelector('#email'); // Must wait explicitly
await page.type('#email', 'user@example.com');
await page.click('#submit');

selenium-webdriver also lacks auto-waiting. You rely on WebDriverWait with expected conditions — but note: the JavaScript version doesn’t have a direct equivalent; you typically use driver.wait() with custom conditions.

// selenium-webdriver: Explicit waits
await driver.get('/login');
await driver.wait(until.elementLocated(By.id('email')), 5000);
await driver.findElement(By.id('email')).sendKeys('user@example.com');
await driver.findElement(By.id('submit')).click();

💡 Key takeaway: Playwright reduces flakiness by default. With Puppeteer or Selenium, you’ll write more boilerplate to achieve the same reliability.

🧪 Test Isolation and Emulation: Contexts vs Pages vs Sessions

Isolating tests prevents state leakage (e.g., cookies from one test affecting another).

playwright uses browser contexts — lightweight, isolated environments that share a browser instance but have separate cookies, storage, and permissions. Creating a new context is fast (~1ms).

// playwright: Per-test context
const browser = await chromium.launch();
const context = await browser.newContext(); // Isolated session
const page = await context.newPage();
// ... test logic
await context.close(); // Clean up instantly

puppeteer relies on separate pages or browser instances for isolation. Pages share cookies/storage unless you launch a new browser (which is slow) or use incognito mode (less convenient).

// puppeteer: Incognito for isolation
const browser = await puppeteer.launch();
const context = await browser.createIncognitoBrowserContext();
const page = await context.newPage();
// ... test logic
await context.close();

selenium-webdriver typically uses one driver per test, meaning you launch/quit the entire browser for each test — which is slow and resource-heavy.

// selenium-webdriver: Full browser per test
let driver = await new Builder().forBrowser('chrome').build();
// ... test logic
await driver.quit(); // Entire browser shuts down

💡 Key takeaway: Playwright’s context model enables fast, truly isolated tests. Others force trade-offs between speed and cleanliness.

🎥 Debugging and Observability: Built-in Tooling

When tests fail, you need visibility.

playwright includes trace viewer, video recording, and screenshot-on-failure out of the box. You can replay every action with DOM snapshots, network logs, and console output.

// playwright.config.js
module.exports = {
  use: {
    trace: 'on-first-retry',
    video: 'on-first-retry',
    screenshot: 'only-on-failure'
  }
};

puppeteer offers basic screenshot/PDF APIs but no integrated debugging suite. You’d need third-party tools for traces or videos.

// puppeteer: Manual screenshots only
await page.screenshot({ path: 'error.png' });

selenium-webdriver provides no built-in media capture. You must implement screenshot logic yourself and integrate external tools for video or tracing.

// selenium-webdriver: DIY screenshots
const fs = require('fs');
const image = await driver.takeScreenshot();
fs.writeFileSync('error.png', image, 'base64');

💡 Key takeaway: Playwright ships with a full observability stack. The others leave you to build your own.

🧰 Architecture and Setup Complexity

playwright bundles its own browser binaries. Installation is one command (npm install playwright), and it works offline. No external dependencies.

puppeteer also bundles Chromium by default (npm install puppeteer), so setup is equally simple — but only for Chromium.

selenium-webdriver does not bundle drivers. You must separately install ChromeDriver, GeckoDriver, etc., and manage version compatibility. This adds operational overhead.

# selenium-webdriver requires manual driver setup
npm install selenium-webdriver
# Then download matching chromedriver.exe manually or via webdriver-manager

💡 Key takeaway: Playwright and Puppeteer “just work.” Selenium demands infra maintenance.

🔁 Real-World Example: Login Flow

Let’s compare a realistic login test across all three.

Playwright (concise, reliable)

const { test, expect } = require('@playwright/test');

test('login succeeds', async ({ page }) => {
  await page.goto('/login');
  await page.fill('#email', 'user@test.com');
  await page.fill('#password', 'secret');
  await page.click('#submit');
  await expect(page.locator('#welcome')).toBeVisible();
});

Puppeteer (requires explicit waits)

const puppeteer = require('puppeteer');

it('login succeeds', async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('/login');
  await page.waitForSelector('#email');
  await page.type('#email', 'user@test.com');
  await page.type('#password', 'secret');
  await page.click('#submit');
  await page.waitForSelector('#welcome');
  const isVisible = await page.$eval('#welcome', el => el.offsetParent !== null);
  expect(isVisible).toBe(true);
  await browser.close();
});

Selenium WebDriver (verbose, driver-dependent)

const { Builder, By, until } = require('selenium-webdriver');

it('login succeeds', async () => {
  let driver = await new Builder().forBrowser('chrome').build();
  try {
    await driver.get('/login');
    await driver.wait(until.elementLocated(By.id('email')), 5000);
    await driver.findElement(By.id('email')).sendKeys('user@test.com');
    await driver.findElement(By.id('password')).sendKeys('secret');
    await driver.findElement(By.id('submit')).click();
    await driver.wait(until.elementLocated(By.id('welcome')), 5000);
    const isDisplayed = await driver.findElement(By.id('welcome')).isDisplayed();
    expect(isDisplayed).toBe(true);
  } finally {
    await driver.quit();
  }
});

📊 Summary: When to Use Which

Concernplaywrightpuppeteerselenium-webdriver
Browser Coverage✅ Chromium, Firefox, WebKit❌ Chromium only✅ Any (with drivers)
Auto-Waiting✅ Built-in❌ Manual❌ Manual
Test Isolation✅ Fast contexts⚠️ Incognito or new browser❌ Full browser per test
Debugging Tools✅ Trace, video, screenshots⚠️ Screenshots only❌ DIY
Setup Simplicity✅ Bundled browsers✅ Bundled Chromium❌ External drivers required
Enterprise Integration⚠️ Growing❌ Limited✅ Mature (Grid, cross-language)

💡 Final Recommendation

  • Start with Playwright if you’re building a new test suite. Its cross-browser support, reliability, and tooling make it the most future-proof choice for modern web apps.
  • Stick with Puppeteer only if you’re already invested in Chromium-only workflows (e.g., PDF generation) and don’t need multi-browser coverage.
  • Use Selenium WebDriver only if you’re locked into a Selenium Grid, must support legacy browsers, or have cross-language test requirements.

All three are actively maintained and production-ready — but Playwright represents the current state of the art for JavaScript-based browser automation.

How to Choose: playwright vs puppeteer vs selenium-webdriver
  • playwright:

    Choose playwright if you need cross-browser support (Chromium, Firefox, WebKit), built-in auto-waiting, reliable test isolation with context-level emulation, and modern tooling like trace viewer and video recording. It’s ideal for teams building robust, maintainable E2E test suites that must work consistently across multiple browsers without flakiness.

  • puppeteer:

    Choose puppeteer if your testing or automation needs are focused exclusively on Chromium-based browsers (Chrome, Edge) and you want a lightweight, fast-starting solution with tight integration into the Chrome DevTools Protocol. It works well for PDF generation, screenshot capture, or internal tooling where multi-browser support isn’t required.

  • selenium-webdriver:

    Choose selenium-webdriver if you must integrate with an existing Selenium Grid infrastructure, require language interoperability (Java, Python, C#, etc.), or need to run tests against legacy browser versions not supported by newer tools. It’s best suited for enterprise environments with established Selenium workflows and cross-language test suites.

README for playwright

🎭 Playwright

npm version Chromium version Firefox version WebKit version Join Discord

Documentation | API reference

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API. Playwright is built to enable cross-browser web automation that is ever-green, capable, reliable and fast.

LinuxmacOSWindows
Chromium 143.0.7499.4:white_check_mark::white_check_mark::white_check_mark:
WebKit 26.0:white_check_mark::white_check_mark::white_check_mark:
Firefox 144.0.2:white_check_mark::white_check_mark::white_check_mark:

Headless execution is supported for all browsers on all platforms. Check out system requirements for details.

Looking for Playwright for Python, .NET, or Java?

Installation

Playwright has its own test runner for end-to-end tests, we call it Playwright Test.

Using init command

The easiest way to get started with Playwright Test is to run the init command.

# Run from your project's root directory
npm init playwright@latest
# Or create a new project
npm init playwright@latest new-project

This will create a configuration file, optionally add examples, a GitHub Action workflow and a first test example.spec.ts. You can now jump directly to writing assertions section.

Manually

Add dependency and install browsers.

npm i -D @playwright/test
# install supported browsers
npx playwright install

You can optionally install only selected browsers, see install browsers for more details. Or you can install no browsers at all and use existing browser channels.

Capabilities

Resilient • No flaky tests

Auto-wait. Playwright waits for elements to be actionable prior to performing actions. It also has a rich set of introspection events. The combination of the two eliminates the need for artificial timeouts - a primary cause of flaky tests.

Web-first assertions. Playwright assertions are created specifically for the dynamic web. Checks are automatically retried until the necessary conditions are met.

Tracing. Configure test retry strategy, capture execution trace, videos and screenshots to eliminate flakes.

No trade-offs • No limits

Browsers run web content belonging to different origins in different processes. Playwright is aligned with the architecture of the modern browsers and runs tests out-of-process. This makes Playwright free of the typical in-process test runner limitations.

Multiple everything. Test scenarios that span multiple tabs, multiple origins and multiple users. Create scenarios with different contexts for different users and run them against your server, all in one test.

Trusted events. Hover elements, interact with dynamic controls and produce trusted events. Playwright uses real browser input pipeline indistinguishable from the real user.

Test frames, pierce Shadow DOM. Playwright selectors pierce shadow DOM and allow entering frames seamlessly.

Full isolation • Fast execution

Browser contexts. Playwright creates a browser context for each test. Browser context is equivalent to a brand new browser profile. This delivers full test isolation with zero overhead. Creating a new browser context only takes a handful of milliseconds.

Log in once. Save the authentication state of the context and reuse it in all the tests. This bypasses repetitive log-in operations in each test, yet delivers full isolation of independent tests.

Powerful Tooling

Codegen. Generate tests by recording your actions. Save them into any language.

Playwright inspector. Inspect page, generate selectors, step through the test execution, see click points and explore execution logs.

Trace Viewer. Capture all the information to investigate the test failure. Playwright trace contains test execution screencast, live DOM snapshots, action explorer, test source and many more.

Looking for Playwright for TypeScript, JavaScript, Python, .NET, or Java?

Examples

To learn how to run these Playwright Test examples, check out our getting started docs.

Page screenshot

This code snippet navigates to Playwright homepage and saves a screenshot.

import { test } from '@playwright/test';

test('Page Screenshot', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await page.screenshot({ path: `example.png` });
});

Mobile and geolocation

This snippet emulates Mobile Safari on a device at given geolocation, navigates to maps.google.com, performs the action and takes a screenshot.

import { test, devices } from '@playwright/test';

test.use({
  ...devices['iPhone 13 Pro'],
  locale: 'en-US',
  geolocation: { longitude: 12.492507, latitude: 41.889938 },
  permissions: ['geolocation'],
})

test('Mobile and geolocation', async ({ page }) => {
  await page.goto('https://maps.google.com');
  await page.getByText('Your location').click();
  await page.waitForRequest(/.*preview\/pwa/);
  await page.screenshot({ path: 'colosseum-iphone.png' });
});

Evaluate in browser context

This code snippet navigates to example.com, and executes a script in the page context.

import { test } from '@playwright/test';

test('Evaluate in browser context', async ({ page }) => {
  await page.goto('https://www.example.com/');
  const dimensions = await page.evaluate(() => {
    return {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
      deviceScaleFactor: window.devicePixelRatio
    }
  });
  console.log(dimensions);
});

Intercept network requests

This code snippet sets up request routing for a page to log all network requests.

import { test } from '@playwright/test';

test('Intercept network requests', async ({ page }) => {
  // Log and continue all network requests
  await page.route('**', route => {
    console.log(route.request().url());
    route.continue();
  });
  await page.goto('http://todomvc.com');
});

Resources