去年这个时候,我们还在被 Selenium 的“玄学失败”折磨得睡不好觉。
每周一晨会,大家聊得最多的不是业务需求,而是:“昨天那几个脚本又随机挂了,谁帮忙看看?”
直到一个周五深夜,我第 N 次调试那个经典的 “Element not interactable” 错误——明明元素就在页面上,就是点不了。那一刻我意识到:不是我们写得不够好,而是工具已经跟不上时代了。
三个月后,我们完成了核心回归套件从 Selenium 到 Playwright 的迁移。结果远超预期:
-
回归时间缩短 60%
-
脚本维护成本下降 70%
-
最重要的是——测试同学终于能在周五准点下班了。
今天想和大家聊聊这段迁移经历,不吹不黑,只讲实话。
一、为什么非换不可?Selenium 的“中年危机”
Selenium 曾是 Web 自动化的代名词,这点没人否认。但今天的前端早已不是十年前的样子:SPA、动态加载、微前端、Shadow DOM……而 Selenium 的底层架构,还停留在“请求-响应”的静态时代。
我们最头疼的三个问题:
1. 随机失败像“薛定谔的猫”
同一个脚本,今天跑通,明天就挂。排查半天,发现只是网络慢了 200ms。
我们至少 30% 的时间花在“救火”上,而不是设计更有效的测试。
2. API 层级太深,代码臃肿
想等一个元素出现?得这样写:
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "submit-btn")) )
还没开始测逻辑,光等待就写了三四行。更别说多层嵌套后的可读性灾难。
3. 执行速度慢,资源消耗大
我们的核心回归套件(约 300 个用例)在 Selenium 上要跑 2.5 小时。每次上线前,必须提前半天启动测试,严重影响交付节奏。
二、Playwright 快在哪?关键在架构
很多人问:“都是控制浏览器,差别真有那么大?”
答案藏在通信机制里。
Selenium:三层转发,层层损耗
你的代码 → WebDriver 协议 → 浏览器驱动 → 浏览器
每一步都有序列化、反序列化、网络延迟。就像寄快递,中间经手人越多,越容易丢件、延误。
Playwright:直连 DevTools Protocol
你的代码 → 浏览器(通过 CDP)
没有中间代理,直接与浏览器内核对话。这不仅是“快一点”,而是质的飞跃。
我们实测了一个典型场景:打开电商首页 → 搜索“手机” → 加入购物车,重复 100 次:
| 指标 | Selenium | Playwright | 提升 |
|---|---|---|---|
| 平均耗时 | 4.2s | 1.3s | 3.2 倍 |
| 标准差 | 0.8s | 0.1s | 稳定性大幅提升 |
三、迁移实战:如何平稳过渡?
我们没搞“一刀切”,而是分阶段推进。
阶段一:双轨并行,验证价值(第1个月)新需求:全部用 Playwright 编写
旧脚本:优先迁移高频失败的核心模块(如登录、支付)
对比验证:同一功能两套实现,看稳定性、速度、维护成本
以“用户登录”为例:
Selenium 版本(45 行):
def login(username, password):
driver.get(LOGIN_URL)
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "username")))
driver.find_element(By.ID, "username").clear()
driver.find_element(By.ID, "username").send_keys(username) # ... 类似重复操作 ...
WebDriverWait(driver, 10).until(EC.url_contains("/dashboard"))
Playwright 版本(15 行):
async def login(page, username, password):
await page.goto(LOGIN_URL)
await page.fill("#username", username)
await page.fill("#password", password)
await page.click("#login-btn")
await page.wait_for_url("**/dashboard")
代码量减少 67%,且从未再出现“元素不可交互”类问题。
阶段二:释放 Playwright 的“超能力”(第2–3个月)迁移完成后,我们开始做以前“想做但做不到”的事:
✅ 智能等待,告别 time.sleep()
# 等待元素可见
await page.locator(".product-card").first.wait_for(state="visible")
# 等待特定接口返回
async with page.expect_response("**/api/products") as response_info:
await page.click("#load-more")
✅ 多浏览器 + 移动端一键切换
# 同一测试,轻松跑在 Chromium / Firefox / WebKit
# 甚至模拟 iPhone 14 Pro context = await browser.new_context(device="iPhone 14 Pro")
✅ 内置网络拦截,Mock 不再依赖第三方
# 支付流程无需真实调用
await page.route("**/api/payment/**", lambda route: route.fulfill( status=200,
body='{"status":"success"}' ))
这些能力,在 Selenium 里要么做不到,要么需要额外集成大量工具链。
四、踩过的坑:避雷指南
坑1:选择器太脆弱
初期我们用录制工具生成的选择器:
# ❌ 容易因 DOM 结构变动而失效 "div.main > div:nth-child(3) > button"
后来我们和前端约定:关键交互元素加 data-testid 属性。
# ✅ 稳定、语义清晰 "[data-testid='checkout-button']"
坑2:并行执行爆内存
第一次尝试并发 50 个测试,机器直接 OOM。
解决方案:用 asyncio.Semaphore 控制并发数。
semaphore = asyncio.Semaphore(10) # 最多10个并发
坑3:动态内容加载判断不准
对无限滚动页面,我们改用“元素数量不再增长”或“监听特定 API 响应”来判断加载完成,而非硬编码等待。
五、真实数据对比
我们对商品搜索流程做了完整压测(搜索 → 翻页5次 → 点击加载更多):
| 指标 | Selenium | Playwright | 提升 |
|---|---|---|---|
| 总耗时 | 28.5s | 9.2s | 3.1 倍 |
| 内存占用 | 850MB | 420MB | ↓50% |
| 成功率 | 87% | 99.8% | 近乎零失败 |
更惊喜的是 Trace Viewer 功能——测试失败后,可以直接回放整个操作过程,像看视频一样定位问题。很多偶现 Bug,靠它一锤定音。
六、如果你也想迁移:我的建议路线图
第1周:装 Playwright,用 playwright codegen 录一个简单脚本,感受差异
第1月:搭建基础框架,迁移最不稳定的 10% 用例
第2–3月:新需求全用 Playwright,逐步替换旧脚本
长期:结合 Git 变更智能调度、视觉回归、性能基线等深度优化
七、写在最后:工具升级,更是思维升级
迁移完成后,团队里一位干了8年的老测试说:
“以前我是 Selenium 的‘消防员’,天天救火;现在我是 Playwright 的‘建筑师’,能真正设计质量体系。”
这句话让我感触很深。
从 Selenium 到 Playwright,表面是换了个工具,实质是:
-
从 被动等待 到 智能同步
-
从 单点执行 到 并行协作
-
从 功能覆盖 到 体验保障
-
从 脚本维护者 到 质量赋能者
当然,Playwright 也不是万能药。如果你还在维护 IE 项目,或者重度依赖 Selenium Grid 的分布式架构,可能还需谨慎评估。
但对于绝大多数现代 Web 应用——尤其是 React/Vue/Angular 架构——Playwright 带来的效率提升,是革命性的。
最近面试时,我会问候选人:“如果让你重新设计自动化框架,你会怎么做?”
那些能清晰说出 Playwright 优势,并展示实际落地思考的人,总能让我眼前一亮。
工具不会决定你的上限,但选对工具,能让你的价值被看见。
而这,或许才是技术人最该追求的事。

97

被折叠的 条评论
为什么被折叠?



