2023 年把 Web 端自动化从 Selenium 整体迁到 Playwright,迁移本身 2 周,但迁移带来的稳定性提升才是真正目的:等待问题消失、选择器更聪明、并发免费。这篇记录迁移的完整思路和踩过的坑。
一、为什么迁:Selenium 的三个结构性短板
| 短板 | 具体表现 | Playwright 的解法 |
|---|---|---|
| 等待全靠人肉 | 显式等待要自己写 WebDriverWait + 条件,90% 的 flaky 来自"等错了地方" | 自动等待:所有 action 前自动等元素可交互(actionability checks),显式等待变成少数场景 |
| 单线程单浏览器 | 并发要 WebDriverPool + 线程 + 浏览器进程管理,复杂且脆 | 原生并发:async API + 浏览器上下文隔离,context 比 driver 轻得多 |
| 网络是黑盒 | 改请求/拦截响应要靠第三方库硬 hack | route 拦截是一等公民:Mock 响应、改请求头、断言请求,全在 API 里 |
迁移收益最大的一条:Selenium 时代 flaky 用例占失败量的 35%,迁完降到 6%——大头就是等待问题被自动等待吃掉了。
二、定位策略:从"我能写什么"到"最稳定的是什么"
Playwright 的选择器按稳定性重排了优先级:
1 | # 优先级从高到低(团队约定,CI lint 强制) |
get_by_role 是思维转变的关键:它强迫用例从"用户视角"描述元素(“提交按钮"而不是"第三个 div 里的 span”),和可访问性测试天然对齐——按钮没写 accessible name 的用例定位失败,顺带逼出了产品的 a11y 问题。
三、网络拦截:Mock 能力让"环境依赖"消失
1 | # 支付回调 Mock:不用真的调第三方支付,回调想怎么来怎么来 |
原则:UI 用例只验证"给定 X 响应,UI 表现 Y"。数据准备从"调接口造数据"(慢、脏、依赖环境)变成"route 直接给"(快、干净、零依赖)。UI 回归从 40 分钟压到 12 分钟,稳定性反而更高——因为不再和真实数据状态搏斗。
四、并发与报告:免费的规模化
1 | # pytest-playwright 天然支持并发:每个 worker 独立浏览器实例 |
1 | # 失败现场:trace 是迁移后最惊喜的功能 |
trace 回放把"为什么挂"从口口相传变成可视化证据——研发不再说"我这边复现不了",因为 DOM 快照把当时的状态钉死了。
五、迁移踩坑记录
| 坑 | 现象 | 解法 |
|---|---|---|
| 自动等待"等过头" | 元素确实不可交互,用例卡满 timeout(默认 30s) | 区分"慢"和"不会":expect 断言可见性用短 timeout 快速失败 |
| strict mode 违反 | 选择器命中多个元素直接报错 | 这是特性不是 bug——多命中说明选择器太宽,修选择器 |
| iframe 里操作 | 元素定位不到 | frame = page.frame_locator("iframe#pay"); frame.get_by_role(...),Playwright 自动处理 frame 等待 |
| 文件上传 | send_keys 对 input[type=file] 无效 | page.set_input_files("input[type=file]", "a.pdf"),一步到位 |
| 影子 DOM | CSS 选择器穿透不了 | Playwright 的 CSS 引擎原生支持 >>> 穿透 |
六、迁移策略:不要大爆炸
我们的迁移顺序(2 周完成 600 条用例):
- 新用例直接用 Playwright 写(第 1 周,并行跑两套);
- flaky 最重的 20% 先迁(等待问题最严重的那批,收益最大);
- 按模块整体切(一个模块迁完就切,不搞半吊子);
- Selenium 套件冻结(不再接新用例,只修必修的),3 个月后下线。
七、小结
Playwright 迁移的本质是三个转变:等待从人肉到自动(flaky 大头消失)、定位从开发者视角到用户视角(get_by_role + a11y 对齐)、网络从黑盒到可编程(route 拦截让 UI 用例环境独立)。工具选型看 100 分能力,迁移成功看的是策略——新用例先行、flaky 先迁、按模块切换,别搞大爆炸。