微信小程序---自动化测试

2,019 阅读4分钟

微信小程序---自动化测试

关于小程序的自动化测试,我是根据微信小程序官方文档来走的。没有做太深入,只点点点。

总体流程是先安装测试环境,设置安全设置,然后项目目录中创建一个测试文件夹并在文件夹中创建测试代码文件,一步一步走

安装环境

安装小程序自动化JDK(miniprogram-automator),同时安装jest(前端测试框架)

npm i miniprogram-automator jest
npm i jest -g

接着打开"安全设置 CLI/HTTP 调用功能"(<—_—#>丫的找了老半天)

进入里面,它现在长这个样子

然后在项目中创建小程序自动化测试文件夹,和测试脚本index.spec.js

写代码

在index.spec.js中写测试代码,我不会只能先直接复制官网了,跟就要跟完。

const automator = require('miniprogram-automator')

describe('index', () => {
  let miniProgram
  let page

  beforeAll(async () => {
    miniProgram = await automator.launch({
      projectPath: '/Users/xxxxx/Desktop/company/xxxxx'
    })
    page = await miniProgram.reLaunch('/pages/index/index')
    await page.waitFor(500)
  }, 3000000)

  afterAll(async () => {
    await miniProgram.close()
  })
})

上面这个就是一切的源头⬆️,我就按照我自己的理解来看这段了。

首先是官网写的引入来:

  1. 启动并连接工具
  2. 重新启动小程序到首页
  3. 断开连接并关闭工具

我理解就是引入了自动化对象automator,然后固定方法describe,回调里面才是具体事件。beforeAll一切开始之前,连接项目,projectPath项目路径,我写的完整的路径,就是在terminal里面cd cd cd到我的项目里,然后pwd出来的路径。然后page对应的是项目被打开时进入的路径。page.waitFor(500)这个就是等待500毫秒,当然waitFor方法里面可以string number function三种值看下面⬇️

// 如果条件是 string 类型,那么该参数会被当成选择器,当该选择器选中元素个数不为零时,结束等待。

// 如果条件是 number 类型,那么该参数会被当成超时时长,当经过指定时间后,结束等待。

// 如果条件是 Function 类型,那么该参数会被当成断言函数,当该函数返回真值时,结束等待。

beforeAll的第二个参数就很好理解,就等待时间3000000毫秒这个时间内作为初始化时间,设置短了有可能项目都还没有编译完它就可能结束了。

afterAll就所有的方法什么的都结束了之后执行,有点类似生命周期。

接着就是模拟鼠标了,作为一个0级测试我只会点点点。

it('list action', async () => {
  const listHead = await page.$('.kind-list-item-hd')
  expect(await listHead.attribute('class')).toBe('kind-list-item-hd')
  await listHead.tap()
  await page.waitFor(200)
  expect(await listHead.attribute('class')).toBe(
    'kind-list-item-hd kind-list-item-hd-show',
  )
  await listHead.tap()
  await page.waitFor(200)
  expect(await listHead.attribute('class')).toBe('kind-list-item-hd')
  await listHead.tap()
  await page.waitFor(200)
  const item = await page.$('.index-bd navigator')
  await item.tap()
  await page.waitFor(500)
  expect((await miniProgram.currentPage()).path).toBe('page/component/pages/view/view')
})

讲一下我理解的,it就是一个固定方法,第一参数是这个it要做的事,第二个参数事具体执行,async就是同步啊。可以有很多个it,会从上到下顺序执行。

首先获取页面节点,page.$() 看到这个请脑补JQuery,await等page的方法执行完,所以就是async...await,那page的方法应该是异步的。这里弄成同步,然后一步一步之行,expect就是断言,toBe就是预测expect里面的内容返回值,直接翻译就是期望它是它。听懂掌声~!!!piapiapia~,那么什么时候加这个await,只要是个方法,就加这个await。别漏了漏了就不是线性执行的感觉。那接下来就非常好懂啦。tap()就是咱在小程序里面写的bindtap,点击啦~。(await miniProgram.currentPage()).path这个就是获取当前页面的路径,去掉path就是当前页面了。如果切换的页面,一定要page = await miniProgram.currentPage(),写到这里忘记说了,这个it方法是放在describe里面的,在⬇️面。嘿嘿

const automator = require('miniprogram-automator')

describe('index', () => {
  let miniProgram
  let page

  beforeAll(async () => {
    miniProgram = await automator.launch({
      projectPath: '/Users/xxxxxxxxxx/Desktop/company/xxxxxxxx'
    })
    page = await miniProgram.reLaunch('/pages/index/index')
    await page.waitFor(500)
  }, 3000000)

  afterAll(async () => {
    await miniProgram.close()
  })
  it('xxxxxx', async() => {
    page = await miniProgram.reLaunch('/xxxx/xxxx/xxxx?xxxx=/xxxxx/xxxxxx/xxxxx')
    expect((await miniProgram.currentPage()).path).toBe('pages/index/index')
  })
})

启动

然后就是启动了,在terminal里面cdcdcd到写这个脚本的文件夹里面,然后jest index.spec.js就好了。现象就是,小程序会被关闭然后重新启动,然后执行脚本里面的it方法,执行完之后关闭小程序,在terminal里显示结果。不管是成功还是失败都会关闭,因为afterAll里面设置的是关闭哦,那个close方法。

最后总结与更多尝试

初步使用来看挺简单的,当然那些类啊什么的都是提前知道的,比如我是在页面里审查元素看的。那些方法是在官方文档一边查一边写,获取的到页面元素其实也是一种数据啦,循环什么的想怎么招呼就怎么招呼。两个美元符号是获取多个元素,返回数组。提示每个it方法不要执行超过5s,不然说是超时。下面都一些方法,用来打破大家不敢写心思。关于page有很多方法,由于名字起的简单明了我就不多介绍啦。大家自己看看就会,很简单的。在用的时候区分一下是方法还是属性就好了。比如input就是一个方法,我写的时候一直用等号,满脑子都是这个input为什么不能被赋值呢???仔细瞅瞅才看到它是个方法。很无奈脑袋不够用了。

it('xxxxxxxx', async() => {
    const meetingRes = await page.$$('.listBar');
    for(let i = -1; ++i < meetingRes.length; ){
      if (await meetingRes[i].text() === 'xxxxxx') {
        await meetingRes[i].tap();
      }
    }
    await page.waitFor();
    page = await miniProgram.currentPage();
  })
it('xxxxxx', async () => {
    const inputList = await page.$$('.weui-input')
    expect(inputList.length).toBe(6);
    let index = -1;
    while(++index < inputList.length) {
      switch(await inputList[index].attribute('data-field')) {
        case 'xxxxx' || 'xxxx':
          console.log(index)
          await inputList[index].input('xxxx' + new Date().getTime())
          break;
        case 'xxxxxx':
          await inputList[index].tap();
          await page.waitFor(500);
          expect((await miniProgram.currentPage()).path).toBe('pages/xxx/xxxx/xxxxx')
          page = await miniProgram.currentPage();
          const meetingRoomDetail = await page.$$('.xxxx')
          await meetingRoomDetail[0].tap();
          await page.waitFor(500);
          expect((await miniProgram.currentPage()).path).toBe('pages/xxxx/xxxxx')
          page = await miniProgram.currentPage();
          break;
        case 'xxxxxx':
          await inputList[index].tap();
          await page.waitFor(500);
          const confirmdate = await page.$('.van-picker__confirm')
          await confirmdate.tap();
          break;
        case 'xxxxxx':
          await inputList[index].tap();
          await page.waitFor(500)
          const periodList = await page.$$('.van-checkbox__icon');
          let i = periodList.length;
          let count = 0;
          while(--i >= 0) {
            if (periodList[i].attribute('class') === 'van-checkbox__icon van-checkbox__icon--round' && count === 0) {
              count++
              await periodList[i].tap();
              break;
            }
          }
          const confirmperiod = await page.$('.van-picker__confirm')
          await confirmperiod.tap();
          break;
        case 'xxxxxx':
          await inputList[index].tap();
          await page.waitFor(500);
          expect((await miniProgram.currentPage()).path).toBe('pages/xxxx/xxxx/xxxxx')
          page = await miniProgram.currentPage();
          const nameinput = await page.$('.van-field__input')
          await nameinput.input('xxxx')
          await page.waitFor(1200);
          const searchList = await page.$$('.xxxx');
          await searchList[0].tap();
          const closeSearch = await page.$('.xxxx')
          await closeSearch.tap();
          const confirm = await page.$('.xxxx')
          await confirm.tap();
          await page.waitFor(500);
          expect((await miniProgram.currentPage()).path).toBe('pages/xxxx/xxxx')
          page = await miniProgram.currentPage();
          break;
        default:
          break;
      }
    }
    const submit = await page.$('.weui-btn')
    expect(submit.tagName).toBe('button')
    expect(await submit.text()).toBe('确定')
    await submit.tap();
    await page.waitFor(500)
    expect((await miniProgram.currentPage()).path).toBe('pages/xxxx/xxxxx')
    page = await miniProgram.currentPage();
  })

尝试了一下,自己写了一个自动化脚本,别说还真的让我给测出来了问题。自动化脚本真的很有意义,就像我现在写的项目里,迭代很快。我也不能保证每次迭代都不会影响到其它功能,或者什么东西被无意间给改了功能造成了阻塞,同时也是一种主动发现问题的方法。用魔法打败魔法,用程序找程序的问题,嗯~大概就是这个意思。还有一个就是之前说的一切的源头的地方,那里是可以对进入小程序的初始页面做更改,所以如果有从公众号跳转到小程序中的某个页面的业务场景,可以在这里模拟一下试试。毕竟公众号不能跳体验版,每次提交审核又要很长时间。

快乐时光就到了,先祝大家新年快乐。干杯 My Life。 🍻