前言

macaca

图片亮点自寻。

移动端 UI 自动化测试与普通的单元测试有一个让人很苦恼的区别,就是测试的前置条件要求太高,先要启动一个模拟器或者插上一台真机,然后安装需要测试的 App,随后启动才能开始跑测试用例,整个过程耗时可能要一两分钟,若由于某个用例没有通过,同一个流程后面的所有用例都可能不过,又得修改用例,然后重新跑一次,那么每次修改之后都得有那么一两分钟是用来准备前置的 App 的,实在是太浪费时间了,有没有办法能够解决这个问题,只要启动一次,就能让我们可以测试当前的 App 呢,答案是肯定的。

介绍

启动一个 Macaca 服务器

Macaca 客户端本身就已经集成了 Webdriver 协议服务端的实现,通常在运行用例时,我们采用 Mocha 框架自动读取并运行 test 文件夹下的所有用例,除了这种方式,我们还可以选择仅开启一个 Macaca Server,然后利用 Webdriver 客户端的库,一步步地执行测试用例。

启动 Macaca 服务器

macaca server --verbose

Macaca 服务器

从终端看到,当前的服务器已经成功启动并监听本地的 3456 端口。此时,我们便可以利用 Webdriver Client 端来发送请求给 3456 端口进行一系列操作。

在 REPL 中进行测试

Webdriver 协议采用了 CS 模型,利用 HTTP 协议进行通信,整个过程是异步无状态的,所以我们可以在开启 Macaca Server 后,利用 Node 的 REPL 环境进行测试。

整个过程如下图所示:

cd macaca-ios-test-sample
node
>

利用 devtool 代替 REPL 进行测试。

devtool 是一个利用了 Chrome DevTools 的程序调试工具,相对于 REPL,调试和开发更加方便。

安装 devtool,由于网络原因,devtool 依赖的 electron-prebuilt 官方下载很慢。可以通过设置环境变量使用 taobao npm 镜像:
export ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/

cnpm i -g devtool

进入项目目录下,执行 devtool

cd macaca-ios-test-sample
devtool

此时会弹出一个 Chrome 的 DevTools,随后就可以进入 Console 进行操作。

devtool 截图

> const wd = require('wd');
> const driver = wd.promiseChainRemote({
    host: 'localhost',
    port: 3456
  });

> driver.init({
    platformName: 'ios',
    platformVersion: '9.3',
    deviceName: 'iPhone 6s',
    app: '/Users/ziczhu/Code/macaca/macaca-ios-test-sample/app/ios-app-bootstrap.zip'
 });

启动模拟器,安装App

模拟器截图0

> driver
    .waitForElementByXPath('//UIATextField[1]')
    .sendKeys('loginName')
    .waitForElementByXPath('//UIASecureTextField[1]')
    .sendKeys('123456')
    .sleep(1000)
    .sendKeys('\n')
    .waitForElementByName('Login')
    .click();

登陆应用

模拟器截图1

> driver
    .contexts()
    .then(arr => {
      return driver
        .context(arr[0]);
      })
    .elementByName('Baidu')
    .click()
    .contexts()
    .then(arr => {
      return driver
        .context(arr[arr.length - 1]);
      })
    .elementById('index-kw')
    .sendKeys('TesterHome')
    .elementById('index-bn')
    .tap()

百度搜索

模拟器截图2

> driver
    .contexts()
    .then(arr => {
      return driver
        .context(arr[0]);
      })
    .elementByName('PERSONAL')
    .click()

结束登出

上述代码和 App 均来自 macaca-ios-test-sample

在 REPL 中进行测试相对于直接执行文件有了更多的灵活性,我们可以反复通过不同的方式去进行测试,例如利用 XPath 或者 CSS 获取元素等,不用担心因为元素查找错误而直接结束用例。我们推荐可以先在 REPL 中进行编写,等得到正确的结果之后,再将其编写成标准的用例格式,进行回归测试,而不是一开始就完全写成测试用例去执行,这样会浪费大量时间在启动模拟器和安装 APP 之上。

另外,需要注意的是,大部分的 Webdriver Client 实现都是基于 Promise 的,所有操作的结果都是异步的,并不是常规的将结果作为返回值的函数,如果想要得到某个步骤的返回值输出,需要在结尾跟上 then 去解析结果。

 > driver
     .currentContext()
     .then(console.log.bind(console))
   // NATIVE_APP

> driver
    .elementByName('Baidu')
    .then(console.log.bind(console))
  // PromiseElement {value: 7, browser: PromiseWebdriver}

当然,为了安全起见,最好在 then 的第二个参数也加上一个函数来捕捉可能出现的错误信息,比如当我查询一个不存在的元素时:

> driver
    .elementByName('Google')
    .then(console.log.bind(console), console.log.bind(console))
  // Error: [elementByName("Google")] Error response status: 7, , NoSuchElement - An element could not be located on the page using the given search parameters.

参考资料


↙↙↙阅读原文可查看相关链接,并与作者交流