如果你曾经浏览过 《京喜前端自动化测试之路(一)》,可跳过前言局部浏览。

前言

京喜(原京东拼购)我的项目,作为京东策略级业务,领有千万级别的流量入口。为了保障线上业务的稳固运行,每月例行发展前端容灾演习,次要蕴含小程序及 H5 版本,要求各页面各模块在异常情况下进行适当的降级解决,不能呈现空窗、款式错乱、不合理的谬误提醒等体验问题。

容灾演习是一项长期继续的工作,且波及页面性能及场景多,人工的切换场景模仿异样导致演习效率较低,因而想通过开发自动化测试工具来晋升演习效率,让容灾演习工作随时能够轻松发展。因为京喜 H5 和小程序场景差别比拟大,自动化测试分 H5 和小程序两局部进行。后期曾经分享过 H5 的自动化测试计划 —— 京喜前端自动化测试之路(一),本文则次要讲述小程序版的自动化测试计划。

综上所述,咱们心愿京喜小程序自动化测试工具能够提供以下性能:

  1. 拜访指标页面,对页面进行截图;
  2. 模仿用户点击、滑动页面操作;
  3. 网络拦挡、模仿异常情况(接口响应码 500、接口返回数据异样);
  4. 操作缓存数据(模仿有无缓存的场景等)。

小程序自动化 SDK

聊到小程序的自动化工具,微信官网为开发者提供了一套小程序自动化 SDK —— miniprogram-automator , 咱们不须要关注技术选型,可间接应用。

小程序自动化 SDK 为开发者提供了一套通过内部脚本操控小程序的计划,从而实现小程序自动化测试的目标。

如果你之前应用过 Selenium WebDriver 或者 Puppeteer,那你能够很容易疾速上手。小程序自动化 SDK 与它们的工作原理是相似的,次要区别在于管制对象由浏览器换成了小程序。

个性

通过该 SDK,你能够做到以下事件:

  • 管制小程序跳转到指定页面
  • 获取小程序页面数据
  • 获取小程序页面元素状态
  • 触发小程序元素绑定事件
  • 往 AppService 注入代码片段
  • 调用 wx 对象上任意接口
  • ...

示例

const automator = require('miniprogram-automator')automator    .launch({        cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 工具 cli 地位(绝对路径)        projectPath: 'path/to/project', // 我的项目文件地址(绝对路径)    })    .then(async miniProgram => {        const page = await miniProgram.reLaunch('/pages/index/index')        await page.waitFor(500)        const element = await page.$('.banner')        console.log(await element.attribute('class'))        await element.tap()        await miniProgram.close()    })

综上所述,咱们抉择应用官网保护的 SDK —— miniprogram-automator 开发小程序的自动化测试工具,通过 SDK 提供的一系列 API ,实现拜访指标页面、模仿异样场景、生成截图的过程自动化。最初再通过人工比对截图,判断页面降级解决是否合乎预预期、用户体验是否敌对。

实现计划

原来的容灾演习过程:

小程序的通信形式改成 HTTPS ,通过 Whistle 对接口返回进行批改来模仿异常情况,验证各页面各模块的降级解决合乎预期。

现阶段的容灾演习自动化计划:

咱们将容灾演习过程分为自动化流程人工操作两局部。

自动化流程:

  1. 启动微信开发者工具(开发版);
  2. 拜访指标页面,模仿用户点击、滑动等行为;
  3. 模仿异样场景:拦挡网络申请,批改接口返回数据(接口返回 500、异样数据等);
  4. 生成截图。

人工操作:

自动化脚本执行结束后,人工比对各个场景的截图,判断是否合乎预期。

计划流程图:

开发实录

疾速创立测试用例

为了进步测试脚本的可维护性、扩展性,咱们将测试用例的信息都配置到 JSON 文件中,这样编写测试脚本的时候,咱们只需关注测试流程的实现。

测试用例 JSON 数据配置包含专用数据(global)公有数据

专用数据(global):各测试用例都须要用到的数据,如:模仿拜访的指标页面地址、名字、形容、设施类型等。

公有数据: 各测试用例特定的数据,如测试模块信息、api 地址、测试场景、预期后果、截图名字等数据。

{  "global": {    "url": "/pages/index/index",    "pageName": "index",    "pageDesc": "首页",    "device": "iPhone X"  },  "homePageApi": {    "id": 1,    "module": "home_page_api",    "moduleDesc": "首页主接口",    "api": "https://xxx",    "operation": "模仿响应码 500",    "expectRules": [      "1. 有缓存数据,显示容灾兜底数据",      "2. 申请容灾接口,显示容灾兜底数据",      "3. 容灾接口异样,显示信异样息、刷新按钮",      "4. 复原网络,点击刷新按钮,显示失常数据"    ],    "screenshot": [      {        "name": "normal",        "desc": "失常场景"      },      {        "name": "500_cache",        "desc": "有缓存-主接口返回500"      },      {        "name": "500_no_cache",        "desc": "无缓存-主接口返回500-容灾兜底数据"      },      {        "name": "500_no_cache_500_disaster",        "desc": "无缓存-主接口返回500-容灾兜底接口返回500"      },      {        "name": "500_no_cache_recover",        "desc": "无缓存-返回500-复原网络"      }    ]  },  …}

编写测试脚本

咱们以京喜首页主接口的测试用例为例子,通过模仿主接口返回 500 响应码的异样场景,验证主接口的异样解决机制是否欠缺、用户体验是否敌对。

预期成果:

  • 主接口异样,有缓存数据,显示缓存数据
  • 主接口异样,无缓存数据,则申请容灾接口,显示容灾兜底数据
  • 主接口、容灾接口异样,无缓存数据,显示信异样息、刷新按钮
  • 复原网络,点击刷新按钮,显示失常数据

测试流程:

场景实现:

依据测试流程以及配置的测试用例信息,编写测试脚本,模仿测试用例场景:

  1. 拜访页面
const miniProgram = await automator.launch({      cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 开发者工具命令行工具(绝对路径)      projectPath: 'jx_project', // 我的项目地址(绝对路径)})await miniProgram.reLaunch('/pages/index/index')
  1. 生成截图
await miniProgram.screenshot({    path: 'jx_weapp_index_home_page_500.png'})
  1. 模仿异样数据
const getMockData = (url, mockType, mockValue) => {    const result = {      data: 'test',      cookies: [],      header: {},      statusCode: 200,    }    switch (mockType) {      case 'data':        result.data = getMockResponse(url, mockValue) // 批改返回数据        break      case 'cookies':        result.cookies = mockValue // 批改返回数据        break      case 'header':        result.header = mockValue // 批改返回响应头        break      case 'statusCode':        result.statusCode = mockValue // 批改返回响应头        break    }    return {      rule: url,      result    }  } // 批改本地存储数据 const mockValue = {     data: {         modules: [{            tpl:'3000',            content: []         }]     } } const mockData =  [    getMockData(api1, 'statusCode', 500), // 模仿接口返回 500    getMockData(api2, 'data', mockValue) // 模仿接口返回异样数据    ... ] 
  1. 拦挡接口申请,批改返回数据
const interceptAPI = async (miniProgram, url, mockData) => {    try {      await miniProgram.mockWxMethod(        'request',        function(obj, data) { // 解决返回函数          for (let i = 0, len = data.length; i < len; i++) {            const item = data[i]            // 命中规定的返回 mockData            if (obj.url.indexOf(item.rule) > -1) {              return item.result            }          }          // 没命中规定的实在拜访后盾          return new Promise(resolve => {            obj.success = res => resolve(res)            obj.fail = res => resolve(res)            / origin 指向原始办法            this.origin(obj)          })        },        mockData, // 传入 mock 数据      )    } catch (e) {      console.error(`拦挡【${url}】API报错`)      console.error(e)    }  }await interceptAPI(interceptAPI, url, mockData)
    • miniProgram.mockWxMethod:笼罩 wx 对象上指定办法的调用后果。利用该 API,能够笼罩 wx.request API,拦挡网络申请,批改返回数据。
    • 目前是本地存储一份接口返回的 JSON 数据,通过批改本地的 JSON 数据生成 mockData。若须要批改接口实时返回的数据,可在 obj.success 中获取实时数据并批改。
    1. 革除缓存
    try {    await miniProgram.callWxMethod('clearStorage')} catch (e) {    await console.log(`革除缓存报错: `)    await console.log(e)}
    1. 点击刷新按钮
    const page = await miniProgram.currentPage()const $refreshBtn = await page.$('.page-error__refresh-btn') // 同 WXSS,仅反对局部 CSS 选择器await $refreshBtn.tap()
    1. 勾销拦挡,复原网络
    const cancelInterceptAPI = async (miniProgram) => {    try {      await miniProgram.restoreWxMethod('request') // 重置 wx.request ,打消 mockWxMethod 调用的影响。    } catch (e) {      console.error(`勾销拦挡【${url}】API报错`)      console.error(e)    }}await cancelInterceptAPI(miniProgram)

    启动自动化测试

    因为第一阶段的测试工具尚未平台化,先通过在终端输出命令行,运行脚本的形式,启动自动化测试。

    在我的项目的 package.json 文件中,应用 scripts 字段定义脚本命令:

     "scripts": {    "start": "node pages/index/index.js"  },

    运行环境:

    • 装置 Node.js 并且版本大于 8.0
    • 根底库版本为 2.7.3 及以上
    • 开发者工具版本为 1.02.1907232 及以上

    运行:

    在终端切入到我的项目根目录门路,输出以下命令行,就能够启动测试工具,运行测试脚本。

    $ npm run start

    测试后果

    人工比对截图后果:

    运行脚本示例:

    应用 SDK,你必须晓得 Shadow DOM

    当咱们想管制小程序页面时,需获取页面实例 page,利用 page 提供的办法管制页面内的元素。

    比方,当咱们想点击页面中搜寻框时,咱们个别会这么做:

     const page = await miniProgram.currentPage() const $searchBar = await page.$('search-bar') await $searchBar.tap()

    但这样真的可行吗?答案是:

    试试就晓得了。

    运行这段测试脚本后生成的截图:

    咱们失去的后果是:基本没有触发点击事件。

    Shadow DOM:

    它是 HTML 的一个标准,它容许在文档( document )渲染时插入一颗DOM元素子树,然而这个子树不在主 DOM 树中。

    它容许浏览器开发者封装本人的 HTML 标签、css 款式和特定的 javascript 代码、同时开发人员也能够创立相似 <input>、<video>、<audio> 等、这样的自定义的一级标签。创立这些标签内容相干的 API,能够被叫做 Web Component。

    Shadow DOM 的关键所在,它能够将一个暗藏的、独立的 DOM 附加到一个元素上。

    • Shadow host: 一个惯例 DOM 节点,Shadow DOM 会被附加到这个节点上。它是 Shadow DOM 的一个宿主元素。比方:<input />、<audio>、<video> 标签,就是 Shadow DOM 的宿主元素。
    • Shadow tree: Shadow DOM 外部的 DOM 树。
    • Shadow root: Shadow DOM 的根节点。通过 createShadowRoot 返回的文档片段被称为 shadow-root , 它和它的后辈元素,都会对用户暗藏。

    回到咱们刚刚的问题:

    因为小程序应用了 Shadow DOM,因而咱们不能间接通过 page 实例获取到搜寻框实在 DOM。咱们看到的页面中渲染的搜寻框,实际上是一个 Shadow DOM。因而,咱们必须先获取到搜寻框 Shadow DOM 的宿主元素,并通过宿主元素获取到搜寻框实在的 DOM,最初触发实在 DOM 的点击事件。

      const page = await miniProgram.currentPage()  const $searchBarShadow = await page.$('search-bar')  const $searchBar = await $searchBarShadow.$('.search-bar')  const { height } = await $searchBar.size()

    运行这段测试脚本后生成的截图:

    从截图能够看到,触发了搜寻框的点击事件。

    更多测试场景实现

    1. 下拉刷新

    const pullDownRefresh = async (miniProgram) => {    try {      await miniProgram.callWxMethod('startPullDownRefresh')    } catch (e) {      console.error('下拉刷新操作失败')      console.error(e)    }}

    2. 滚动到指定 DOM

    const page = await miniProgram.currentPage() // 获取页面实例const $recommendTabShadow = await page.$('recommend-tab') // 获取Shadow DOMconst $recommendTab = await $recommendTabShadow.$('.recommend') // 获取实在 DOMconst { top } = await $recommendTab.offset() // 获取DOM 定位await miniProgram.pageScrollTo(top) // 滚动到指定DOM

    3. 事件

    • 日志打印;
    • 监听页面解体事件
    // 日志打印时触发miniProgram.on('console', msg => {    console.log(msg.type, msg.args)  })})// 页面 JS 出错时触发page.on('error', (e) => {    console.log(e)})

    结语

    第一阶段的小程序自动化测试之路告一段落。和 H5 自动化测试一样,容灾演习已实现了半自动化,可通过在终端运行测试脚本,模仿异样场景主动生成截图,再配合人工比对截图操作,判断演习后果是否合乎预期。目前已投入到每个月的容灾演习中应用。

    因为 H5 和小程序的差别比拟大,第一阶段的自动化测试分两端进行,测试脚本语法也是截然不同,须要同时保护两套测试工具。为了升高保护老本,晋升测试脚本的开发效率,咱们正在研发第二阶段的自动化测试工具,一套代码反对两端测试,目前曾经进入内测阶段。更多彩蛋,敬请期待第二阶段自动化测试工具——多端自动化测试 SDK 。


    欢送关注凹凸实验室博客:aotu.io

    或者关注凹凸实验室公众号(AOTULabs),不定时推送文章。