作者:吴小平
部门:商业赋能
一、引言
UI 自动化是质量保障的一种重要手段,我们从分层测试金字塔模型可以看出,质量保障更多的应该依靠底层的单元测试和接口集成测试,UI 自动化测试占比是非常小的一部分,众所周知,UI 层的自动化测试稳定性差,成本高。然而我们团队经过一年多的 UI 自动化测试的实践与优化,发现我们 UI 层自动化测试相对性价比是最高的,脚本的稳定性也非常好,误报率降到了 1% 左右,每次上线前能帮助我们回归系统的一些核心业务流程,下面将跟大家分享一些关于我们 UI 自动化测试的实践经验。
二、覆盖合适的业务
有赞商业化团队负责保障有赞商业化业务的产品交付,我们的核心业务是商家通过我们系统进行有赞软件、插件的订购,这是一套标准的电商下单、履约流程。核心用户是有赞商家,订购入口是 PC web 页面和 H5 页面。我们将商家下单流程定义成我们 P0 业务,P0 业务需要保证绝对的稳定性,任何的功能上线前都要对这块业务进行回归测试,保证不会出线上问题。通过分析发现这套下单流程及相关的页面改动较少,测试策略上适合使用 UI 自动化来保证质量,提高回归效率。如何判断业务适合覆盖 UI 自动化测试呢?原则大概如下:
- 业务流程不频繁改动
- UI 元素不频繁改动
- 需要频繁回归的场景
- 核心场景等
基于以上原则我们发现真正适合覆盖 UI 自动化测试的业务肯定不会太多。UI 自动化测试的稳定性会受到很多因素影响,业务流程改动、接口改动、页面布局改动、网络抖动都会影响我们的脚本稳定性。我们选取业务进行覆盖时一定要克制,不要不断地做加法,选取最核心需要频繁回归的场景,将用例量尽量精简,以保证最高的性价比。
三、选择合适的框架
有赞 UI 自动化用的框架选用的是 Puppeteer mocha,我们以往文章《有赞前端质量保障体系》里有做过介绍,如为什么选择Puppeteer mocha,如何做方法封装等。
此外,Puppeteer 还可以对页面操作的接口请求进行拦截,获取接口返回值等,我们可以利用这些能同时操作 UI 与接口的特性来提升 UI 自动化测试脚本执行的稳定性与效率。
四、提升脚本稳定性与执行效率
4.1 元素定位
元素定位是 UI 自动化很重要的一个环节,它也是造成 UI 自动化不稳定的因素之一,比如,我们用 class 来定位元素,有些元素的 class 是动态生成的,如果开发改了 CSS 结构,新版本的 class 就变化了,元素对应的 Selector 也会变化,导致脚本不稳定。
像这种不会随着业务变化的元素控件,我们为了元素定位稳定,最直接的办法,可以让前端开发给元素增加一个专门为 UI 自动化测试使用的 CSS 属性,如下图:
图中“立即订购”按钮加了一个 testId = ' test-submit' 的属性,这是一个自定义的属性,只要元素一直存在,用这个属性去定位元素就一定可以定位到,CSS 定位属性的选择器可以这样写:[testId='test-submit'],尝试用自定义属性定位元素,如下图,是可以精准定位到的。
如果觉得让开发加属性比较麻烦,而且本身 UI 和流程改动不大,也可以按页面组件维度来定位元素,可以先定位到组件,就是先找到父元素,再依次找到子元素。
还有一种思路是根据相邻稳定的兄弟选择器来定位相对不稳定的元素,如 A 选择器相对不稳定,但相邻的 B 元素比较稳定,那可以用 CSS 组合器。
4.2 多变元素的校验
上面说的元素一般是不会随着业务的变化而变化,我们获取到元素后可以直接校验,但如果有些元素的内容,即使开发不发布代码,元素也会因为不同时间,不同业务场景而改变,该如何去做校验呢?
如上图,订购时长根据业务规则是会每天动态变化的,前端页面依赖后端数据展示,我们可以从接口返回值获取到对应的值作为 Expect,页面内容获取到的作为 Actual。Puppteer 的 Response 类里的 response.json() 方法,表示页面接收的响应,我们可以从响应内容里获取想要的东西,UI 自动化脚本代码实现:
上述代码表示当遇到请求 'https://www.xxx.com/xxx/xx' 接口时,获取其返回值,该接口是后端给前端返回商品价格、周期等信息的接口,当获取到周期信息时,我们可以计算出价格 = 周期 * 商品单价,从而作出用例校验。
这种方式我们不仅仅用来做 UI 自动化测试,假如有些比较复杂的控件,元素不好定位,我们并不一定非要通过 UI 去校验,我们可以在 UI 自动化执行过程中直接对接口返回值进行校验。
4.3 执行等待机制
上图这个界面,该商品的价格依赖于后端返回的数据,但因为此处价格的计算时间不稳定,大部分情况都是很快可以展示,但也存在不可预期的出现时间,刚开始脚本里的实现是页面等待 2 秒钟,在 Puppeteer 里,页面等待可以用 page.waitFor(time)
但后面在脚本执行的时候还是会偶尔出现价格断言失败的情况,这是因为价格计算时间在网络等因素影响下可能会大于等待时间 2s。既然价格依赖后端接口返回的数据,那能不能等接口返回成功了,我们再去断言价格的正确性呢?答案是可以的,Puppeteer 框架有一个方法为 page.waitForResponse ,等待指定的 url 返回成功,这里的 url 是在当前页面发起的请求,可从 chrome 浏览器调试控制台处查看。
控制台查看依赖的接口:
在脚本工程里对该方法进行了封装,等待参数里的 url 返回 status 等于 200,代表接口请求结束并且是成功的,当接口返回成功了,前端页面要做的事情便是取出数据,将数据在页面上渲染出来,考虑到渲染需要的时间,此处加个 0.1s 的等待。
UI 自动化用例脚本执行只需要在断言前调用上述方法对需要的接口进行等待即可,为了稳定起见,此处还加了一个控件等待 page.waitForSelector()
4.4 利用接口代替 UI 执行
业务场景举例:
(1)创建订单;
(2)再次创建相同商品的订单会提示有待支付订单,如下图;
(3)在订单列表进行关闭订单才能继续下单。
如果每次创建完订单或者开始创建订单前走 UI 进行关闭待支付订单,一是降低了 UI 自动化的稳定性,二是增加了 UI 自动化的执行时间。
对于链路比较长的非校验点,但又会影响到当前校验的功能的操作,可以用接口操作来代替。那如何在 UI 自动化框架里发起 API 请求呢?在此,我们在框架里引入 Axios。
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
我们在框架里基于 Axios 根据常见的业务场景封装了一个通用的 request 请求服务:get、post请求,其中不一样的地方是我们不需要单独去构造 cookie,而是通过 Puppteer 的page.cookies() 来获取 cookie,再塞到 Axios 的 header 里。
request 方法有了,接下来只需要发出请求了,比如要关闭待支付的订单,只需要找出关闭订单的接口和对应的参数即可(可以在浏览器控制台 network 获取),在用例执行前、后分别调用接口,相当于接口自动化里的清理数据。接口封装的代码:
UI自动化脚本的对上述封装的接口的调用:
4.5 用例重试机制
有些脚本可能刚好因为网络抖动等原因执行失败了,为了提升测试用例的稳定性,我们可以在脚本里加入重试机制,一般测试框架都有重试机制,如我们用的 mocha 框架,重试机制非常简单,可以在每个测试用例前加上重试语句,可以指定重试次数,如下代码展示,如果用例失败了,可以自动重试两次:
4.6 截图和日志打印
我们执行完用例如果有失败用例,最直接的是看页面的展示,这个比较简单,我们可以在测试框架钩子函数 afterEach 里加入截图的功能,afterEach 是 mocha 框架每执行完一个测试用例后会去执行的函数,为了脚本稳定性,不用每个用例执行完都去截图,而是用例失败了才触发截图,所以在 afterEach 里得判断下当前执行完的用例是成功的还是失败。
另外为了能帮助我们定位是前端问题还是后端问题,我们可以将重要的接口返回值在控制台打印出来,用的还是上面介绍过的 Puppteer 的 Response 类:
4.7 持续集成
我们 UI 自动化在两种时间会被执行,一是项目上线前在预上线环境执行,以拦截可能会出现的 bug;二是定时在线上执行,以保证线上核心功能正确。我们把 UI 自动化的工程部署在 Jenkins 上,每次执行结果会通过企业微信发送到群里,并@当前值班同学。预上线环境的执行用例一定是核心用例,执行时间会控制在5-7分钟,自动化执行时间过长了,一是加大了 UI 自动化的不稳定性,二是对于部分紧急发布的日常需求会不太友好。
五、总结
做 UI 自动化之前,要想清楚想让 UI 自动化为你做什么,你想要做成 UI 自动化的的业务是不是稳定的,你要覆盖的场景是不是你每次回归测试必须要测试的,当脚本跑起来的时候,它的不稳定因素是什么?UI自动化也不仅仅是验证 UI 层相关的内容,也可以通过 UI 路径来验证接口的业务逻辑。UI 自动化测试是一把双刃剑,不要一味追求覆盖率,覆盖合适的场景才能形成最高的性价比。