作者 | 赵里京

前言

UI 自动化我们都了解可以模拟用户真正的使用场景和用户行为,而边界、异常等场景接口测试更加适合。那么实现了 UI 自动化是否就可以完全代替手工用例去执行测试任务。理论上没有错,但总是理想很丰满,现实很骨感。在做 UI 自动化时总会有那么多的问题阻碍了我们目标的实现:

  1. 脚本开发成本高而录制的脚本又基本无法重复使用;
  2. 脚本运行不稳定、问题的随机性;
  3. UI 变化快脚本维护成本高等等等等。 那如何避免上述问题让我们可以做到更接近我们的目标?(下面的答案与上面的问题不一一对应)
  4. 测试和验证逻辑而非 UI,我们减少页面上 UI 的验证、图片的识别等等。这些不稳定因素越少越稳定,并且你的 UI 用例思想都是以服务端的逻辑为主。如 WebSocket 发送方发送一条消息,接收方可以只验证接收到就好,至于样式用眼睛看可能会更轻松。
  5. 对于脚本不稳定以及问题的随机性,大部分原因是因为我们用例的健壮性不足导致。比如说,在你没有预测到的场景下用户发起的请求,和在你没有预测到的场景下系统的返回,这些没有预测到的情况就会导致用例失败,但这样的失败对于系统来说又是正常的。
  6. 互联网时代 UI 变化快,如果加上脚本开发的成本高,UI 自动化将很难被测试使用。直到 Puppeteer 的出现,它提供了大量操作 Chrome 的 API 供开发者使用,使得开发速度快成本低。目前在我处的业务线中,小的需求半天就可以开发完成全部功能,供给开发进行测试使用。
  7. 最后一点就是写用例前你的用例工程框架设计的要合理些,这样便于后期的维护。下面就来简单介绍一下 Puppeteer 的一些相关内容。

Puppeteer 介绍

Puppeteer 是谷歌官方出品的一个通过 DevTools 协议控制 headless Chrome 的 Node 库,注意它只是一个 node 库,所以环境以及使用都很方便。我们可以通过 Puppeteer 提供的 API 直接控制浏览器,模拟大部分用户操作来进行 UI Test 或者作为爬虫访问页面来收集数据。
Puppeteer 能做什么?

  1. 生成页面的截图和 PDF。
  2. 抓取 SPA 并生成预先呈现的内容(即 “SSR” 服务端渲染)。
  3. 从网站抓取你需要的内容。
  4. 自动表单提交,UI 测试,键盘输入等
  5. 创建一个最新的自动化测试环境。使用最新的 JavaScript 和浏览器功能,直接在最新版本的 Chrome 中运行测试。
  6. 捕获您的网站的时间线跟踪,以帮助诊断性能问题,Puppeteer Trace API。

Puppeteer 环境安装

就不说了,网上海量资料供你选择

Puppeteer vs Selenium

  1. Puppeteer 允许访问 Chrome 性能分析工具,来度量加载与渲染时间,可以进行页面性能测试。
  2. 由于谷歌的支持和对 Chrome 的复杂了解,Puppeteer 可以更好地控制 Chrome 的浏览器,使测试用例运行的更加稳定。
  3. Puppeteer 删除了对外部驱动程序的依赖以运行测试,使得环境更加轻量。而 Selenium、Webdriver 等,往往依赖众多,不够扁平,环境较重。
  4. Puppeteer 默认设置为在无头模式下运行,也可以更改为在非无头模式下观看执行。

Puppeteer 仍有些不足

  1. 到目前为止,Puppeteer 仅限于 Chrome 浏览器,但 Firefox 的支持计划正在进行中。
  2. 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 过程中常遇到的问题

  1. 如何模拟多端以及不同的 APP 来打开同一 M 页 模拟不同的移动设备,首先 Puppeteer 提供了模拟移动设备的库,位置在工程的: /node_modules/puppeteer/DeviceDescriptors,我们可以直接引用,如模拟一个 iPhone 6 设备,下图为 DeviceDescriptors 文件配置: 使用方式: 模拟不同的 APP,有些 M 页通过设置 UA(userAgent)来告诉服务端所使用的是什么 app,这样的逻辑我们可以修改 DeviceDescriptors 文件中对应配置的 UA 来实现。而直接修改 Puppeteer 库里的文件肯定不是好的,我们可将此文件拷到自己工程文件夹下进行修改并引用。下图为修改过的 DeviceDescriptors 文件,在 userAgent 中加入服务端识别的标识:
  2. 浏览器实例的封装降低重复代码 下图是封装的 page 模块,返回页面和浏览器实例。也可以将浏览器实例与用例写在同一文件中直接使用,免去了传参的麻烦,不过当用例过多时这样写就会产生大量的重复代码。
  3. 浏览器实例不会被关闭 在每条用例中都会有关闭浏览器实例的动作,但往往一段时间后服务器上就会驻留大量 chrome 实例,占用了服务器大量的资源,同时也有可能对后续的用例执行产生影响。即便使用 try..catch{await browser.close()}这样的句子也无法解决问题。 官方解答是:“如果你想在 promise 执行完毕后无论其结果怎样都做一些处理或清理时,finally() 方法可能是有用的,它仅用于无论最终结果如何都要执行的情况”。此方法也确实有效。
  4. 元素位置不固定 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)
  5. page,测试的页面实例,Object
  6. selector,任意一次的 DOM 元素位置,String
  7. labelname,元素展示的部分或全部内容,给出的内容保证页面中唯一
  8. returns selector,返回当前页面所选元素正确的 selector 定位

基于 Puppeteer 的 UI 用例监控系统

目前客服 UI 自动化用例同时肩负着监控线上客服系统功能回归的任务,并随之开发了用例配套系统 “UI 用例监控系统”,系统的核心仍然是 “用例”。当执行线上用例失败后,系统首先会截取当前浏览器的快照,并将快照与异常结果一同上报至平台,同时发送报警信息给相关负责人。下图为系统中异常的记录,主要有异常信息、发生时间、执行人(获取的是系统用户名)、操作以及截屏图片等。

操作的备注功能功能主要是对异常数据人工打标,上报的数据将和标注后的数据进行对比。标注有两个属性分别为是否为 bug 与类别,如下图:

新产生的异常首先与库中异常数据进行比较,匹配后再以图片进行比较。如果均匹配成功则自动补充样本的标注信息,如果未匹配则不补充。同时如果与不是 bug 的匹配成功的话则不发报警信息,来减少误报情况。

系统核心模块如下图:

系统还有一些其他功能:

  1. 监控用例的两种执行方式 监听:集群上线完成后,会发送上线完成消息,监控平台收到消息后,会自动触发在服务器上的用例。当然消息会以集群名进行区分,用例有选择性的去执行。 定时任务:在 centos 服务器上创建定时任务,来定时触发用例监控线上功能。
  2. 执行人监控 目前用例分为线上和线下模式,由开关控制。用例场景完全一样,用户区分为线上和线下用户。但无论执行哪种模式,在哪执行用例,一旦发生异常都会上报至监控平台。这样你就可以清楚的知道,你的用例被人执行的情况与频率。
    1. 接入平台方式 一个 API 即可,并且自动帮助截取异常时的屏幕快照,不限制框架的使用,不限制用例编写的规范,只要是使用 node 环境的就可以。接入平台的 API 如下:monitorapi.FailMessage(e,casename,isOffline,...page)
    2. e,异常信息,或自己定义的 error,String
    3. casename,用例名称,String
    4. isOffline,线上线下标识,int 或 String
    5. page,测试的页面实例,{...any} Object
    6. returns void
    7. (自带异常时截取屏幕快照功能,并发送给监控平台)

在业务线中的具体应用

客服系统核心功能之一是用户与机器人交互与人工客服聊天,最初的测试方式是采用 java 创建多个 WebSocketClient 与服务端建立连接,写心跳逻辑与服务端保持连接,进而模拟消息发送与接收。全场景用例写好后,相当于写好了一个没有页面的用户端,成本非常高。
而使用 Puppeteer 实现 WebSocket 场景就相当简单了,可以快速创建多个 Chrome 实例模拟多个用户登陆并与机器人进行交互、申请人工,客服优先级接待等复杂场景。这些多用户场景用手动测试很难,但使用 UI 自动化还是非常理想的。目前这些复杂场景用例,已经提供开发自测或者执行冒烟用例时使用。

未来规划

  1. 仍然是用例,用例覆盖的场景,用例的健壮性,仍然是基础与重点。
  2. 平台会增加用例管理,与报告展示等基础功能。
  3. UI 层面的自动生成用例也是一个重点研究方向。


↙↙↙阅读原文可查看相关链接,并与作者交流