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.
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.
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.
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.
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.
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.
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.
Let’s compare a realistic login test across all three.
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();
});
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();
});
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();
}
});
| Concern | playwright | puppeteer | selenium-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) |
All three are actively maintained and production-ready — but Playwright represents the current state of the art for JavaScript-based browser automation.
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.
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.
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.
Playwright is a framework for web automation and testing. It drives Chromium, Firefox, and WebKit with a single API — in your tests, in your scripts, and as a tool for AI agents.
Choose the path that fits your workflow:
| Best for | Install | |
|---|---|---|
| Playwright Test | End-to-end testing | npm init playwright@latest |
| Playwright CLI | Coding agents (Claude Code, Copilot) | npm i -g @playwright/cli@latest |
| Playwright MCP | AI agents and LLM-driven automation | npx @playwright/mcp@latest |
| Playwright Library | Browser automation scripts | npm i playwright |
| VS Code Extension | Test authoring and debugging in VS Code | Install from Marketplace |
Playwright Test is a full-featured test runner built for end-to-end testing. It runs tests across Chromium, Firefox, and WebKit with full browser isolation, auto-waiting, and web-first assertions.
npm init playwright@latest
Or add manually:
npm i -D @playwright/test
npx playwright install
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');
await page.getByRole('link', { name: 'Get started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
npx playwright test
Tests run in parallel across all configured browsers, in headless mode by default. Each test gets a fresh browser context — full isolation with near-zero overhead.
Auto-wait and web-first assertions. No artificial timeouts. Playwright waits for elements to be actionable, and assertions automatically retry until conditions are met.
Locators. Find elements with resilient locators that mirror how users see the page:
page.getByRole('button', { name: 'Submit' })
page.getByLabel('Email')
page.getByPlaceholder('Search...')
page.getByTestId('login-form')
Test isolation. Each test runs in its own browser context — equivalent to a fresh browser profile. Save authentication state once and reuse it across tests:
// Save state after login
await page.context().storageState({ path: 'auth.json' });
// Reuse in other tests
test.use({ storageState: 'auth.json' });
Tracing. Capture execution traces, screenshots, and videos on failure. Inspect every action, DOM snapshot, network request, and console message in the Trace Viewer:
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry',
},
});
npx playwright show-trace trace.zip
Parallelism. Tests run in parallel by default across all configured browsers.
Playwright CLI is a command-line interface for browser automation designed for coding agents. It's more token-efficient than MCP — commands avoid loading large tool schemas and accessibility trees into the model context.
npm install -g @playwright/cli@latest
Optionally install skills for richer agent integration:
playwright-cli install --skills
Point your coding agent at a task:
Test the "add todo" flow on https://demo.playwright.dev/todomvc using playwright-cli.
Take screenshots for all successful and failing scenarios.
Or run commands directly:
playwright-cli open https://demo.playwright.dev/todomvc/ --headed
playwright-cli type "Buy groceries"
playwright-cli press Enter
playwright-cli screenshot
Use playwright-cli show to open a visual dashboard with live screencast previews of all running browser sessions. Click any session to zoom in and take remote control.
playwright-cli show
Full CLI documentation | GitHub
The Playwright MCP server gives AI agents full browser control through the Model Context Protocol. Agents interact with pages using structured accessibility snapshots — no vision models or screenshots required.
Add to your MCP client (VS Code, Cursor, Claude Desktop, Windsurf, etc.):
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
One-click install for VS Code:
For Claude Code:
claude mcp add playwright npx @playwright/mcp@latest
Ask your AI assistant to interact with any web page:
Navigate to https://demo.playwright.dev/todomvc and add a few todo items.
The agent sees the page as a structured accessibility tree:
- heading "todos" [level=1]
- textbox "What needs to be done?" [ref=e5]
- listitem:
- checkbox "Toggle Todo" [ref=e10]
- text: "Buy groceries"
It uses element refs like e5 and e10 to click, type, and interact — deterministically and without visual ambiguity. Tools cover navigation, form filling, screenshots, network mocking, storage management, and more.
Full MCP documentation | GitHub
Use playwright as a library for browser automation scripts — web scraping, PDF generation, screenshot capture, and any workflow that needs programmatic browser control without a test runner.
npm i playwright
Take a screenshot:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev/');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();
Generate a PDF:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev/');
await page.pdf({ path: 'page.pdf', format: 'A4' });
await browser.close();
Emulate a mobile device:
import { chromium, devices } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext(devices['iPhone 15']);
const page = await context.newPage();
await page.goto('https://playwright.dev/');
await page.screenshot({ path: 'mobile.png' });
await browser.close();
Intercept network requests:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
await page.goto('https://playwright.dev/');
await browser.close();
Library documentation | API reference
The Playwright VS Code extension brings test running, debugging, and code generation directly into your editor.
Run and debug tests from the editor with a single click. Set breakpoints, inspect variables, and step through test execution with a live browser view.
Generate tests with CodeGen. Click "Record new" to open a browser — navigate and interact with your app while Playwright writes the test code for you.
Pick locators. Hover over any element in the browser to see the best available locator, then click to copy it to your clipboard.
Trace Viewer integration. Enable "Show Trace Viewer" in the sidebar to get a full execution trace after each test run — DOM snapshots, network requests, console logs, and screenshots at every step.
Install the extension | VS Code guide
| Linux | macOS | Windows | |
|---|---|---|---|
| Chromium1 148.0.7778.96 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit 26.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox 150.0.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless and headed execution on all platforms. 1 Uses Chrome for Testing by default.
Playwright is also available for Python, .NET, and Java.