最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

小程序自動(dòng)化測(cè)試的示例代碼

 更新時(shí)間:2020年08月11日 12:02:17   作者:Shenfq  
這篇文章主要介紹了小程序自動(dòng)化測(cè)試,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

背景

近期團(tuán)隊(duì)打算做一個(gè)小程序自動(dòng)化測(cè)試的工具,期望能夠做的業(yè)務(wù)人員操作一遍小程序后,自動(dòng)還原之前的操作路徑,并且捕獲操作過(guò)程中發(fā)生的異常,以此來(lái)判斷這次發(fā)布時(shí)候會(huì)影響小程序的基礎(chǔ)功能。

上述描述看似簡(jiǎn)單,但是中間還是有些難點(diǎn)的,第一個(gè)難點(diǎn)就是如何在業(yè)務(wù)人員操作小程序的時(shí)候記錄操作路徑,第二個(gè)難點(diǎn)就是如何將記錄的操作路徑進(jìn)行還原。

自動(dòng)化 SDK

如何將操作路徑還原這個(gè)問(wèn)題,當(dāng)然首選官方提供的 SDK: miniprogram-automator 。

小程序自動(dòng)化 SDK 為開(kāi)發(fā)者提供了一套通過(guò)外部腳本操控小程序的方案,從而實(shí)現(xiàn)小程序自動(dòng)化測(cè)試的目的。通過(guò)該 SDK,你可以做到以下事情:

  • 控制小程序跳轉(zhuǎn)到指定頁(yè)面
  • 獲取小程序頁(yè)面數(shù)據(jù)
  • 獲取小程序頁(yè)面元素狀態(tài)
  • 觸發(fā)小程序元素綁定事件
  • 往 AppService 注入代碼片段
  • 調(diào)用 wx 對(duì)象上任意接口
  • ...

上面的描述都來(lái)自官方文檔,建議閱讀后面內(nèi)容之前可以先看看官方文檔 ,當(dāng)然如果之前用過(guò) puppeteer ,基本是無(wú)縫銜接。下面簡(jiǎn)單介紹下 SDK 的使用方式。

// 引入sdk
const automator = require('miniprogram-automator')

// 啟動(dòng)微信開(kāi)發(fā)者工具
automator.launch({
 // 微信開(kāi)發(fā)者工具安裝路徑下的 cli 工具
 // Windows下為安裝路徑下的 cli.bat
 // MacOS下為安裝路徑下的 cli
 cliPath: 'path/to/cli',
 // 項(xiàng)目地址,即要運(yùn)行的小程序的路徑
 projectPath: 'path/to/project',
}).then(async miniProgram => { // miniProgram 為 IDE 啟動(dòng)后的實(shí)例
 // 啟動(dòng)小程序里的 index 頁(yè)面
 const page = await miniProgram.reLaunch('/page/index/index')
 // 等待 500 ms
 await page.waitFor(500)
 // 獲取頁(yè)面元素
 const element = await page.$('.main-btn')
 // 點(diǎn)擊元素
 await element.tap()
 // 關(guān)閉 IDE
 await miniProgram.close()
})

有個(gè)地方需要提醒一下:使用 SDK 之前需要開(kāi)啟開(kāi)發(fā)者工具的服務(wù)端口,要不然會(huì)啟動(dòng)失敗。

捕獲用戶行為

有了還原操作路徑的辦法,接下來(lái)就要解決記錄操作路徑的難題了。

在小程序中,并不能像 web 中通過(guò)事件冒泡的方式在 window 中捕獲所有的事件,好在小程序所以的頁(yè)面和組件都必須通過(guò) PageComponent 方法來(lái)包裝,所以我們可以改寫(xiě)這兩個(gè)方法,攔截傳入的方法,并判斷第一個(gè)參數(shù)是否為 event 對(duì)象,以此來(lái)捕獲所有的事件。

// 暫存原生方法
const originPage = Page
const originComponent = Component

// 改寫(xiě) Page
Page = (params) => {
 const names = Object.keys(params)
 for (const name of names) {
 // 進(jìn)行方法攔截
 if (typeof obj[name] === 'function') {
  params[name] = hookMethod(name, params[name], false)
 }
 }
 originPage(params)
}
// 改寫(xiě) Component
Component = (params) => {
 if (params.methods) {
  const { methods } = params
  const names = Object.keys(methods)
  for (const name of names) {
  // 進(jìn)行方法攔截
  if (typeof methods[name] === 'function') {
   methods[name] = hookMethod(name, methods[name], true)
  }
  }
 }
 originComponent(params)
}

const hookMethod = (name, method, isComponent) => {
 return function(...args) {
 const [evt] = args // 取出第一個(gè)參數(shù)
 // 判斷是否為 event 對(duì)象
 if (evt && evt.target && evt.type) {
  // 記錄用戶行為
 }
 return method.apply(this, args)
 }
}

這里的代碼只是代理了所有的事件方法,并不能用來(lái)還原用戶的行為,要還原用戶行為還必須知道該事件類型是否是需要的,比如點(diǎn)擊、長(zhǎng)按、輸入。

const evtTypes = [
 'tap', // 點(diǎn)擊
 'input', // 輸入
 'confirm', // 回車
 'longpress' // 長(zhǎng)按
]
const hookMethod = (name, method) => {
 return function(...args) {
 const [evt] = args // 取出第一個(gè)參數(shù)
 // 判斷是否為 event 對(duì)象
 if (
  evt && evt.target && evt.type &&
  evtTypes.includes(evt.type) // 判斷事件類型
 ) {
  // 記錄用戶行為
 }
 return method.apply(this, args)
 }
}

確定事件類型之后,還需要明確點(diǎn)擊的元素到底是哪個(gè),但是小程序里面比較坑的地方就是,event 對(duì)象的 target 屬性中,并沒(méi)有元素的類名,但是可以獲取元素的 dataset。

為了準(zhǔn)確的獲取元素,我們需要在構(gòu)建中增加一個(gè)步驟,修改 wxml 文件,將所以元素的 class 屬性復(fù)制一份到 data-className 。

<!-- 構(gòu)建前 -->
<view class="close-btn"></view>
<view class="{{mainClassName}}"></view>
<!-- 構(gòu)建后 -->
<view class="close-btn" data-className="close-btn"></view>
<view class="{{mainClassName}}" data-className="{{mainClassName}}"></view>

但是獲取到 class 之后,又會(huì)有另一個(gè)坑,小程序的自動(dòng)化測(cè)試工具并不能直接獲取頁(yè)面里自定義組件中的元素,必須先獲取自定義組件。

<!-- Page -->
<toast text="loading" show="{{showToast}}" />
<!-- Component -->
<view class="toast" wx:if="{{show}}">
 <text class="toast-text">{{text}}</text>
 <view class="toast-close" />
</view>
// 如果直接查找 .toast-close 會(huì)得到 null
const element = await page.$('.toast-close')
element.tap() // Error!

// 必須先通過(guò)自定義組件的 tagName 找到自定義組件
// 再?gòu)淖远x組件中通過(guò) className 查找對(duì)應(yīng)元素
const element = await page.$('toast .toast-close')
element.tap()

所以我們?cè)跇?gòu)建操作的時(shí)候,還需要為元素插入 tagName。

<!-- 構(gòu)建前 -->
<view class="close-btn" />
<toast text="loading" show="{{showToast}}" />
<!-- 構(gòu)建后 -->
<view class="close-btn" data-className="close-btn" data-tagName="view" />
<toast text="loading" show="{{showToast}}" data-tagName="toast" />

現(xiàn)在我們可以繼續(xù)愉快的記錄用戶行為了。

// 記錄用戶行為的數(shù)組
const actions = [];
// 添加用戶行為
const addAction = (type, query, value = '') => {
 actions.push({
 time: Date.now(),
 type,
 query,
 value
 })
}

// 代理事件方法
const hookMethod = (name, method, isComponent) => {
 return function(...args) {
 const [evt] = args // 取出第一個(gè)參數(shù)
 // 判斷是否為 event 對(duì)象
 if (
  evt && evt.target && evt.type &&
  evtTypes.includes(evt.type) // 判斷事件類型
 ) {
  const { type, target, detail } = evt
  const { id, dataset = {} } = target
  const { className = '' } = dataset
  const { value = '' } = detail // input事件觸發(fā)時(shí),輸入框的值
  // 記錄用戶行為
  let query = ''
  if (isComponent) {
  // 如果是組件內(nèi)的方法,需要獲取當(dāng)前組件的 tagName
  query = `${this.dataset.tagName} `
  }
  if (id) {
  // id 存在,則直接通過(guò) id 查找元素
  query += id
  } else {
  // id 不存在,才通過(guò) className 查找元素
  query += className
  }
  addAction(type, query, value)
 }
 return method.apply(this, args)
 }
}

到這里已經(jīng)記錄了用戶所有的點(diǎn)擊、輸入、回車相關(guān)的操作,但是還有一個(gè)滾動(dòng)屏幕的操作還沒(méi)記錄。這里可以直接監(jiān)聽(tīng) Page 的 onPageScroll。

// 記錄用戶行為的數(shù)組
const actions = [];
// 添加用戶行為
const addAction = (type, query, value = '') => {
 if (type === 'scroll' || type === 'input') {
 // 如果上一次行為也是滾動(dòng)或輸入,則重置 value 即可
 const last = this.actions[this.actions.length - 1]
 if (last && last.type === type) {
  last.value = value
  last.time = Date.now()
  return
 }
 }
 actions.push({
 time: Date.now(),
 type,
 query,
 value
 })
}

Page = (params) => {
 const names = Object.keys(params)
 for (const name of names) {
 // 進(jìn)行方法攔截
 if (typeof obj[name] === 'function') {
  params[name] = hookMethod(name, params[name], false)
 }
 }
 const { onPageScroll } = params
 // 攔截滾動(dòng)事件
 params.onPageScroll = function (...args) {
 const [evt] = args
 const { scrollTop } = evt
 addAction('scroll', '', scrollTop)
 onPageScroll.apply(this, args)
 }
 originPage(params)
}

這里有個(gè)優(yōu)化點(diǎn),就是滾動(dòng)操作記錄的時(shí)候,可以判斷一下上次操作是否也為滾動(dòng)操作,如果是同一個(gè)操作,則只需要修改一下滾動(dòng)距離即可,以為兩次滾動(dòng)可以一步到位。同理,輸入事件也是,輸入的值也可以一步到位。

還原用戶行為

用戶操作完畢后,可以在控制臺(tái)輸出用戶行為的 json 文本,把 json 文本復(fù)制出來(lái)后,就可以通過(guò)自動(dòng)化工具運(yùn)行了。

// 引入sdk
const automator = require('miniprogram-automator')

// 用戶操作行為
const actions = [
 { type: 'tap', query: 'goods .title', value: '', time: 1596965650000 },
 { type: 'scroll', query: '', value: 560, time: 1596965710680 },
 { type: 'tap', query: 'gotoTop', value: '', time: 1596965770000 }
]

// 啟動(dòng)微信開(kāi)發(fā)者工具
automator.launch({
 projectPath: 'path/to/project',
}).then(async miniProgram => {
 let page = await miniProgram.reLaunch('/page/index/index')
 
 let prevTime
 for (const action of actions) {
 const { type, query, value, time } = action
 if (prevTime) {
  // 計(jì)算兩次操作之間的等待時(shí)間
  await page.waitFor(time - prevTime)
 }
 // 重置上次操作時(shí)間
 prevTime = time
 
 // 獲取當(dāng)前頁(yè)面實(shí)例
 page = await miniProgram.currentPage()
 switch (type) {
  case 'tap':
   const element = await page.$(query)
  await element.tap()
  break;
  case 'input':
   const element = await page.$(query)
  await element.input(value)
  break;
  case 'confirm':
   const element = await page.$(query)
    await element.trigger('confirm', { value });
  break;
  case 'scroll':
  await miniProgram.pageScrollTo(value)
  break;
 }
 // 每次操作結(jié)束后,等待 5s,防止頁(yè)面跳轉(zhuǎn)過(guò)程中,后面的操作找不到頁(yè)面
 await page.waitFor(5000)
 }

 // 關(guān)閉 IDE
 await miniProgram.close()
})

這里只是簡(jiǎn)單的還原了用戶的操作行為,實(shí)際運(yùn)行過(guò)程中,還會(huì)涉及到網(wǎng)絡(luò)請(qǐng)求和 localstorage 的 mock,這里不再展開(kāi)講述。同時(shí),我們還可以接入 jest 工具,更加方便用例的編寫(xiě)。

總結(jié)

看似很難的需求,只要用心去發(fā)掘,總能找到對(duì)應(yīng)的解決辦法。另外微信小程序的自動(dòng)化工具真的有很多坑,遇到問(wèn)題可以先到小程序社區(qū)去找找,大部分坑都有前人踩過(guò),還有一些一時(shí)無(wú)法解決的問(wèn)題只能想其他辦法來(lái)規(guī)避。最后祝愿天下無(wú) bug。

到此這篇關(guān)于小程序自動(dòng)化測(cè)試的示例代碼的文章就介紹到這了,更多相關(guān)小程序自動(dòng)化測(cè)試內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • javascript setTimeout和setInterval計(jì)時(shí)的區(qū)別詳解

    javascript setTimeout和setInterval計(jì)時(shí)的區(qū)別詳解

    window對(duì)象有兩個(gè)主要的定時(shí)方法,分別是setTimeout 和 setInteval 他們的語(yǔ)法基本上相同,但是完成的功能取有區(qū)別。
    2013-06-06
  • javascript 流暢動(dòng)畫(huà)實(shí)現(xiàn)原理

    javascript 流暢動(dòng)畫(huà)實(shí)現(xiàn)原理

    瀏覽器目前來(lái)說(shuō)是沒(méi)有抗鋸齒效果的(將來(lái)不一定哦),這樣dom元素外觀的改變就被限制在1個(gè)像素為最佳效果。

    2009-09-09
  • 前端深入理解Typescript泛型概念

    前端深入理解Typescript泛型概念

    這篇文章主要介紹了前端深入理解Typescript泛型概念,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • JavaScript實(shí)現(xiàn)與使用發(fā)布/訂閱模式詳解

    JavaScript實(shí)現(xiàn)與使用發(fā)布/訂閱模式詳解

    這篇文章主要介紹了JavaScript實(shí)現(xiàn)與使用發(fā)布/訂閱模式,較為詳細(xì)的分析了發(fā)布/訂閱模式的概念、原理并結(jié)合實(shí)例形式分析了javascript實(shí)現(xiàn)與使用發(fā)布/訂閱模式的相關(guān)操作技巧,需要的朋友可以參考下
    2019-01-01
  • js設(shè)計(jì)模式之代理模式及訂閱發(fā)布模式實(shí)例詳解

    js設(shè)計(jì)模式之代理模式及訂閱發(fā)布模式實(shí)例詳解

    這篇文章主要介紹了js設(shè)計(jì)模式之代理模式及訂閱發(fā)布模式,結(jié)合實(shí)例形式詳細(xì)分析了代理模式及訂閱發(fā)布模式的概念、原理、實(shí)現(xiàn)方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2019-08-08
  • 基于JS實(shí)現(xiàn)父組件的請(qǐng)求服務(wù)過(guò)程解析

    基于JS實(shí)現(xiàn)父組件的請(qǐng)求服務(wù)過(guò)程解析

    這篇文章主要介紹了基于JS實(shí)現(xiàn)父組件的請(qǐng)求服務(wù)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • JavaScript實(shí)現(xiàn)私有屬性的幾種方式小結(jié)

    JavaScript實(shí)現(xiàn)私有屬性的幾種方式小結(jié)

    在JavaScript中,私有屬性是指只能在對(duì)象內(nèi)部訪問(wèn)的屬性,外部無(wú)法直接訪問(wèn),JavaScript并沒(méi)有提供官方的私有屬性的支持,但可以通過(guò)一些技巧來(lái)模擬實(shí)現(xiàn)私有屬性,所以本文給大家總結(jié)了JavaScript實(shí)現(xiàn)私有屬性的幾種方式,需要的朋友可以參考下
    2024-04-04
  • JavaScript中的FileReader圖片預(yù)覽上傳功能實(shí)現(xiàn)代碼

    JavaScript中的FileReader圖片預(yù)覽上傳功能實(shí)現(xiàn)代碼

    本文通過(guò)實(shí)例代碼給大家介紹了js中的FileReader圖片預(yù)覽上傳功能,代碼分為html和js代碼兩部分,具體實(shí)現(xiàn)代碼大家參考下本文
    2017-07-07
  • Layer.js實(shí)現(xiàn)表格溢出內(nèi)容省略號(hào)顯示,懸停顯示全部的方法

    Layer.js實(shí)現(xiàn)表格溢出內(nèi)容省略號(hào)顯示,懸停顯示全部的方法

    今天小編就為大家分享一篇Layer.js實(shí)現(xiàn)表格溢出內(nèi)容省略號(hào)顯示,懸停顯示全部的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-09-09
  • JS實(shí)現(xiàn)圖片延遲加載并淡入淡出效果的簡(jiǎn)單方法

    JS實(shí)現(xiàn)圖片延遲加載并淡入淡出效果的簡(jiǎn)單方法

    我們大家都知道,對(duì)于一個(gè)網(wǎng)站最占用帶寬,最影響頁(yè)面顯示速度的東西就是圖片。圖片是很重要的,作為一個(gè)站長(zhǎng)我們是千方百計(jì)的使用各種技巧來(lái)優(yōu)化圖片,但其實(shí)有一種簡(jiǎn)單的方法,只需要幾行代碼就能達(dá)到這種效果。同時(shí)還附加一種淡入淡出的顯示效果,下面一起來(lái)看看。
    2016-08-08

最新評(píng)論

得荣县| 广德县| 武隆县| 齐齐哈尔市| 会同县| 陆河县| 新竹市| 安图县| 阿拉善左旗| 灵山县| 九龙坡区| 娄烦县| 绥滨县| 杭锦旗| 徐水县| 三河市| 邮箱| 彭水| 于都县| 上虞市| 淅川县| 汝城县| 辽宁省| 达孜县| 赤壁市| 天等县| 通道| 盐池县| 北流市| 肥乡县| 三门峡市| 呼图壁县| 社旗县| 钦州市| 成安县| 吉首市| 静宁县| 利川市| 沅江市| 天等县| 凤山县|