Playwright vs Selenium 4——2024 年 E2E 框架选型的完整对比
Playwright vs Selenium 4——2024 年 E2E 框架选型的完整对比
适读人群:技术负责人 / 测试架构师 | 阅读时长:约 15 分钟 | 核心价值:给出有决策依据的框架选型建议,避免选错框架后的高昂迁移成本
选错框架的代价
两年前,我认识一个朋友小陈,他所在的公司做金融 SaaS 产品,Java 技术栈,团队里测试工程师五人。他们当时新招了一个"E2E 测试专家"老赵,老赵强烈推荐 Cypress,理由是"社区活跃,文档好,调试体验一流"。
于是他们花了三个月把 Cypress 测试体系建起来,写了近 200 个测试用例。
然后问题来了:
- Cypress 原生只支持 JavaScript/TypeScript,他们团队全员 Java,没人会 JS,维护成本剧增
- Cypress 不支持 Safari,但他们的产品很多 Mac 用户,Safari 兼容性必须覆盖
- Cypress 不支持跨标签页操作,他们的产品偏偏有大量跨标签页场景
三个月后,他们不得不推翻 Cypress 体系,重新用 Playwright + Java 重写。
这就是选型决策没做好的代价。今天我来帮你把这个对比做清楚。
2024 年主流 E2E 框架格局
目前市场上主流的 E2E 测试框架有四个:
| 框架 | 语言支持 | 浏览器支持 | 定位 |
|---|---|---|---|
| Selenium 4 | Java/Python/JS/C#/Ruby | 所有主流 | 老牌通用框架 |
| Playwright | Java/Python/JS/C# | Chromium/Firefox/WebKit | 现代化框架 |
| Cypress | JS/TS | Chromium/Firefox/Edge | 前端友好 |
| WebdriverIO | JS/TS | 所有主流 | JS 生态 |
本文聚焦 Playwright vs Selenium 4 的对比,因为这是 Java 技术栈团队最常面临的二选一。
核心维度对比
1. 等待机制
这是两个框架最根本的设计差异。
Selenium 4 的方式:
// Selenium 需要手动管理等待
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 显式等待——等待元素可见
WebElement button = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("submit-btn"))
);
button.click();
// 等待可点击
wait.until(ExpectedConditions.elementToBeClickable(By.id("submit-btn"))).click();
// 等待文本出现
wait.until(ExpectedConditions.textToBePresentInElementLocated(
By.cssSelector(".status-message"), "操作成功"
));
// 很多人图省事,用隐式等待——但这会带来性能问题和难以排查的竞态条件
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));Playwright 的方式:
// Playwright 所有操作都内置自动等待
// 这一行代码等价于 Selenium 里三行 WebDriverWait
page.locator("#submit-btn").click(); // 自动等待元素可见、可点击、稳定
// 断言也自动重试
assertThat(page.locator(".status-message")).containsText("操作成功"); // 自动重试5秒这个差异直接影响 Flaky Test 的发生率。我们团队从 Selenium 迁移到 Playwright 后,Flaky Test 比例从约 15% 降到不足 3%。
2. 浏览器支持范围
Selenium 4: 支持所有主流浏览器,包括 IE 11(通过特殊驱动),还支持移动浏览器(通过 Appium 集成)。
Playwright: 支持 Chromium(Chrome/Edge 内核)、Firefox、WebKit(Safari 内核)。不支持 IE。
如果你的产品需要支持 IE,用 Selenium。2024 年大多数产品已经放弃 IE,Playwright 三大引擎已经够用。
3. 网络拦截能力
// Selenium 4 的网络拦截(需要 CDP,只有 Chrome 支持)
DevTools devTools = ((HasDevTools) driver).getDevTools();
devTools.createSession();
devTools.send(Network.enable(Optional.empty(), Optional.empty(), Optional.empty()));
devTools.addListener(Network.requestIntercepted(), interceptedRequest -> {
// 拦截逻辑复杂,且只支持 Chrome
});// Playwright 的网络拦截——简洁,所有浏览器都支持
page.route("/api/user/**", route -> {
route.fulfill(new Route.FulfillOptions()
.setStatus(200)
.setContentType("application/json")
.setBody("{\"name\": \"测试用户\", \"role\": \"admin\"}")
);
});Playwright 的网络拦截 API 是一等公民,设计非常优雅,且对三个浏览器都有效。
4. 并行执行
// Selenium 需要自己管理 WebDriver 实例的创建和分配
// 通常需要 Selenium Grid 或者 ThreadLocal<WebDriver>
private static final ThreadLocal<WebDriver> driverThreadLocal = new ThreadLocal<>();
@BeforeEach
void setUp() {
WebDriver driver = new ChromeDriver();
driverThreadLocal.set(driver);
}
protected WebDriver getDriver() {
return driverThreadLocal.get();
}
@AfterEach
void tearDown() {
WebDriver driver = driverThreadLocal.get();
if (driver != null) {
driver.quit();
driverThreadLocal.remove();
}
}// Playwright 天然支持并行
// 每个测试方法有独立的 BrowserContext(相当于独立的浏览器会话)
// 即使在同一 JVM 进程中,多个测试并发运行也互不干扰
@BeforeEach
void setUp() {
context = browser.newContext(); // 超轻量,毫秒级创建
page = context.newPage();
}Playwright 的 BrowserContext 是浏览器上下文的隔离单元,创建极其轻量,相当于浏览器的无痕模式窗口。多个测试并发共享同一个 Browser 实例,但各自有独立的 BrowserContext,互不干扰。
5. 调试体验
| 特性 | Selenium 4 | Playwright |
|---|---|---|
| 内置代码生成器 | 无(需要第三方插件) | playwright codegen 命令 |
| 失败截图 | 需手动配置 | 配置一行开启 |
| 操作录像 | 不支持 | 原生支持 |
| Trace 回放 | 不支持 | Trace Viewer |
| Inspector | Chrome DevTools | Playwright Inspector + DevTools |
6. 生态和社区
Selenium 的优势:
- 历史悠久,超过 15 年,文档和 StackOverflow 解答极其丰富
- 支持几乎所有语言
- Selenium Grid 成熟,大规模并行测试方案完善
- 很多公司的 QA 团队都有 Selenium 经验,招人更容易
Playwright 的优势:
- 微软在积极维护,更新频繁
- 文档质量高,API 设计统一
- GitHub Star 增长速度远超 Selenium
- 新特性(AI 辅助定位器等)持续推出
我的选型建议
经过这两年的实践,我给出以下选型决策树:
优先选 Playwright,如果满足以下条件:
- 新项目,没有历史包袱
- 团队用 Java、Python、JS、C# 其中之一
- 不需要支持 IE 11
- 希望减少 Flaky Test,降低维护成本
- 想要开箱即用的调试工具(视频、Trace)
选 Selenium 4,如果满足以下条件:
- 已有大量 Selenium 测试代码,迁移成本高于维护成本
- 需要支持 IE 11 或非标准浏览器
- 需要对接 Selenium Grid 等已有基础设施
- 团队对 Selenium 非常熟悉,Playwright 学习成本难以接受
考虑共存方案(特殊场景):
- 核心业务流用 Playwright(稳定、快)
- 遗留功能保持 Selenium(不动存量代码)
- 通过共同的测试基类封装,对测试代码透明
真实迁移案例:从 Selenium 到 Playwright
我参与了一个中型项目的迁移,大概 180 个 Selenium 测试用例,用了 2 周完成迁移,分享几个经验:
迁移前的准备
// 先把所有定位器收拢到 Page Object,避免迁移时到处改
// Selenium 版本的 Page Object
public class LoginPageSelenium {
private WebDriver driver;
private By usernameLocator = By.id("username");
private By passwordLocator = By.id("password");
private By submitLocator = By.cssSelector("button[type='submit']");
public void login(String username, String password) {
driver.findElement(usernameLocator).sendKeys(username);
driver.findElement(passwordLocator).sendKeys(password);
driver.findElement(submitLocator).click();
}
}// 迁移后的 Playwright 版本
public class LoginPagePlaywright {
private Page page;
private Locator usernameInput = page.locator("#username");
private Locator passwordInput = page.locator("#password");
private Locator submitButton = page.locator("button[type='submit']");
public DashboardPage login(String username, String password) {
usernameInput.fill(username);
passwordInput.fill(password);
submitButton.click();
return new DashboardPage(page);
}
}主要的 API 映射关系:
| Selenium | Playwright |
|---|---|
driver.findElement(By.id("x")) | page.locator("#x") |
element.sendKeys("text") | locator.fill("text") |
element.click() | locator.click() |
element.getText() | locator.textContent() |
new WebDriverWait(...).until(...) | assertThat(locator).hasText(...) |
driver.navigate().to(url) | page.navigate(url) |
driver.quit() | context.close() / browser.close() |
踩坑实录
坑一:Selenium 的 isDisplayed() 没有对应方法
现象: 迁移时找不到 Playwright 里对应 element.isDisplayed() 的方法。
解法: Playwright 用 locator.isVisible() 替代,但语义有细微差别——isVisible() 同步返回布尔值(不等待),在断言中要用 assertThat(locator).isVisible()(自动重试)。
// Selenium
boolean visible = element.isDisplayed();
assertTrue(visible);
// Playwright(推荐方式,自动重试)
assertThat(page.locator(".element")).isVisible();
// Playwright(立即检查,不重试)
boolean visible = page.locator(".element").isVisible();坑二:Selenium 的 Actions 类操作在 Playwright 里的对应
现象: 鼠标悬停、拖拽等复杂操作在 Selenium 用 Actions 类,迁移时不知道怎么改。
解法:
// Selenium
Actions actions = new Actions(driver);
actions.moveToElement(element).perform(); // 悬停
actions.dragAndDrop(source, target).perform(); // 拖拽
// Playwright
page.locator(".menu-item").hover(); // 悬停
page.locator(".draggable").dragTo(page.locator(".droppable")); // 拖拽坑三:frame 处理方式不同
现象: Selenium 用 driver.switchTo().frame() 切换 iframe,Playwright 没有对应的 switchTo。
解法: Playwright 用 frameLocator 直接在 frame 内定位,不需要切换上下文:
// Selenium
driver.switchTo().frame("payment-iframe");
driver.findElement(By.id("card-number")).sendKeys("4111111111111111");
driver.switchTo().defaultContent(); // 操作完要切换回去
// Playwright
page.frameLocator("#payment-iframe")
.locator("#card-number")
.fill("4111111111111111");
// 不需要切回,下次操作其他元素照常小结
从实际项目经验来看,2024 年新项目我无条件推荐 Playwright。它的自动等待机制、网络拦截能力、Trace 调试工具,是在真实项目中每天都能感受到的生产力提升。
Selenium 4 加入了很多新特性(相对定位器、CDP 协议、WebDriver BiDi),依然是成熟可靠的选择,尤其适合有历史存量代码的团队。
迁移前必读:评估迁移成本的清单
决定是否从 Selenium 迁移到 Playwright,需要客观评估:
存量代码评估
# 统计现有 Selenium 测试数量
find . -name "*.java" -exec grep -l "WebDriver\|Selenium" {} \; | wc -l
# 统计 Page Object 数量(如果有的话)
find . -name "*Page.java" | wc -l
# 统计直接使用 driver.findElement 的次数(没有 POM 的测试,迁移成本更高)
grep -r "driver.findElement" . --include="*.java" | wc -l迁移成本参考:
- 有完整 POM 架构:每个 Page Object 约 2-4 小时
- 没有 POM,直接写在测试里:每个测试文件约 1-2 小时
- 有复杂的自定义 Wait 封装:额外 1-2 天拆解
特殊功能清单
检查以下功能是否在项目中使用:
| 功能 | Selenium | Playwright | 迁移难度 |
|---|---|---|---|
| 基本操作(点击、输入) | ✓ | ✓ | 低 |
| 截图 | ✓ | ✓ | 低 |
| iframe 操作 | switchTo().frame() | frameLocator() | 低 |
| 文件上传 | sendKeys() | setInputFiles() | 低 |
| 拖拽 | Actions | locator.dragTo() | 低 |
| 多浏览器窗口 | switchTo().window() | context.pages() | 中 |
| CDP 操作 | 有限支持 | 完整支持 | 中 |
| IE 11 支持 | ✓ | 不支持 | 高(无法迁移) |
| Selenium Grid | ✓ | 需改造 | 中 |
并存策略:如何让两个框架共存
在大型项目中,全量迁移风险太高。以下是可行的并存方案:
方案一:按测试层级区分
├── tests/
│ ├── e2e-playwright/ # 新 E2E 测试,用 Playwright
│ │ ├── LoginTest.java
│ │ └── CheckoutTest.java
│ └── e2e-selenium/ # 存量 E2E 测试,保留 Selenium
│ ├── LegacyPaymentTest.java
│ └── LegacyUserTest.javaCI 中两套测试独立运行:
# GitHub Actions
- name: Run Playwright Tests
run: mvn test -pl e2e-playwright
- name: Run Selenium Tests
run: mvn test -pl e2e-selenium方案二:增量替换策略
每次修复或改造某个功能时,顺手把对应的 Selenium 测试改为 Playwright。不需要专门立项做迁移,自然渗透:
规则:
1. 修 BUG 时,如果涉及到某个 Selenium 测试,顺手改成 Playwright
2. 新功能必须用 Playwright 写测试
3. 老 Selenium 测试不主动删除,等待自然淘汰(功能下线时删)预计 6-12 个月内,存量 Selenium 测试会自然减少到可以一次性清理的程度。
下一篇我写 Selenium 4 的现代化实战,让你在不得不用 Selenium 的场景里,也能用上它的新特性。
