ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

别再硬扛Selenium了:8款更高效的浏览器自动化替代工具

别再硬扛Selenium了:8款更高效的浏览器自动化替代工具 上了一天的班又没别班。白天例行版本回归UI自动化脚本在 Chrome 更新后报废了一批晚上本想早点回去结果有台执行机的 WebDriver 又和浏览器版本对不上。相信只要是跟“Selenium 自动化脚本”打过交道的朋友看到这个描述基本就想叹气。Selenium 确实是 Web 自动化的老前辈能跑通绝大多数浏览器操作但“能跑”和“好不好维护”完全是两码事。尤其在页面交互越来越复杂的今天坚持“Selenium 手写一堆显式等待、外加处理各种驱动版本问题”的方案写出来的脚本很自然就会变成低效脚本的代名词。这篇文章我打算盘一盘 8 个真正能在日常场景里帮你省事的 Selenium 替代工具。目标很明确不是让你把现有全部脚本重写一遍而是希望你在下次新建自动化项目、或者在老项目维护成本实在压不住的时候能清楚知道有哪些更好的选择。文中会谈工具的核心思路、自己上手用的感受以及替换时最容易踩的坑适合正在维护 UI 自动化脚本的测试开发、也适合想做网页自动化的 Python 或 Node 选手参考。1. 先聊聊现状为什么 Selenium 脚本越写越累1.1 低效脚本通常不是 Selenium 的锅而是架构和生态造成的我见过不少团队一上来就批判 Selenium 慢、不稳定但平心而论Selenium 的底层协议 WebDriver 设计得非常早它把“浏览器操作”抽象成了简单指令这种设计在当时绝对先进。问题出在后来的生态演进上前端框架从 jQuery 一步步走到 React/Vue页面早就不是“刷新后等 2 秒再操作”的传统形态了而是大量异步渲染、局部更新、CSS 动画切换。Selenium 解决这些事情的办法仍停留在最早期的模型里只能靠写脚本的人自己判断“什么时候可以操作元素”。于是代码里最常出现的东西就变成了time.sleep(2)或者WebDriverWait(...).until(...)。一两处没什么十个用例二十个用例下来整套脚本的等待策略就开始互相打架等多了浪费时间等少了偶发失败。这还不算那些浏览器驱动意外崩溃、Chrome 自动更新后找不到对应 chromedriver 的糟心事。自动化脚本本来应该代替人做重复劳动最后反而成了需要人天天伺候的“大爷”这种局面想不低效都难。1.2 旧项目里最常见的三个“效率黑洞”我总结了一下凡是半死不活的老自动化项目基本都同时踩中了下面三个坑等待逻辑混乱到处是无脑 sleep用例总执行时间被硬生生拉长短则多出两成长则翻倍。选择器过度耦合xpath 里写满了div[3]/span[2]这种层级依赖前端一改结构脚本马上崩要么就是 CSS class 是动态生成的每次刷新都换。环境依赖严重集中在执行机必须在装了指定版本浏览器的机器上才能跑驱动缺失、系统弹窗、权限问题全都要逐台排查。这三个问题单看每一个都觉得“忍忍也能过”但组合在一起维护成本就会像滚雪球一样膨胀。所以后面我盘点的这些替代工具核心逻辑都高度一致把等待自动化、选择器做得更智能、把环境依赖尽量收敛掉。区别只是它们在“多大程度上替你操心”上做得不一样。2. 选替代工具前先想明白这三点2.1 你的脚本是“长期测试资产”还是“一次性抓取任务”这是第一个要先问自己的问题。如果只是临时爬个页面、保存点数据那其实不用考虑太重的测试框架直接选个轻量浏览器自动化库就够了。如果要构建的是持续运行几个月、几百条用例的回归测试资产那就必须认真考虑自动等待、结果报告、重试机制、失败排查这些能力。把一次性任务硬做成标准测试框架是内耗用轻量库去硬撑长期测试资产更是灾难。2.2 核心团队掌握的编程语言是什么Selenium 覆盖语言很广Python、Java、C#、Ruby、JavaScript 都有官方绑定但很多替代工具对不同语言的支持深度并不一样。比如 Cypress 只在 Node 生态里比较好用Puppeteer 本质就是 Node 库Playwright 虽然也提供 Java/Python 等语言版本但很多调试配套工具跟 Node 结合得更顺手。如果团队主力是 Java硬把项目切换成 Cypress 就要让全员学会一套前端工具链这个迁移成本很现实。2.3 你要跑的“浏览器”到底指哪些“浏览器自动化”这个词听起来简单但在不同场景下含义完全不一样。传统 Web 页面回归要关心 Chrome/Firefox/Safari 的兼容性混合 App 里嵌的是 WebView桌面自动化经常还要跟系统原生窗口打交道。Selenium 的生态优势在于它理论上哪都能碰一下但替代工具多半有明确边界有的只支持 Chromium有的强制绑定自家执行引擎有的对移动端支持得更好。做技术选型时先把这个边界理清楚能帮你直接排除掉一半选项。3. 8 款替代工具我按“能给你省多少事”分了三档3.1 第一档从协议层就改变游戏规则的 Playwright 和 CypressPlaywright是微软开源的浏览器自动化方案它的核心技术不是 WebDriver而是一套自己的浏览器调试协议对接层。这意味着团队可以彻底丢开“下载 chromedriver、保证驱动版本和浏览器版本一致”这类琐事。Playwright 在安装时会自动把浏览器下载到本地固定目录还允许你直接在playwright install时指定浏览器版本执行环境基本做到了“代码到哪浏览器到哪”。实际写 Playwright 测试脚本时最直观的感受是很多以前要自己用显式等待硬扛的场景现在直接用page.goto()page.click()就行。它默认开启自动等待元素可操作时才会执行下一步再也不需要给每个点击前面插几秒 sleep。它还内置了page.waitForSelector这种底层方法但绝大多数情况下不需要手动调用。早期我迁移项目前也担心这种“过于聪明”的自动等待会不会把问题掩盖掉结果用了几周发现它监控的真实可操作性比如元素 visible、stable、enabled比人肉 sleep 可靠得多。Cypress则是一种完全不同思路的路线。它跑测试时不是从外部向浏览器发命令而是直接注入到浏览器内部运行所以你能以极快的速度拿到页面里的真实运行时对象。它的语法对测试人员极其友好比如cy.visit(/login) cy.get(#username).type(admin) cy.get(#password).type(123456) cy.contains(button, 登录).click() cy.url().should(include, /dashboard)这里不需要 webdriver 或驱动管理所有命令都在 Cypress 自己的执行器里失败时还能直接重放录像、查看每一步之前的 DOM 快照。对于只测自己业务系统的纯 Web 前端来说Cypress 的体验非常舒服。它的限制也很出名只能在测试运行时的同一浏览器上下文里执行不能真正跨两个 tab 或跨域做复杂模拟如果目的是“驱动真实浏览器来覆盖用户场景的整套流程”那它可能不适合当底层引擎。3.2 第二档保留 WebDriver 思想但把体验打磨过的 WebdriverIO 和 Nightwatch.js如果你因为历史原因必须兼容老 WebDriver 协议又不想继续维护 Selenium Java/Python 那套老代码可以考虑 WebdriverIO。它本质是个 JavaScript 测试框架底层既能跑 WebDriver 协议也支持用 devtools 协议直连浏览器。WebdriverIO 最大的改进是把原来那些要在 Selenium 里手工拼凑的东西做成了现成能力自动重试、自动等待、强类型接口、appium 可共用一套语法跑 App 测试。它还内置了 TestRunner跟 Cucumber、Mocha、Jasmine 这些都能无缝配合。它的写法是典型的链式 API比如await browser.url(https://example.com/login) await $(#username).setValue(admin) await $(#password).setValue(123456) await $(button[typesubmit]).click() await expect(browser).toHaveUrlContaining(/dashboard)这里$的选择器能做到智能等待命令执行前元素没有出现在 DOM 里时会自动等待一段时间不像传统 selenium 那样直接给你个NoSuchElementException。对于从 Selenium 迁移到 Node 的团队WebdriverIO 的学习曲线很平滑同时因为它的命令清单沿用了 WebDriver 的格式很多老概念也能直接平移过来。Nightwatch.js是最早一批用 Node 写 WebDriver 测试的框架之一直接基于 W3C WebDriver 协议。它的特点是内置了测试运行器、断言库和 HTML 报告不用整一大堆插件配置相对集中。这些年它的存在感不像 Playwright 那么强但如果你接手的是“上面只能跑 WebDriver 协议、又一定要用 Node 写用例”的存量项目它仍是个可选项。提示Nightwatch 和 Selenium 一样需要处理 driver 服务所以它并不是在“省事”维度上最突出的工具。我把它排在第二档的原因主要是它把 Selenium 思维迁到 Node 里时代码组织确实整齐不少对老项目友好。3.3 第三档特定场景下很香的 Puppeteer、TestCafe 和 Appium 系方案Puppeteer如果你想聊“脚本”绕不开的就是 Puppeteer。它是 Google 官方出品的 Chrome DevTools Protocol 封装库只支持 Chromium 内核。但它做爬虫、页面截图、PDF 生成、性能数据采集、设备老化场景下的长跑压测脚本都比 Selenium 顺手得多。原因很简单它直接就长在 Chrome 里不需要额外 driver而且对浏览器的控制粒度非常细。比如一段最简单的无头浏览器截图const puppeteer require(puppeteer); (async () { const browser await puppeteer.launch({ headless: true }); const page await browser.newPage(); await page.setViewport({ width: 1280, height: 720 }); await page.goto(https://example.com, { waitUntil: networkidle2 }); await page.screenshot({ path: home.png }); await browser.close(); })();用 Selenium 写同样逻辑时需要管理 chromedriver、初始化 WebDriver、使用driver.save_screenshot还要处理自身等待逻辑Puppeteer 则更像一个“浏览器遥控器”。尤其在监控页面是否出现内存泄漏、长页面滚动是否异常这类设备老化测试任务里Puppeteer 提供的 CDP 接口可以直接拿到 performance 指标、JS 堆内存快照这是 WebDriver 协议很难替代的。它最大的毛病也很明确只支持 Chromium 内核想要跑 Firefox 或者 Safari 就没戏了所以它更适合干脏活累活不适合当企业级跨浏览器回归的唯一底座。TestCafe属于那种“低调但好用”的工具。它最大的特色是无需 WebDriver、无需额外浏览器驱动直接用浏览器自带的远程调试端口对接。写测试时可以非常依赖 Node 环境测试代码执行在 Node 进程里浏览器只负责渲染和操作。这意味着 CI 环境里不用为了装 chromedriver 折腾镜像跑起来也比较稳。TestCafe 也有自动等待机制和对 iframe、多标签页、下载文件的处理对于很多怕麻烦的测试团队来说它完全可以替代 Selenium 完成绝大多数回归任务。它的测试读起来更接近自然语言import { Selector } from testcafe; fixture(登录功能).page(https://example.com/login); test(输入正确账号密码后跳转, async t { await t .typeText(#username, admin) .typeText(#password, 123456) .click(#submit) .expect(Selector(#welcome).visible).ok(); });跟 Selenium 相比它省掉的东西一眼就能看出来没有显式wait、没有 driver setup、失败时自动截图和录屏也是一条命令的事。TestCafe 的社区活跃度不如 Playwright但稳定性和跨浏览器兼容性都很靠谱。Appium严格来说不是 Web 自动化的替代品但如果你在“Web 自动化”之外还要覆盖移动端 App 里的 WebView 场景很多 Selenium 出来的团队最终会考虑用 Appium。它沿用了 WebDriver 协议只是把 session 扩展成移动端的 capability。好处是团队知识能复用坏处是它同样要处理“驱动版本匹配、环境配置复杂”的老问题。它的价值在于全局统一同一个写脚本的模型覆盖 Web 页面、iOS/Android 原生控件和混合应用中的 H5 页面。它更适合被当成“Web 自动化之外的补充”而不是纯粹替代 Selenium 的技术。3.4 顺带一提老牌 Watir 还值得用吗说起 Selenium 替代品很多老 Ruby 玩家会提 Watir。它的思想是把 WebDriver 再封装一层更人性化的 Ruby DSL让人能用browser.text_field(id: username).set(admin)这种非常自然的表达去驱动浏览器。Watir 的历史比很多工具都长但现在它的维护节奏和周边工具已经不太能跟上现代前端测试需求。除非你维护的是古董级 Ruby 自动化框架否则我并不推荐新项目再选它。注意把 Watir 列进来更多是为了帮你避坑——在搜索引擎里它依然能搜到大量老教程照着写很容易就把项目带进维护死角。如果你因为团队原因必须走 Ruby 技术栈更合理的方向是参考 Watir 和 Selenium Ruby binding 各自的当前状态再做一次技术选型而不是默认沿用它。4. 把最头疼的三件事分别用替代工具落地一遍4.1 自动等待从“人工估算”变成“浏览器真实状态反馈”这是我在所有替代工具里最看重的改进。拿自动化里的老段子来说写 Selenium 脚本的新手第一课是“元素找不到就加 time.sleep”写到后期发现执行时间越来越长再补显式等待。替代工具的理解不一样它们认为“可操作”应该由浏览器时刻反馈而不是你站在浏览器门外瞎猜。以 Playwright 的 Python API 为例from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch() page browser.new_page() page.goto(https://example.com/login) page.fill(#username, admin) page.fill(#password, 123456) page.click(text登录) page.wait_for_url(**/dashboard) browser.close()留意到没整段脚本里没有任何 sleep。page.fill()会自己去等这个输入框可编辑如果页面加载失败或者元素一直没出现它会在超时后给出清晰的报错快照。对比 Selenium 的send_keys经常因为“元素存在但不可编辑”而失败这种自动等待基本是为了现代 SPA 页面设计的。4.2 失败排查视频回放、Trace、DOM 快照到底怎么帮人省时间普通的 Selenium 脚本失败后你要面对一条ElementNotInteractableException的堆栈外加自己截的几张图很难定位到前一步到底发生了什么。替代工具在这方面直接往“开发者工具化”方向做。Cypress 能在测试失败时自动展示每一步对应的 DOM 快照和网络请求记录。你用鼠标点一下时间线就能看到点击按钮前一刻页面长什么样、网络请求是否发出、控制台有没有报错。这个“回到过去看现场”的能力对排查偶然性失败帮助巨大能省掉大把“复现一下试试”的沟通时间。Playwright 更进一步提供了 Trace Viewer。跑测试时可以开启trace: on-first-retry或trace: retain-on-failure失败后得到一个 zip 文件在浏览器里打开就能看到完整的测试时间线包括每个操作前后的 DOM 快照、网络请求、控制台日志甚至浏览器内部执行的 sourcemap。你不需要让开发复现也不需要在执行机上翻日志拿到 trace 基本就能断定到底是前端 bug 还是等待策略问题。Puppeteer 在排查上不像上面两个有那么完整的测试框架体验但如果你是“脚本自动化”而不是“测试框架”它提供的page.on(console)、page.on(requestfailed)能让你把所有浏览器事件流导到自己的日志系统里这种自定义能力在 Selenium 里做得非常费劲。4.3 并行和多浏览器把执行时间从小时级压到分钟级Selenium Grid 可以撑起分布式执行但不是所有人都愿意为一个 UI 自动化专门维护一个 Grid。替代工具的思路更轻。Playwright 具备“并行 worker 多浏览器语境隔离”能力跑测试时能开多个 worker 进程每个 worker 里可以同时开多个浏览器标签页。它允许多个 context 之间数据不互相污染这种设计天生适合做登录态隔离便于并行跑不同账号的用例。WebdriverIO 也能通过maxInstances和specs的拆分策略并行执行用例配合它的wdio/spec报告器能有效把长时间回归拆分到多台上。最关键的是这些工具都不再强制要求执行机预装“指定版本的浏览器驱动”。Playwright 直接自带浏览器下载管理Cypress 自带 Electron 浏览器TestCafe 运行时自动用浏览器二进制。把环境依赖收敛到项目层面后你甚至可以让开发本地直接跑同一套 UI 自动化不用再额外配一套 Selenium 环境脚本维护效率自然上来了。5. 工具选型和迁移避坑实录5.1 我踩过的大坑提前帮你避开不要给纯测试框架硬塞“不是它的活”比如非要拿 Cypress 去爬第三方站点做长期监控它会把跨域拦得很死又比如非要用 Puppeteer 去跑完整的企业级 BDD 回归测试那你就要自己组装一堆报告和断言。这个工具“适合干什么”必须在选型一开始就清楚。Playwright 不带测试断言但有人误以为它能跑单元测试Playwright 只是个浏览器自动化库测试运行器需要配合playwright/test或者 pytest。如果你只装了 Playwright 库就写断言会发现文档里的很多东西用不上。它本身是个“库”成熟项目要的是“测试框架”这两层得分开理解。Cypress 的单 tab 限制会在重活里卡住你如果你需要测试跨域页面跳转、多登录态并排、或者跟两个独立源的页面交互Cypress 会很不顺手。虽然可以用cy.origin处理部分跨域场景但复杂度明显上升这时 Playwright 的多 context 设计反而更适合。老的 Page Object 模型可以不用了很多团队从 Selenium 迁移时会本能地想继续套 Page Object 模式。这个模式不是不好但在 Playwright 里继续把每个页面都抽象成一个 class往往会让脚本比直接写操作还绕。Playwright 官方推荐的测试风格更接近“把用户主流程写成一个完整的故事”过度封装反而损失了框架带给你的默认能力。5.2 常见问题速查场景推荐的替代工具推荐理由跨浏览器 Web 回归测试UI 用例量大Playwright自动等待、多浏览器支持、trace 调试最强前端团队自己在做组件和流程测试Cypress开发体验好时间旅行调试直观只需对 Chromium 做页面抓取/监控/老化测试Puppeteer与 Chrome 关系最近CDP 能力强老骨架是 WebDriver 协议又想迁 NodeWebdriverIO语法现代协议兼容社区生态好不想接触 WebDriver 但主语言是 Node/TSTestCafe环境最干净CI 配置成本低需要同时覆盖 Web 和移动 App WebViewAppium跨端统一模型老 Selenium 团队可复用存量老项目正好是 Ruby 生态Watir只能算是最后得选才选这个表格只解决“大概该往哪个方向走”如果你要做大型团队的长期推广我还建议关注另外一件事不管选什么工具一定把“复用内部组件录制的可选能力”想清楚。比如 Playwright 有 codegenCypress 有 Test Runner 录制这些工具可以帮助测试人员快速生成脚本骨架但生成的脚本质量参差不齐最终还是要靠一个好的人均熟悉度来校准。不要把低效脚本的责任甩给框架框架解决的是从 1 到 10 的效率从 0 到 1 的用例设计还是得靠人。6. 选型后的执行策略到了执行层面我建议你不要搞“大爆炸式重写”。UI 自动化脚本最怕的就是一边业务还在迭代、一边重写测试框架两边同时动最后排错都分不清是产品 bug 还是脚本 bug。稳妥的顺序是这样第一步挑一条覆盖面最广的核心主流程用新工具做“影子用例”。所谓影子用例就是先不动老脚本而是拿新工具把最重要的端到端流程另写一遍每天定时在 CI 里跟老脚本同时跑观察新工具的稳定性和报错体验。第二步对比两周数据重点统计失败率、执行总时长、以及排查失败需要的平均时间。如果新工具确实优势明显再把相对独立的模块一批批迁过去。还有一个容易忽略的点上线后要留够维护老脚本的缓冲时间。比如习惯用 Selenium 驱动的老设备、或者某些特定环境还没有合适版本的新工具支持这时强行砍掉旧框架会导致一段时间的回归覆盖真空。比较好的做法是并行维护两套脚本一个月左右确保新工具在真实业务环境下扛得住再逐步下线老方案。在我最近一个项目里迁移到 Playwright 之后最大收获不是刷得飞快的执行时间而是同事终于愿意主动写自动化脚本了。以前开一个 Selenium 环境要搞半天 WebDriver 和浏览器版本光环境就把新人的热情磨没了现在一条npm init playwright命令装完脚手架都能自动跑起来。这种开发体验上的改变可能比任何技术指标都更能让自动化落地到日常。如果你目前还在跟几年前的 Selenium 脚本搏斗我的建议很简单花一个下午把这里提到的工具各跑一个 Demo亲眼看一看自动等待、失败截图、录像、trace 这些功能做完以后维护一套脚本的真实成本能降多少。工具本身没有绝对优劣和团队场景、语言栈匹配后才能发挥价值。祝你也早日告别“白天写脚本、晚上修脚本”的日子。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进