作者 | 赵里京
前言
UI 自动化我们都了解可以模拟用户真正的使用场景和用户行为,而边界、异常等场景接口测试更加适合。那么实现了 UI 自动化是否就可以完全代替手工用例去执行测试任务。理论上没有错,但总是理想很丰满,现实很骨感。在做 UI 自动化时总会有那么多的问题阻碍了我们目标的实现:
- 脚本开发成本高而录制的脚本又基本无法重复使用;
- 脚本运行不稳定、问题的随机性;
- UI 变化快脚本维护成本高等等等等。
那如何避免上述问题让我们可以做到更接近我们的目标?(下面的答案与上面的问题不一一对应)
- 测试和验证逻辑而非 UI,我们减少页面上 UI 的验证、图片的识别等等。这些不稳定因素越少越稳定,并且你的 UI 用例思想都是以服务端的逻辑为主。如 WebSocket 发送方发送一条消息,接收方可以只验证接收到就好,至于样式用眼睛看可能会更轻松。
- 对于脚本不稳定以及问题的随机性,大部分原因是因为我们用例的健壮性不足导致。比如说,在你没有预测到的场景下用户发起的请求,和在你没有预测到的场景下系统的返回,这些没有预测到的情况就会导致用例失败,但这样的失败对于系统来说又是正常的。
- 互联网时代 UI 变化快,如果加上脚本开发的成本高,UI 自动化将很难被测试使用。直到 Puppeteer 的出现,它提供了大量操作 Chrome 的 API 供开发者使用,使得开发速度快成本低。目前在我处的业务线中,小的需求半天就可以开发完成全部功能,供给开发进行测试使用。
- 最后一点就是写用例前你的用例工程框架设计的要合理些,这样便于后期的维护。下面就来简单介绍一下 Puppeteer 的一些相关内容。
Puppeteer 介绍
Puppeteer 是谷歌官方出品的一个通过 DevTools 协议控制 headless Chrome 的 Node 库,注意它只是一个 node 库,所以环境以及使用都很方便。我们可以通过 Puppeteer 提供的 API 直接控制浏览器,模拟大部分用户操作来进行 UI Test 或者作为爬虫访问页面来收集数据。
Puppeteer 能做什么?
- 生成页面的截图和 PDF。
- 抓取 SPA 并生成预先呈现的内容(即 “SSR” 服务端渲染)。
- 从网站抓取你需要的内容。
- 自动表单提交,UI 测试,键盘输入等
- 创建一个最新的自动化测试环境。使用最新的 JavaScript 和浏览器功能,直接在最新版本的 Chrome 中运行测试。
- 捕获您的网站的时间线跟踪,以帮助诊断性能问题,Puppeteer Trace API。
Puppeteer 环境安装
就不说了,网上海量资料供你选择
Puppeteer vs Selenium
- Puppeteer 允许访问 Chrome 性能分析工具,来度量加载与渲染时间,可以进行页面性能测试。
- 由于谷歌的支持和对 Chrome 的复杂了解,Puppeteer 可以更好地控制 Chrome 的浏览器,使测试用例运行的更加稳定。
- Puppeteer 删除了对外部驱动程序的依赖以运行测试,使得环境更加轻量。而 Selenium、Webdriver 等,往往依赖众多,不够扁平,环境较重。
- Puppeteer 默认设置为在无头模式下运行,也可以更改为在非无头模式下观看执行。
Puppeteer 仍有些不足
- 到目前为止,Puppeteer 仅限于 Chrome 浏览器,但 Firefox 的支持计划正在进行中。
- Puppeteer 目前使用该工具的测试社区较小,而 Selenium 有更多针对测试的支持。
用例工程结构
多人协作一定要考虑维护的成本,工程的框架设计至关重要。结构清晰简单其他人维护的成本就会降低,下图为转转客服 UI 用例工程结构图:
主要是用 Puppeteer+Mocha(摩卡)组合,Mocha 与 testNG 的作用很像,主要也是用于单元测试的测试框架,也有测试套件等定义,以及用例控制功能。
上图第一层为用例的测试套件,一定要放置在工程的 test 文件夹下,下图中存在两个测试套件分别为 IMmainProcess.test.js 和 WorkOrderIM.test.js。mocha.opts 文件里写 mocha 执行命令的参数,如生成报告、执行方式等参数。
describe 定义测试套件名称,it 定义单个测试用例,Mocha 同时提供了 only,skip 等控制用例执行方法和钩子函数 before(),after() 等。
(mocha 生成的测试报告样式)
结构图中的逻辑层封装了逻辑变化校验,里面有大量的 if else 逻辑。如果触发一个动作后,会将页面实例传给逻辑变化模块进行校验,去判断当前页面是哪一种情况,进而来进行下一个动作,这就是前面说的用例健壮性的问题。当然在用例的什么位置加校验要通过个人的经验来判断。
业务逻辑中封装了浏览器实例减少重复代码,以及特定的业务逻辑。元素库主要封装的是 global 形式的页面元素常量。
最后就是经常用到的工具方法,比如 httpclient,dbserver,以及发送邮件发送报警等工具。这些方法已经逐步封装成了 API,其他同学只需下载 npm 包即可使用,使 QA 能将更多的精力放在用例逻辑的开发中。
使用 Puppeteer 过程中常遇到的问题
- 如何模拟多端以及不同的 APP 来打开同一 M 页
模拟不同的移动设备,首先 Puppeteer 提供了模拟移动设备的库,位置在工程的:
/node_modules/puppeteer/DeviceDescriptors,我们可以直接引用,如模拟一个 iPhone 6 设备,下图为 DeviceDescriptors 文件配置:
使用方式:
模拟不同的 APP,有些 M 页通过设置 UA(userAgent)来告诉服务端所使用的是什么 app,这样的逻辑我们可以修改 DeviceDescriptors 文件中对应配置的 UA 来实现。而直接修改 Puppeteer 库里的文件肯定不是好的,我们可将此文件拷到自己工程文件夹下进行修改并引用。下图为修改过的 DeviceDescriptors 文件,在 userAgent 中加入服务端识别的标识:
- 浏览器实例的封装降低重复代码
下图是封装的 page 模块,返回页面和浏览器实例。也可以将浏览器实例与用例写在同一文件中直接使用,免去了传参的麻烦,不过当用例过多时这样写就会产生大量的重复代码。
- 浏览器实例不会被关闭
在每条用例中都会有关闭浏览器实例的动作,但往往一段时间后服务器上就会驻留大量 chrome 实例,占用了服务器大量的资源,同时也有可能对后续的用例执行产生影响。即便使用 try..catch{await browser.close()}这样的句子也无法解决问题。
官方解答是:“如果你想在 promise 执行完毕后无论其结果怎样都做一些处理或清理时,finally() 方法可能是有用的,它仅用于无论最终结果如何都要执行的情况”。此方法也确实有效。
- 元素位置不固定
M 页中很多元素都是以 feed 流形式,所以位置有可能是这样的:
body > div:nth-child(7) > div > div.ant-modal-wrap > div > div.ant-modal-content > div.ant-modal-body > div > p:nth-child(1) > div:nth-child(2) > div > div > div > ul > li:nth-child(5)
其中的数字在每次页面打开的时候都有可能发生变化,而同一类元素的路径结构一般不会变。对于这类问题解决原理,首先判断 selector 中存在几个数字。拿两个举例,那就定义一个二维数组的 selector 循环遍历,与给出的元素上面唯一标识进行比较,selector 与唯一元素标识匹配成功则找到真实的页面元素的 selector。方法封装的 API:
monitorapi.getSelector (page,selector,labelname)
- page,测试的页面实例,Object
- selector,任意一次的 DOM 元素位置,String
- labelname,元素展示的部分或全部内容,给出的内容保证页面中唯一
- returns selector,返回当前页面所选元素正确的 selector 定位
基于 Puppeteer 的 UI 用例监控系统
目前客服 UI 自动化用例同时肩负着监控线上客服系统功能回归的任务,并随之开发了用例配套系统 “UI 用例监控系统”,系统的核心仍然是 “用例”。当执行线上用例失败后,系统首先会截取当前浏览器的快照,并将快照与异常结果一同上报至平台,同时发送报警信息给相关负责人。下图为系统中异常的记录,主要有异常信息、发生时间、执行人(获取的是系统用户名)、操作以及截屏图片等。
操作的备注功能功能主要是对异常数据人工打标,上报的数据将和标注后的数据进行对比。标注有两个属性分别为是否为 bug 与类别,如下图:
新产生的异常首先与库中异常数据进行比较,匹配后再以图片进行比较。如果均匹配成功则自动补充样本的标注信息,如果未匹配则不补充。同时如果与不是 bug 的匹配成功的话则不发报警信息,来减少误报情况。
系统核心模块如下图:
系统还有一些其他功能:
- 监控用例的两种执行方式
监听:集群上线完成后,会发送上线完成消息,监控平台收到消息后,会自动触发在服务器上的用例。当然消息会以集群名进行区分,用例有选择性的去执行。
定时任务:在 centos 服务器上创建定时任务,来定时触发用例监控线上功能。
- 执行人监控
目前用例分为线上和线下模式,由开关控制。用例场景完全一样,用户区分为线上和线下用户。但无论执行哪种模式,在哪执行用例,一旦发生异常都会上报至监控平台。这样你就可以清楚的知道,你的用例被人执行的情况与频率。
- 接入平台方式
一个 API 即可,并且自动帮助截取异常时的屏幕快照,不限制框架的使用,不限制用例编写的规范,只要是使用 node 环境的就可以。接入平台的 API 如下:monitorapi.FailMessage(e,casename,isOffline,...page)
- e,异常信息,或自己定义的 error,String
- casename,用例名称,String
- isOffline,线上线下标识,int 或 String
- page,测试的页面实例,{...any} Object
- returns void
- (自带异常时截取屏幕快照功能,并发送给监控平台)
在业务线中的具体应用
客服系统核心功能之一是用户与机器人交互与人工客服聊天,最初的测试方式是采用 java 创建多个 WebSocketClient 与服务端建立连接,写心跳逻辑与服务端保持连接,进而模拟消息发送与接收。全场景用例写好后,相当于写好了一个没有页面的用户端,成本非常高。
而使用 Puppeteer 实现 WebSocket 场景就相当简单了,可以快速创建多个 Chrome 实例模拟多个用户登陆并与机器人进行交互、申请人工,客服优先级接待等复杂场景。这些多用户场景用手动测试很难,但使用 UI 自动化还是非常理想的。目前这些复杂场景用例,已经提供开发自测或者执行冒烟用例时使用。
未来规划
- 仍然是用例,用例覆盖的场景,用例的健壮性,仍然是基础与重点。
- 平台会增加用例管理,与报告展示等基础功能。
- UI 层面的自动生成用例也是一个重点研究方向。
↙↙↙阅读原文可查看相关链接,并与作者交流