假如你现已阅读过 《京喜前端自动化测验之路(一)》,可跳过前语部分阅读。

前语

京喜(原京东拼购)项目,作为京东战略级业务,拥有千万等级的流量入口。为了保障线上业务的稳定运转,每月例行开展X W H U m d前端容灾演习,首要包括小程序及 H5 版别,要求各页面各模块在反常情况下进行适当的降级处理,不能呈现空窗、样式错乱、不合B m 3 ;理的过错提示等体会问题。n . 8 % +

容灾演习是一项长期持续的作业,且涉及页面功能及场景多,人工的切换场景模仿反常导致演习功率较低,因而想经过开发自动化测验东西来提高演习功率,让容灾演习作业随时能够轻松开展。因为京喜 H5 和小程序场景差异比较大,自动化测验分 H5 和小程序两部分进行。前期现已共享过 H5 的自动化测验计划 —— 京喜前端自动化测验之路(一),本文则首要讲述小程序版的自动化测验计划。

综上所述,咱们希望京喜小程序自动h K V 4 (化测验东西能够供给以下功能[ s | # g 3 T D

  1. 拜访方针页面,对页: i j @ @ I 5面进行0 ; p G Y G k截图;
  2. 模仿用户点击、滑动页面f 2 =操作;
  3. 网络阻拦、模仿反常情况(接口呼应码 500、接口回来数据反常);
  4. 操作缓存数据(模仿L L a T v E w k |有无缓存n / 5的场景等)。

小程序自动化 SDK

聊到小程序的自动化O & 3东西,微信官方为开发者供给了一套小程序自动化 SDK —— miniprogrt G 8 J qam-automator , 咱们不需求重视技能选型,可直接运用。

小程序自动化 SDK 为开发者供给了一套经过外部脚本操控小程序的计划,从而完成小程序自动化测验的意图。

假如你之前运用过 Selenium WebDriver 或许 Puppeteer,那你2 U ; _能够很简单快速上手。小程序自动化 SDK 与它们的作业原理是相似的,首要h f e ) .差异在于操控方针由浏览器换成了小程序。

特性

经过该 SDM 2 HK,你能够做到以下事情:

  • 操控小程序跳转到指定页面
  • 获取小程序页面数据
  • O N a 3取小程序页面元素状况
  • 触发小程序元素绑定事情
  • 往 AppService 注入代码片段
  • 调用 wx 方针上恣意接口

示例

const automator = r9 g 8 } % D ! Iequire('miniprogram-autc 8 l Yomator')
auto{ E . v .mator
.launch({
cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 东西 cli 方位(绝对路. Q W W G g径)L k t t ; H t $ N
projectPath: 'path/to/project', // 项目文件地址b l Z y - 1(绝对路径)
})
.thB m ^ # 8 % 7en(async minio C I r XProgram => {
const page = await miniProgram.reLaunch('/pages/index/index')
await page.waitFor(500)
const element = await page.$('.banner')
console.log(D $ + - w v } / $await element.attribute('class'))
aw# h K * _ tait element.tap()
await miniProgram.close()
})

综上所述,咱们选择运用官方保护的 SDK —— miniprogram-automator 开发小程序的自动化测验东西,经过 SDK 供给的一系列 API ,完成拜访方针页面、z ? d模仿反常场景、生成截图的进程自动化。最终再经过人工比对截图,判断页面降级处理是否契合预预期、用户体会是否友爱。

完成计划

原来的容灾演习进程:

小程序的通讯O ) . – M N办法改成 HTTPS ,经过 Whistle 对接口回$ / F %来进行修正来模仿反常情况,验证各页面各模块的降级处理契合预期。

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

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

自动化流程:

  1. ) { i动微信开发者东西(开发版);
  2. 拜访方针页面,模仿用户点击、滑动等行为;
  3. 模仿反常场景:阻拦网络恳求,修正接口回来数据(接口回来 500、反常数据等)n H h 2 f
  4. 生成截图。

人工操作:

自动化脚本Z l i执行完毕后,人工比对各个场景的截图,h g g [ : ? )判断是否契合预期。

计划流程图:

京喜前端自动化测试之路(小程序篇)

开发实录

快速创立| O 5 Ka Y f验用w 8 e 0 j W

为了2 8 Z i N a + | =提高 m % e s U { ] s测验脚本的可保护性、扩展性,咱们将测验用例的信息都装备到 JSON 文件中,这样编写测验` M & e `脚本的时分,咱们只需重视测验流程的完成。

测验用例 JSON 数据装备包括公用数据(glo+ b d 4 M l ( ob[ % 4 h ! 9 1 ) .al2 $ V i私有数据

公用数据(global):各测验用例都需求用到的数据,如:模仿拜访的方针页面地址、姓名、描述、设备类型等。

私有数据: 各测验用例特定的数据,如测验模块信息、apiQ { + $ v ^ : ] 地址、测验场景、预期成果、截图姓名等数据j k { $

{
"global"p x K n ;: {
"url": "/pages/index/index",
"pageName": "index",
"pageDesc": "主页",
"device": "iPhone X"
},
"homePageApi": {
"id": 1,
"module": "home_page_api",
"moduleDesc": "主页主h ; D 2接口",
"api": "htt& @ 5 sps://xxx",
"operation": "模仿呼应码 500",
"expectRules": [
"1. 有缓存数据,显现容灾兜底数据",
"2. 恳求容灾接口,显现容灾兜底数据",
"3. 容灾接W | i ` h N $ d S口反常,显现信反常息、改写按钮",
"4. 康复网络,点击改写按钮Z | ( 3,显现正常数据"
],K V o  S G  m +
"screenshot": [
{
"name": "normal",
"desc": "正常场景"
},
{
"name": "500_cache",
"desc": "有缓存-主接口回来500"
},
{
"name": "500_no_cache",
? R d ` ^ J v z }"desc": "无缓存-主接口回来500-容灾兜底数据"
},
{
"name": "500_no_cache_500_disaster",
"desc": "无缓存-主s X & x $ G w |接口回来500-容灾兜底接口回来500"
},
{
"name": "500_no_cache_recover",
"desc": "无缓存-回来500-康复网络"
}
]
},
…
}

编写测验脚本

咱们以京喜主页主接口的测验用例为例子,经过模仿主接口回来 500 呼应码的反常场景,验证主接口的反常处理机制是否完善、用户体会是否友爱。

预期效果:

  • 主接口反常,有缓存数据,显现^ Y * T % M缓存数据
  • 主接口反常,无缓存数据,则恳求容灾接口,显现容灾兜底数据
  • 主接口、容灾接口反常,无缓存数据,显现信反常d z d T X H T y (息、改写按钮
  • 康复网络,点击改写按钮,显现正常数据

测验流程:

京喜前端自动化测试之路(小程序篇)

场景完成:

根据测验流程以及装备的测验用例信息,编写测验脚本,模仿测验用例场景:

  1. 拜访页面
const miniProgram = await automaY L  g Y ] 3tor.launch({
cliPath: '/Applications/wechatwebdevtools.app/Contents/f  - 0 # M H L `MacOS/clii ) | *', // 开发者东西指令行东西(绝对路径)
projectPath: 'jx_project', // 项目地址(绝对路径)
})
await miniProgram.reLaunch('/pages/in6 [ % Edex/index')
  1. 生成截图
await miniProgram6 C X J e %.screenshot({
path: 'jx_weapp_index_home_page_500.h t L s npng'v ; Z &
})) t ] b b
  1. – 4 K S g仿反常数& G w : . , 9 2
const getMockData = (urf O k & , sl, mockType, mockValue) => {
const result = {$ 5 2 Z R G z b
data: 'test',
cookies:c J B [],
header: {},
statusCode: 200,
}
switch (mockType) {
case 'data':
r. [ e n # V ? _esS k 3 Vult.data =^ E c getMockResponse(url, mockValue) // 修正回来数据
break
case 'cookies':
result.cookies = mockValue$ E O J // 修正回来数据
break
case 's } eheader':
result.header = mockValue // 修X Z r R C正回来呼应头
break
case 'stam O d C gtusCode':
result.statusCode = mockValue // 修正回来呼应头
break
}
return {
rule: url,
result0 W F w 6 P Y t
}
}
// 修正本地存储数据
const mockValue = {
data: {
modules: [{
tpl:'3000',
content) B u | V: []
}]
}
}
const mockData =  [
getMockDD a U | T Y !ata(api1, 'statusCode', 500), // 模仿接口回来 500
getMockData(api2, I $ 6 * C 1 { A'data', mockValue) // 模仿接口回来反常数据
...
]
  1. 阻拦接口恳求,修正回来数据
const interceptAK n CPI = async (miniProgram, urB  0 hl, mockData)p . N k / => {
try {
await miniProgram.mockWxMethod(
'requp @ 0 o 1estH $ x + 9 R 9 n',
function(obj, data) { // 处理回来函数
for (let i = 0, len = data.length; i < len; i++) {@ O a d
const item = data[i]
// 射中规矩的回来 mocZ Z H Q 6 f _ qkData
if (obj.url.indexOf(item.rule) > -1) {
return item.rev Y b  esult
}
}
// 没射中规矩的实在拜访后台
re- H 9turn new Promise(resolve => {
o$ Q h nbj.success? J M = t , y = res =>B % $ 9 B y; resolve(, 7 E 8res)
obj.fail = res => resolve(res)
/ oU r F D p v 7 - yrigX ` x  m w h Lin 指向原始办法
this.origin(obj)
})
},
mockData, // 传入 mock 数据
)
} catch (e) {
console.error(`阻拦【$) a h ({url}】API报错`)
consF @ # r % y 6 role.error(e)
}
}
awaitA 2 d ~ interceptAPI(in| q Itercer x % b ] y XptAPI, url, mockData)
  • miniProgram.mockWxMethod:掩盖 wx 方针上指定办法的调用成果。利用该 API,能够掩盖 wx.request API,阻拦网络恳求,修正回来数据。
  • 现在是本地存储一份接口回来的 JSON 数据,经过修正本地的 JSON 数据生成 mockData。若需求修正接口实时回来的数据,可在 obj.successl : , ?获取实时数据并修正。
  1. 铲除缓存
try {
await miniProgram.callY  + ] TWr ! [ x p G QxMethod('cleaS # H n ! C grStorage')
} cat| y J I S $ Ich (e) {
await console.log(`铲除缓存报错: `)
await console.log(e)
}
  1. 点击改写按钮
const page = await miniProg3 V p $ Z 8 | Fram.currentPage(Q ~ . p q } . a ^)
cont j + I Z st $refreshBtn = awaity *  w * ! page.$('.page-eD g 4 y Frror__refresh-btn') // 同 WXSS,仅支持部分 CSS 选择器V g J Q - , ( X y
await $refreshBtn.tap()
  1. 取消阻拦,康复网络
const caW - r } ; g KncelT { w d N c @ P +InterceptAPI = async (miniProgram)l 7 5 N S => {
try {
await miniProX , B *gram.restoreWxMethod('request') // 重置 wx.request ,消除 mockWxMethod 调用的影T U 2响。
} catch (e) {
console.error(`取消阻拦【${url}】API报错`)
console.error(Q 9 , ) R # e ]e)
}
}
await cancelInterce# 8 - vptAPI(miniProgram)

发动自动化测验

因为第一阶段的测验东西没有平台化,先经过在) A # 4 e @ ` r q终端8 A 3 m Y = ;输入指令行,运转脚本的办法,发动自动化测验。

在项意图 package.jsox $ ? Ln 文件中,运用 scripts 字段界说脚本指令:

 "scripts": {
"startH & } D J x d p"0 F 3 -: "node pages/index/index.js"
},

运转环境:

  • 装置 Node.js 而且版别大于 8.0
  • 基础库版别为 2.7.3 及以上
  • 开发者东西版别为 1.02.1907232 及以上

运转:

在终端切= { 9入到项目根目录路径,输入以下指令行,就能够发动测验东西9 ` t e n 1 ;,运转测验脚本。

$ npm run start

M X 4 4 5 x验成果

人工比对截图成果:

京喜前端自动化测试之路(小程序篇)

运转脚本示例:

京喜前端自动化测试之路(小程序篇)

运用 SDK,你必须知道 Shadow DOM

当咱们想e ~ % F , b + u操控小程序页面时,需获取页面实例 page,利? ^ # )用 page 供给的办法操控页面内的元素。

比如,当咱们想点击页面中查找框时,咱们一般会这么做:

 const page =i % * 8 m 5 Q H ~ await miniProgram.currentPage()
const $searchBar = await page.$('seaZ 4 o % ? Nrch-bar')
await $searchBar.tap()

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

试试就知道了。

运转这段测验脚本后生成的截图:

京喜前端自动化测试之路(小程序篇)

咱们得到的成果是:根本没有触发点击事情。

Shadow DOM:f ( } k ) |

它是 HTML 的一个标准,它答应在文档( document )渲染时刺进一颗DOM元素子树,但是g Q G这个子树不在主 DC A | j q I BOM 树中。

它答应浏H ] J # i ( 1 c览器开发者封装自己的 HTML 标签、css 样式和特定的 javaO M S W ) | `script 代码、一起开发人员也能够创立相似 <input>、<video>、<audio> 等、这样的自界说的一级标/ | 9 ~签。创立这些标签内容相关的 API,能够被叫做 Web Component。

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

  • Shadow host: 一个常规 DOM 节点,Shadow DOM 会被$ t W . r附加到这个节点上。它是 Shadow DOM 的一个宿主元素。比如:<input />、<audio>、<video> 标签,便是 Shadow DOM 的宿主元素。
  • Shadow tra 6 D ? Z U -ee: Shadow D7 ^ k . 3 uOM 内部的 DOM 树。
  • Shadow root: Shadow DOM 的根节点。经过 creatD d . EeShadowRoot 回来的文档片段被称为 shadow-root , 它和它的子孙元素,都会对用户躲藏。

回到咱们刚刚的问题:

因为小程序运用了 Shadow DC ^ Z S l bOM,因而咱们不能直接经过$ 3 f 3 F Q page 实例获取到查找框实在 DOM。咱们看到的页面中渲染的` T 9 l Z P查找框,实际上是一个 Shadow DO| ! : Q nM。因而,咱们必须先获取到查找框 Shadow DOM 的宿主元素,并经过宿主元素获取到查找框实在的 DOM,最终触发实在 DOM 的点击事情。

  const page = await miniProc s J C Y a 7gram.currentPage()
const $sL u & d } dearchBarShadowr k 8 ) = await page.$('search-bar')
const $searchBar = await $searchB 5 = ? w @arShadow.$('.search-bar')
const { height } = await $searo , S w r ) ` !ch{ I A z 3Bar.size()

运转这段测验脚本后生成的截图:

京喜前端自动化测试之路(小程序篇)

从截图能够看到,触发了查找框的点击事情。

更多测验场景完成

1. 下拉改写

const pullDownRefreX B G Q { esh = async (miniProgr! Z } : s Q  nam) => {
try {
await miniPe M Z G 6 y ? i [rogrL : ) $ U ^am.callWxMethod('starI E  L B etPul? D NlDownRefresh')
} catch7 : ! ) H D H $ (e) {
console.error('下拉改写操作失利')
console.error(e)
}
}

2. 滚动到指定 DO{ B _ ] P VM

const page = await miniProgram.currentPage() // 获取页面实例
consX z ! K Lt $recommendTabShad{ Y u } ] s $ ~ _ow = await page.$('recommend-tab') // 获取Shadow DOM
const $recommendTab = await $recomme5 G & . ? =ndTabShadow.$('.recommend') // 获取实在 DOM
const { top } = awaiV p n K $ $t $recommendTab.offset() // 获取DO~ g A H q # TM 定位
await mi6 P OniProgram.pageScrollTo(top$ ( j) // 滚动到指定DOM

3. 事情

  • 日志打印;
  • 监听页面崩溃事情
// 日志打印时触发
miniProgram.on('consolK G m t ;e', msg => {^ ( J e x ~ 9
console.log(msg.type, msg.args)6 = W | a X M
})
})
// 页面 JS 犯错时触发
page.on('error', (e) =>! k | e s v X s ,; {
console.log(ep [ _ h)
})

结语

第一阶段的小程序自动化测验之路告一段落。和 H5 自动化测验一样,容灾演习已完成了半自动化,可经过在终端运转测验脚本,模仿反常场景r a _ g 9自动生成截图,再合作人工比对截图操作,判断演习成果是否契合预期。现在已投入到每个月的容灾演习中运用。

因为 H5 和小程序的差异比较大,第一阶O } Z w ] 0 Z } L段的自动化测验分两端进行,测验脚本语法也是天壤之别,需求一起保护两套测验东西。为了降低保护成本,提高测验脚本的开发功率,咱们正在研制第二阶段的自动化测验东西,一套代码支持两端测验,现在现已进入内测阶段。更多彩蛋,敬请期待第二阶段自动化测验东西——多端自动化测验Z N N L w / b ) SDK 。


欢迎重视凹凸实验室博客:aotu.io

或许重视凹凸实验室大众号(AOTULabs),不守时推送文章:

京喜前端自动化测试之路(小程序篇)