Web 自动化测试搭建这件事,网上教程一搜一大把,但真正能在项目里活过三个版本迭代的方案,其实没那么多。我这段时间在一套跨平台业务系统上从零搭自动化,最后定下来的技术组合是 Robot Framework + Playwright。这两个名字放在一起,很多人第一反应是“有现成的 Selenium 方案为什么要换”,也有人觉得 Robot Framework 这种“老牌框架”配不上“新工具”。实际用下来,这个组合在可读性、执行效率和维护成本之间的平衡做得相当好。这篇文章不聊虚的,就把环境搭建、用例设计、关键字封装、CI 集成这条链路上踩过的坑和反复调整的细节摊开说,给正在选型和准备上手的读者一个真实参考。
1. 为什么我不建议继续用 Selenium:Robot Framework 与 Playwright 的组合逻辑
每个做 Web 自动化的团队,大概率都经历过同样的纠结:工具选型阶段,Selenium 是默认答案,因为它生态最老、资料最多。但真正写一段时间用例后,你会发现 Selenium 的维护成本往往比预期高不少。定位元素不稳定、等待时机全靠 sleep、多标签和 iframe 处理起来繁琐,这些都是实际痛点。Robot Framework 加 Playwright 的组合并不是为了追新,而是刚好把“用例可读性”和“执行稳定性”这两件以前很难兼得的事凑齐了。
1.1 Robot Framework 的核心优势:关键字驱动与测试资产沉淀
Robot Framework 吸引人的地方从来不是执行速度,而是关键字的抽象能力。它把操作步骤拆成“关键字”,再把关键字组装成用例,读起来像自然语言。这种设计有一个很实际的好处:业务人员即使不会写代码,也能看懂用例在验证什么,遇到失败时可以直接指出哪一步不符合预期。以前我接手过一个纯代码写的自动化项目,用例堆在各种函数里,业务同事完全没法参与评审,最后用例和业务需求脱节得厉害,维护自然就断了。
Robot Framework 的报告体系也是我坚持选它的原因。log.html 和 report.html 做得非常直观,用例步骤、日志、截图都归置得清楚。这对日常维护极为重要——在真实项目中,一个用例跑了半年突然挂掉,如果没有清晰的日志和调用链,排查起来会让人抓狂。再加上标签、模板、数据驱动这些原生能力,测试资产可以沉淀得比较规整,而不是每次都在重写脚本。
1.2 Playwright 到底比 Selenium 强在哪
Selenium 解决的问题是“能用”,但它的设计目标是浏览器兼容性,而不是现代 Web 应用的测试体验。Playwright 是后来者,设计取舍更贴近当下前端技术:自动等待是内置的,节点出现之前不会盲目点击,显得更智能;对 iframe、多页面、下载事件、弹窗的处理也顺手很多;还有 trace 录制,能回放出完整操作过程。这些都实际减小了用例脚本的脆弱性。举个例子,以前用 Selenium 处理多 iframe 嵌套页面,得来回 switch,一旦层级搞错就要踩坑。换成 Playwright 的 frame 定位思路后,代码明显干净了。
另外,Playwright 在网络层的处理能力很强,可以在测试中拦截请求、mock 接口,这对于验证一些不好构造的前端状态非常有效。比如后端异常返回、接口超时、空数据列表这类场景,以前要么造数据要么等环境,现在直接在用例层控制。用到后面你会发现,它能帮你绕开不少 Selenium 时代需要各种 workaround 的场景。
1.3 三种组合方式的对比与选型结论
我把实际选型时对比过的几种方案放在一张表里:
| 组合方案 | 用例可读性 | 执行稳定性 | 学习成本 | 维护体验 |
|---|---|---|---|---|
| Robot Framework + SeleniumLibrary | 高 | 中等,依赖大量显式等待 | 较低,资料多 | 一般,脚本容易变脆 |
| Robot Framework + Playwright | 高 | 高,自动等待 + 现代定位方式 | 中等,API 需要适应 | 好,trace 方便排障 |
| Playwright + pytest 纯代码 | 低 | 高 | 较高,写代码前要理解不少概念 | 好,但测试资产与业务人员脱节 |
结论其实很明确:如果你的团队有业务人员参与用例评审,或者你希望测试资产能长期被“读懂”,Robot Framework + Playwright 是当前很合适的组合。如果整个团队都是开发者、完全不需要业务视角参与,直接上 Playwright + pytest 也完全没问题,只是这篇文章聊的是前一条路线。
2. 环境搭建实操:从零跑通第一个 RF 脚本
环境搭建这一步看起来简单,实际上有不少细节容易卡住。尤其是第一次装 Playwright 浏览器内核的时候,不少人会误以为命令挂了。
2.1 环境准备与安装步骤
我使用的是 Python 3.10 和 Robot Framework 6.x,robotframework-playwright 用的是当时比较新的稳定版。第一步先建虚拟环境,这几乎是必须的——不建虚拟环境,后续装依赖容易把全局环境搞乱,尤其机器上同时有好几个项目的时候,版本冲突会让你怀疑人生。
python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install --upgrade pip pip install robotframework robotframework-playwright rfbrowser init这里要重点说下rfbrowser init:它负责下载 Playwright 对应的浏览器内核,首次执行通常耗时几分钟,请确保网络稳定。很多人第一次装的时候以为卡死了,其实它就是在下载。装完后用robot --version验证主框架,再到 Python 交互环境里确认能正常 import,都没问题就可以往下走了。
如果rfbrowser init一直卡住,大概率是当前网络环境对浏览器下载源不友好。我当时排查了一圈,最后发现是办公网络代理把下载请求影响了,换了网络后顺利通过。这个场景后面在常见问题里再展开。
2.2 项目目录结构规划
目录结构决定了后续维护的体验。我的建议是分成四块:用例目录、资源目录、配置目录、结果目录。资源目录里再按页面维度拆分成独立文件。一个能直接开始跑的结构长这样:
web_auto/ ├── config/ │ └── variables.py # 环境变量、全局配置 ├── resources/ │ ├── common.resource # 公共关键字 │ └── pages/ │ ├── login_page.resource │ └── home_page.resource ├── testcases/ │ ├── test_login.robot │ └── test_order.robot ├── results/ # 每次运行输出都放这里 └── run.py # 可选,封装统一运行入口很多初学者喜欢把所有用例和关键字堆在同一个文件里,前期确实方便,但用例数量过百后根本没法维护。每次定位器一改,满屏搜索替换的痛苦,经历过的都懂。所以哪怕刚开始只有一个目录,我也建议按这个骨架走,后面扩展时不用返工。
2.3 跑通第一个用例
环境装好后,先写一个最小用例验证链路通不通。不要一上来就挑战复杂页面,先拿公开示例页面跑一下:
*** Settings *** Library Browser *** Test Cases *** 打开首页并校验标题 New Browser chromium headless=${False} New Page https://example.com Get Title == Example Domain Take Screenshot运行命令也很简单:
robot --outputdir results testcases/跑完以后到 results 目录下打开 report.html,能看到刚才的用例状态。首次调试建议把headless设为 False,肉眼确认浏览器确实按预期打开了页面;等用例稳定后再切回 True,因为 CI 环境里基本都是无头模式运行。这一步的意义是把“环境问题”和“脚本问题”彻底分开。
3. 用例设计与关键字封装:让业务人员也能读懂的测试资产
框架搭起来之后,真正的挑战才刚开始:怎么组织用例,怎么封装关键字,怎么避免测试资产在三个月后烂成一堆没人敢碰的脚本。这一章基本决定了一个自动化项目的寿命。
3.1 用例文件的核心结构
Robot Framework 的用例文件有四个核心部分:Settings、Variables、Test Cases、Keywords。Settings 用来导入库和资源文件,Variables 定义变量,Test Cases 写具体用例,Keywords 定义当前文件内的私有关键字。想快速上手,先理解这四张表就够用了。
写一个带自定义关键字的登录用例,结构大概是这样的:
*** Settings *** Library Browser Resource ../resources/pages/login_page.resource *** Variables *** ${USERNAME} admin ${PASSWORD} admin123 *** Test Cases *** 使用正确账号登录 登录系统 ${USERNAME} ${PASSWORD} 首页应显示用户名 ${USERNAME}把登录动作封装成“登录系统”这个关键字后,用例读起来就是一句人话。业务同事看到这个用例,不需要理解任何代码细节,就能知道你验证的是什么。这种可读性带来的协作价值,往往比节约几行代码重要得多。
3.2 资源文件与关键字封装的价值
我把资源文件比作自动化项目的“代码底座”。所有元素定位、公共操作都下沉到 resource 文件里,而不是散落在用例中。如果一个定位器在二十个用例里重复出现,一旦前端改了某个属性,没封装之前你要改二十处,封装之后只改一处。
这里给一个 login_page.resource 的示例,注意定位器和操作都集中在一起:
*** Settings *** Library Browser *** Variables *** ${LOGIN_URL} https://example.com/login *** Keywords *** 登录系统 [Arguments] ${username} ${password} Go To ${LOGIN_URL} Fill Text input[name="username"] ${username} Fill Text input[name="password"] ${password} Click button[type="submit"] Wait For Load State networkidle 首页应显示用户名 [Arguments] ${expected_username} Get Text .user-info == ${expected_username}[Arguments]用来定义关键字入参,[Return]可以用来返回值。刚开始用 RF 时容易忽略这两个特性,结果把变量做得全局可见,最后互相污染。我的经验是:资源文件里的变量能少则少,数据尽量通过参数传递,这样关键字的复用性才强。
3.3 页面对象模式的简化实践
很多人一提到页面对象模式,就想把 Java 那套严格的 POM 搬过来。在 Robot Framework 里这么做往往会变得很臃肿。我建议的做法是:一个页面一个 resource 文件,文件内部维护这个页面专属的定位器变量和相关操作关键字;跨页面流转时,用公共关键字串联。比如下单流程涉及购物车页、确认页、支付页,那就每个页面建一个 resource,测试用例负责按业务顺序调用。
不要过度设计。见过一些团队把资源文件拆得极细,导致定位一个元素要翻三个文件,这种抽象反而增加了维护成本。测试资产的目标是稳定和能读懂,不是为了证明架构能力强。
4. 高级场景拆解:登录态、参数化与网络拦截的落地做法
基础用例跑通后,要面对的就是真实项目里那些绕不开的场景:登录怎么处理、大量重复场景怎么写、数据状态怎么构造。Playwright 在这些方面给了很好的基础能力,关键是把它用在合适的地方。
4.1 登录态复用:从脚本登录到 Storage State
最常见的痛点是每个用例都走一遍登录流程。登录接口慢的时候,一套一百个用例的回归,光登录就白白耗掉好几十分钟。Playwright 支持保存登录状态,Robot Framework 的 Browser 库也提供了对应能力。
做法是:先单独跑一个登录用例,把登录后的状态保存到 JSON 文件,后续用例直接加载这个状态,不再执行登录动作。示意如下:
*** Keywords *** 保存登录状态 [Arguments] ${username} ${password} ${state_path} 登录系统 ${username} ${password} Save Storage State ${state_path}后续用例在创建上下文时指定状态文件:
*** Test Cases *** 加载登录状态进入首页 New Context storageState=${STATE_PATH} New Page ${APP_URL} Get Title == 控制台首页这套做法能把整套用例的执行时间砍掉明显一截。不过要注意一点:登录状态不能无限期复用,token 过期后用例会大面积失败。我的处理方式是把它做成一个前置关键字,先判断当前状态是否有效,无效就重新登录,有效就直接复用。
4.2 数据驱动:把重复用例压缩成一张参数表
很多场景只是参数不同、步骤完全相同。比如登录用例要验证不同账号在不同条件下的结果。如果每个场景都复制一份用例,文件会膨胀得很快,而且一旦登录逻辑调整,要同步改好几处。
Robot Framework 的[Template]机制天生就是干这个的:
*** Test Cases *** 登录场景参数化 [Template] 登录并校验结果 admin admin123 成功 user01 wrongpass 失败 user02 locked 失败对应的关键字负责执行和断言:
*** Keywords *** 登录并校验结果 [Arguments] ${username} ${password} ${expected} 登录系统 ${username} ${password} Run Keyword If '${expected}' == '成功' 首页应显示用户名 ${username} ... ELSE 登录页面应显示错误提示数据驱动的价值不只是减少代码量,最重要的是让测试设计回归到“输入输出”的思考方式。业务上每种输入对应什么期望结果,一目了然。
4.3 网络请求拦截与 Mock 的实战思路
有些前端场景很难用真实数据构造。典型的例子:接口返回空列表时页面要展示空状态;接口超时的时候页面要有超时提示;接口返回异常字段时前端不能崩溃。这些用例如果在后端造数据,可能要改数据库、杀缓存、甚至麻烦后端同事配合,效率极低。
Playwright 的网络拦截能力解决了这个问题。Robot Framework 的 Browser 库也提供了对应封装,可以在测试中拦截指定请求并返回自定义响应。思路大概是这样的:
# 示意写法,具体关键字名以你安装的 Browser 库文档为准 New Context ignoreHTTPSErrors=${True} New Page ${APP_URL} # 拦截某个接口并返回自定义响应 Route **/api/order/list response={"code":0,"data":[]}实际操作时,我会优先拦截那些影响页面关键状态的接口。比如订单列表为空时页面是否提示“暂无订单”,接口返回 500 时页面是否有错误提示。这类用例跑起来之后,后端的异常状态就不再是测试的阻塞项了。这也是相比传统 Selenium 方案最有感知的优势之一。
5. 接入 CI 与测试报告:调试慢、跑得快才是硬道理
自动化测试如果只停留在本地跑,价值会大打折扣。接入 CI 之后,每次提交代码都能自动触发回归,问题在开发阶段就被拦住,这才是测试自动化的意义。这一章讲讲我实际接入过程中的配置和取舍。
5.1 命令行运行与常用参数
Robot Framework 的命令行参数非常多,实际高频用的就那么几个。我整理了一份经常用到的命令:
# 运行全部用例,输出到 results 目录 robot --outputdir results testcases/ # 只跑冒烟标签相关的用例 robot --include smoke testcases/ # 排除已知失败的用例 robot --exclude known_broken testcases/ # 指定运行环境参数 robot --variable ENV:UAT testcases/ # 输出 xunit 格式,方便 CI 平台解析 robot --xunit xunit.xml --outputdir results testcases/--include和--exclude配合标签,可以灵活控制回归范围。--variable ENV:UAT这种参数传递方式特别好用,配合 config 目录下的变量文件,可以做到同一套用例在不同环境切换运行。
5.2 日志、报告与截图视频的配置
Robot Framework 自带报告体系已经够用,log.html 记录了每个关键字的输入输出和耗时,report.html 汇总了用例执行情况。每次运行完,我习惯直接把这两个文件丢给开发同事看,他们自己就能定位到问题。
失败截图是排障利器。简单场景可以写一个关键字放到用例 Teardown 里:
*** Keywords *** 失败时截图 Run Keyword If ${TEST_STATUS} == 'FAIL' Take Screenshot更复杂的场景建议用 listener 统一处理,而不是每个用例都手动加这个关键字。Browser 库还支持录制视频,开启后能看到整个操作过程,对排查偶发问题帮助很大。但由于视频文件体积大,我只建议在关键路径或 debug 时开启,平时不要全局开。
5.3 并行执行的取舍与建议
Robot Framework 官方不带并行执行,社区常用 pabot 来做。并行能显著缩短回归时间,但代价是资源竞争、用例隔离,以及报告合并的复杂性。我吃过一次亏:当时为了赶回归时长,直接上了 8 并发,结果用例之间因为共享登录状态互相影响,30% 的用例偶发失败,后面花了一周去隔离状态,得不偿失。
我的建议是:除非用例量非常大,否则先做好用例的稳定性和隔离性,再考虑并行。每个用例独立创建浏览器上下文、独立准备数据、独立清理数据,这些基础打不牢,并行只会把隐藏问题放大。
6. 常见问题与排查技巧实录
这个章节把我实际踩过的坑整理成速查表,按出现频率排序。遇到同样的报错,可以直接对着处理。
6.1 高频报错速查表
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| rfbrowser init 长时间无响应 | 浏览器内核下载受网络环境干扰 | 检查代理设置,手动下载浏览器内核后放到对应缓存目录 |
| 元素定位报 TimeoutError | 定位器写得太严格,或元素是动态加载 | 优先用 Playwright 的语义定位,比如 get_by_role,减少对 CSS 类的强依赖 |
| 用例本地能过、CI 上挂 | headless 环境差异、视口尺寸不一致 | 统一设置 viewport,明确 headless 参数,必要时在 CI 里输出截图对比 |
| 接口请求出现认证弹窗 | 被测环境需要基础认证 | 在 New Context 里配置 httpCredentials |
| 中文文本断言失败 | 输出编码或文件编码不一致 | 设置 PYTHONIOENCODING=utf-8,RF 文件和资源统一用 UTF-8 保存 |
第一条值得多说两句。rfbrowser init卡住不等于装错了,它只是下载太慢。建议先确认当前网络能不能正常访问浏览器的下载源,不能就配置代理或者换网络,而不是反复重装。
6.2 三个最容易忽略的细节坑
第一,用例之间共享浏览器上下文导致状态污染。很多刚开始写 RF 的人会在 Setup 里打开浏览器,然后所有用例复用同一个浏览器页面。这看似节省了时间,实际上一个用例失败后,后续用例全被带崩。不要在用例间共享状态,每个用例独立创建上下文,Teardown 时关闭,稳定性和隔离性会好很多。
第二,断言要么太宽松要么太严格。太宽松时用例绿着但实际上没验证核心逻辑,太严格时前端改个文案或加个无关字段就失败。判断标准就一条:只断言用户可感知的关键结果。比如登录用例只断言“进入正确页面”和“用户名显示正确”,不要连按钮颜色、某个隐藏字段都断言。
第三,手动加 sleep。Playwright 的自动等待已经做得很好了,你再在关键字之间塞Sleep,只会拖慢执行速度,而且会掩盖真实的时序问题。正确的姿势是依赖自动等待,或者用显式条件等待,即“等一下直到某个条件成立”,而不是“固定等几秒”。
另外还有一个细节,就是New Browser和New Context这种底层对象生命周期容易混淆。Browser 对应浏览器实例,Context 相当于一个独立的会话环境,页面在 Context 里创建。隔离用例时重点是隔离 Context,而不是反复重启整个浏览器。
最后再分享一个小技巧
整套框架搭建过程中,我最大的体会是:工具只占一半,另一半在于你怎么设计用例资产。如果你的团队打算引入 Robot Framework + Playwright,我的建议是先选一条核心业务链路,从登录到核心操作再到结果校验,把这条链路彻底跑稳,再慢慢扩展覆盖范围。别一上来就把所有页面铺开,那只会让你同时面对几十个不稳定因素,根本分不清是环境问题、定位问题还是设计问题。
最后补一个很实用的习惯:关键字命名尽量用“动作 + 对象”的结构,比如“登录系统”“查询订单”“核对库存”。坚持一个季度后你会发现,用例文件读起来几乎就是验收文档,新同事接手时维护成本会低非常多。