playwright、puppeteer 和 selenium-webdriver 都是用于浏览器自动化的主流 npm 包,广泛应用于端到端测试、爬虫、截图生成等场景。它们都能控制真实浏览器执行用户操作,但底层架构、支持的浏览器范围、API 设计和维护策略存在显著差异。playwright 由 Microsoft 开发,支持 Chromium、WebKit 和 Firefox;puppeteer 由 Google 主导,主要聚焦于 Chromium;selenium-webdriver 是 Selenium 项目的一部分,通过 WebDriver 协议支持多语言和多浏览器,但在 Node.js 环境中使用时需额外配置浏览器驱动。
在构建健壮的端到端(E2E)测试套件或实现高级浏览器自动化任务时,playwright、puppeteer 和 selenium-webdriver 是三个主流选择。它们都能操控真实浏览器,但设计理念、功能覆盖和开发体验差异显著。本文从实际工程角度出发,通过代码示例对比关键能力。
puppeteer 仅支持 Chromium 系浏览器(Chrome、Edge 等)。虽然可通过实验性方式连接 Firefox,但官方不推荐用于生产。
// puppeteer: 启动 Chromium
const browser = await puppeteer.launch();
const page = await browser.newPage();
playwright 原生支持 Chromium、WebKit(Safari 内核)和 Firefox,三者 API 行为高度一致。
// playwright: 启动任意浏览器
const { chromium, webkit, firefox } = require('playwright');
const browser = await chromium.launch(); // 或 webkit.launch(), firefox.launch()
const page = await browser.newPage();
selenium-webdriver 理论上支持所有实现 WebDriver 协议 的浏览器(Chrome、Firefox、Safari、Edge),但需为每种浏览器单独安装对应驱动(如 chromedriver、geckodriver)。
// selenium-webdriver: 需显式指定驱动
const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const driver = new Builder()
.forBrowser('chrome')
.setChromeOptions(new chrome.Options())
.build();
💡 关键区别:Playwright 内置所有浏览器二进制文件,开箱即用;Selenium 需手动管理驱动版本兼容性。
现代 Web 应用大量依赖异步加载,可靠的等待策略至关重要。
playwright 提供 自动等待:几乎所有操作(如 click()、fill())会自动等待元素可交互,无需额外代码。
// playwright: 自动等待按钮可点击
await page.click('#submit-button');
// 自动等待输入框就绪
await page.fill('#email', 'test@example.com');
puppeteer 需要 手动等待 元素状态,否则容易因竞态条件失败。
// puppeteer: 必须显式等待
await page.waitForSelector('#submit-button', { visible: true });
await page.click('#submit-button');
await page.waitForFunction(() => document.querySelector('#email').offsetParent !== null);
await page.type('#email', 'test@example.com');
selenium-webdriver 提供 显式等待(Explicit Waits),但需开发者自行编写条件逻辑。
// selenium-webdriver: 使用 WebDriverWait
const { until } = require('selenium-webdriver');
await driver.wait(until.elementLocated({ id: 'submit-button' }), 5000);
const button = await driver.findElement({ id: 'submit-button' });
await button.click();
💡 实践建议:Playwright 的自动等待大幅减少 flaky tests(不稳定测试),而 Puppeteer 和 Selenium 需要更多样板代码确保稳定性。
拦截请求、模拟响应对测试边界条件至关重要。
playwright 支持 路由(Routing),可拦截并修改任意请求。
// playwright: 拦截 API 请求并返回 mock 数据
await page.route('**/api/user', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ name: 'Mock User' })
});
});
await page.goto('/profile');
puppeteer 通过 .setRequestInterception 实现类似功能,但需手动处理请求继续或响应。
// puppeteer: 拦截请求
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
if (interceptedRequest.url().includes('/api/user')) {
interceptedRequest.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ name: 'Mock User' })
});
} else {
interceptedRequest.continue();
}
});
await page.goto('/profile');
selenium-webdriver 原生不支持网络拦截。需借助外部代理(如 BrowserMob Proxy)或浏览器扩展实现,大幅增加复杂度。
// selenium-webdriver: 无法直接拦截,需额外工具链
// 通常需启动独立代理服务并配置浏览器使用该代理
💡 结论:Playwright 和 Puppeteer 在网络控制上远胜 Selenium,尤其适合需要模拟慢速网络、错误响应或认证令牌的场景。
playwright 内置 设备预设(如 iPhone 12、Pixel 5),一键模拟视口、UA 和触摸事件。
// playwright: 使用设备预设
const { devices } = require('playwright');
const iPhone = devices['iPhone 12'];
const context = await browser.newContext({ ...iPhone });
const page = await context.newPage();
puppeteer 需手动设置视口和 UA 字符串。
// puppeteer: 手动配置移动设备
await page.setViewport({ width: 390, height: 844, isMobile: true });
await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) ...');
selenium-webdriver 可通过 Chrome DevTools Protocol (CDP) 或 Firefox 配置 模拟设备,但 API 繁琐且浏览器间不一致。
// selenium-webdriver: 通过 CDP 设置(仅限 Chrome)
await driver.executeCdpCommand('Emulation.setDeviceMetricsOverride', {
width: 390,
height: 844,
mobile: true,
deviceScaleFactor: 2
});
playwright 原生支持 测试隔离:每个测试可拥有独立的浏览器上下文(Context),共享浏览器进程但内存隔离,启动速度快。
// playwright: 每个测试新建上下文
const context = await browser.newContext();
const page = await context.newPage();
// 测试结束后关闭上下文,而非整个浏览器
await context.close();
puppeteer 通常为每个测试启动 全新浏览器实例,资源开销大、速度慢。
// puppeteer: 常见做法(低效)
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 测试结束需关闭整个浏览器
await browser.close();
selenium-webdriver 依赖外部 Grid 或本地驱动管理,无内置并行抽象,需自行设计资源池。
playwright:活跃维护,Microsoft 主导,持续增加新特性(如组件测试、录制器)。puppeteer:Google 维护,稳定但更新节奏放缓,重心转向 Chrome DevTools 集成。selenium-webdriver:Selenium 项目核心部分,长期支持,但 Node.js 版本 API 相对陈旧,缺乏现代 E2E 工具的便利性。| 场景 | 推荐工具 | 理由 |
|---|---|---|
| 跨浏览器 E2E 测试(含 Safari/Firefox) | playwright | 唯一提供 WebKit 和 Firefox 一致支持的现代方案 |
| 仅 Chromium 环境的轻量自动化(如 PDF 生成) | puppeteer | 启动快、API 简洁、社区成熟 |
| 已有 Selenium 基础设施或多语言测试团队 | selenium-webdriver | 与现有 Grid/Hub 无缝集成,语言无关 |
| 需要网络拦截、设备模拟、自动等待等高级特性 | playwright | 开箱即用,减少样板代码 |
| 资源受限环境(如 CI)需快速并行测试 | playwright | 上下文隔离比新建浏览器实例更高效 |
playwright:它解决了 Puppeteer 和 Selenium 的多数痛点,提供更可靠、更简洁的自动化体验。puppeteer:如果你不需要跨浏览器,且已有 Puppeteer 脚本,迁移成本可能不值得。selenium-webdriver:除非你深度依赖 Selenium 生态(如企业级 Grid),否则其繁琐的配置和底层 API 会拖慢开发效率。无论选择哪个,都应结合具体需求权衡——但对大多数现代前端团队而言,Playwright 正迅速成为事实上的标准。
选择 playwright 如果你需要跨浏览器(包括 WebKit 和 Firefox)的一致性测试能力、内置等待机制、网络拦截、设备模拟以及开箱即用的并行测试支持。它特别适合现代 Web 应用的复杂 E2E 测试场景,且希望避免手动管理浏览器驱动或处理竞态条件问题。
选择 puppeteer 如果你的项目仅针对 Chromium 系浏览器(如 Chrome 或 Edge),且需要轻量级、高性能的自动化控制。它在 PDF 生成、页面截图、无头渲染等任务上表现优异,但不支持非 Chromium 内核浏览器,且对现代前端框架的异步等待支持不如 Playwright 完善。
选择 selenium-webdriver 如果你已在使用 Selenium 生态(如 Java/Python 测试套件),或需要与现有 Selenium Grid 集成。它在多语言团队中具有优势,但在 Node.js 中使用时需手动下载和管理浏览器驱动,API 较为底层,缺乏现代 E2E 工具的便捷特性(如自动等待、网络 mocking)。
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.
| Linux | macOS | Windows | |
|---|---|---|---|
| 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?
Playwright has its own test runner for end-to-end tests, we call it Playwright Test.
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.
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.
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.
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.
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.
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?
To learn how to run these Playwright Test examples, check out our getting started docs.
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` });
});
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' });
});
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);
});
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');
});