Maison > Applet WeChat > Développement de mini-programmes > Explication détaillée des tests automatisés de petits programmes

Explication détaillée des tests automatisés de petits programmes

coldplay.xixi
Libérer: 2020-08-21 17:11:47
avant
3379 Les gens l'ont consulté

Explication détaillée des tests automatisés de petits programmes

[Recommandations d'apprentissage associées : Tutoriel du mini programme WeChat]

Contexte

Récemment, l'équipe prévoit de faire un test automatisé d'un mini-programme L'outil devrait permettre au personnel de l'entreprise de restaurer automatiquement le chemin d'opération précédent après l'exécution du mini-programme et de capturer les exceptions qui se produisent pendant l'opération pour déterminer si cette version affectera les fonctions de base du mini-programme.

Explication détaillée des tests automatisés de petits programmes

La description ci-dessus semble simple, mais il y a encore quelques difficultés au milieu. La première difficulté est de savoir comment enregistrer les opérations lorsque le personnel de l'entreprise opère. le mini programme Path, la deuxième difficulté est de savoir comment restaurer le chemin d'opération enregistré.

SDK d'automatisation

Comment restaurer le chemin d'opération pour ce problème, le SDK officiel est préféré : miniprogram-automator.

Le SDK Mini Program Automation fournit aux développeurs un ensemble de solutions pour contrôler les mini-programmes via des scripts externes, atteignant ainsi l'objectif de test automatisé des mini-programmes. Grâce à ce SDK, vous pouvez effectuer les opérations suivantes :

  • Contrôler le mini programme pour accéder à la page spécifiée
  • Obtenir les données de la page du mini programme
  • Obtenir le mini Statut des éléments de la page du programme
  • Déclencher un événement de liaison d'élément de mini-programme
  • Injecter un extrait de code dans AppService
  • Appeler n'importe quelle interface sur l'objet wx
  • ...

Les descriptions ci-dessus proviennent toutes de documents officiels. Il est recommandé de lire les documents officiels avant de lire le contenu suivant. Bien sûr, si vous avez déjà utilisé Puppeteer, vous pouvez également démarrer rapidement. fondamentalement la même chose. Ce qui suit est une brève introduction à la façon d’utiliser le SDK.

// 引入sdkconst automator = require('miniprogram-automator')// 启动微信开发者工具automator.launch({  // 微信开发者工具安装路径下的 cli 工具
  // Windows下为安装路径下的 cli.bat
  // MacOS下为安装路径下的 cli
  cliPath: 'path/to/cli',  // 项目地址,即要运行的小程序的路径
  projectPath: 'path/to/project',
}).then(async miniProgram => { // miniProgram 为 IDE 启动后的实例
    // 启动小程序里的 index 页面
  const page = await miniProgram.reLaunch('/page/index/index')  // 等待 500 ms
  await page.waitFor(500)  // 获取页面元素
  const element = await page.$('.main-btn')  // 点击元素
  await element.tap()    // 关闭 IDE
  await miniProgram.close()
})复制代码
Copier après la connexion

Un rappel : avant d'utiliser le SDK, vous devez ouvrir le port de service des outils de développement, sinon le démarrage échouera.

Explication détaillée des tests automatisés de petits programmes

Capturer le comportement de l'utilisateur

Avec la méthode de restauration du chemin d'opération, l'étape suivante consiste à résoudre le problème de l'enregistrement du chemin d'opération.

Dans le mini programme, il n'est pas possible de capturer tous les événements dans la fenêtre via le bouillonnement d'événements comme dans le web. Heureusement, toutes les pages et composants du mini programme doivent passer les Page et Component. méthodes. à envelopper, afin que nous puissions réécrire ces deux méthodes, intercepter la méthode entrante et déterminer si le premier paramètre est un objet event pour capturer tous les événements.

// 暂存原生方法const originPage = Pageconst originComponent = Component// 改写 PagePage = (params) => {  const names = Object.keys(params)  for (const name of names) {    // 进行方法拦截
    if (typeof obj[name] === 'function') {
      params[name] = hookMethod(name, params[name], false)
    }
  }
  originPage(params)
}// 改写 ComponentComponent = (params) => {  if (params.methods) {      const { methods } = params      const names = Object.keys(methods)      for (const name of names) {        // 进行方法拦截
        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 // 取出第一个参数
    // 判断是否为 event 对象
    if (evt && evt.target && evt.type) {      // 记录用户行为
    }    return method.apply(this, args)
  }
}复制代码
Copier après la connexion

Le code ici ne fait que proxy toutes les méthodes d'événement et ne peut pas être utilisé pour restaurer le comportement de l'utilisateur. Pour restaurer le comportement de l'utilisateur, vous devez également savoir si le type d'événement est requis, tel qu'un clic, un appui long et. saisir.

const evtTypes = [    'tap', // 点击
    'input', // 输入
    'confirm', // 回车
    'longpress' // 长按]const hookMethod = (name, method) => {  return function(...args) {    const [evt] = args // 取出第一个参数
    // 判断是否为 event 对象
    if (
      evt && evt.target && evt.type &&
      evtTypes.includes(evt.type) // 判断事件类型
    ) {      // 记录用户行为
    }    return method.apply(this, args)
  }
}复制代码
Copier après la connexion

Après avoir déterminé le type d'événement, vous devez toujours clarifier sur quel élément vous avez cliqué. Cependant, le piège dans le mini-programme est qu'il n'y a pas de nom de classe de l'élément dans l'attribut cible de l'objet événement, mais l'élément peut être obtenu.

Explication détaillée des tests automatisés de petits programmes

Afin d'obtenir les éléments avec précision, il faut ajouter une étape dans la construction, modifier le fichier wxml et copier le class attributs de tous les éléments Servir dans data-className.

<!-- 构建前 --><view></view><view></view><!-- 构建后 --><view></view><view></view>复制代码
Copier après la connexion

Mais après avoir obtenu la classe, il y aura un autre écueil. L'outil de test automatisé du mini programme ne peut pas obtenir directement les éléments du composant personnalisé sur la page. Le composant personnalisé doit d'abord être obtenu.

<!-- Page --><toast></toast><!-- Component --><view>
  <text>{{text}}</text>
  <view></view></view>复制代码
Copier après la connexion
// 如果直接查找 .toast-close 会得到 nullconst element = await page.$('.toast-close')
element.tap() // Error!// 必须先通过自定义组件的 tagName 找到自定义组件// 再从自定义组件中通过 className 查找对应元素const element = await page.$('toast .toast-close')
element.tap()复制代码
Copier après la connexion

Ainsi, lorsque nous construisons l'opération, nous devons également insérer tagName pour l'élément.

<!-- 构建前 --><view></view><toast></toast><!-- 构建后 --><view></view><toast></toast>复制代码
Copier après la connexion

Nous pouvons désormais continuer à enregistrer joyeusement le comportement des utilisateurs.

// 记录用户行为的数组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 // 取出第一个参数
    // 判断是否为 event 对象
    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事件触发时,输入框的值
      // 记录用户行为
      let query = ''
      if (isComponent) {        // 如果是组件内的方法,需要获取当前组件的 tagName
        query = `${this.dataset.tagName} `
      }      if (id) {        // id 存在,则直接通过 id 查找元素
        query += id
      } else {        // id 不存在,才通过 className 查找元素
        query += className
      }
      addAction(type, query, value)
    }    return method.apply(this, args)
  }
}复制代码
Copier après la connexion

Jusqu'à présent, tous les clics, saisies et opérations liées à la saisie de l'utilisateur ont été enregistrés. Il reste cependant une opération de défilement de l'écran qui n'est pas enregistrée. On peut directement déléguer la méthode onPageScroll de Page.

// 记录用户行为的数组const actions = [];// 添加用户行为const addAction = (type, query, value = '') => {  if (type === 'scroll' || type === 'input') {    // 如果上一次行为也是滚动或输入,则重置 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) {    // 进行方法拦截
    if (typeof obj[name] === 'function') {
      params[name] = hookMethod(name, params[name], false)
    }
  }  const { onPageScroll } = params  // 拦截滚动事件
  params.onPageScroll = function (...args) {    const [evt] = args    const { scrollTop } = evt
    addAction('scroll', '', scrollTop)
    onPageScroll.apply(this, args)
  }
  originPage(params)
}复制代码
Copier après la connexion

Il y a un point d'optimisation ici, c'est-à-dire que lors de l'enregistrement de l'opération de défilement, vous pouvez juger si la dernière opération était également une opération de défilement. S'il s'agit de la même opération, il vous suffit de modifier le défilement. distance, car les deux Le deuxième défilement peut être effectué en une seule étape. De la même manière, il en va de même pour les événements d'entrée, et la valeur d'entrée peut également être atteinte en une seule étape.

Restaurer le comportement de l'utilisateur

Une fois l'opération utilisateur terminée, le texte json du comportement de l'utilisateur peut être affiché dans la console. Après avoir copié le texte json, il peut être exécuté via l'outil d'automatisation. .

// 引入sdkconst 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 }
]// 启动微信开发者工具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) {      // 计算两次操作之间的等待时间
        await page.waitFor(time - prevTime)
    }    // 重置上次操作时间
    prevTime = time    
    // 获取当前页面实例
    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;
    }    // 每次操作结束后,等待 5s,防止页面跳转过程中,后面的操作找不到页面
    await page.waitFor(5000)
  }    // 关闭 IDE
  await miniProgram.close()
})复制代码
Copier après la connexion

Il s'agit simplement d'une simple restauration du comportement de fonctionnement de l'utilisateur. Pendant le fonctionnement réel, les requêtes réseau et les simulations de stockage local seront également impliquées, qui ne seront pas décrites ici. En parallèle, nous pouvons également accéder à l’outil jest pour faciliter la rédaction de cas d’utilisation.

Résumé

Pour des besoins apparemment difficiles, à condition de les explorer attentivement, vous pouvez toujours trouver une solution correspondante. De plus, il existe de nombreux pièges dans les outils d'automatisation des mini-programmes WeChat. Si vous rencontrez des problèmes, vous pouvez d'abord vous rendre dans la communauté des mini-programmes pour les trouver. La plupart des pièges ont été surmontés par les prédécesseurs. des problèmes qui ne peuvent pas être résolus pour le moment et que vous ne pouvez trouver que d’autres moyens de les éviter. Enfin, je souhaite que le monde soit sans bugs.

Recommandations d'apprentissage associées : Tutoriel de développement de compte public WeChat

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:juejin.im
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal