图片亮点自寻。
移动端 UI 自动化测试与普通的单元测试有一个让人很苦恼的区别,就是测试的前置条件要求太高,先要启动一个模拟器或者插上一台真机,然后安装需要测试的 App,随后启动才能开始跑测试用例,整个过程耗时可能要一两分钟,若由于某个用例没有通过,同一个流程后面的所有用例都可能不过,又得修改用例,然后重新跑一次,那么每次修改之后都得有那么一两分钟是用来准备前置的 App 的,实在是太浪费时间了,有没有办法能够解决这个问题,只要启动一次,就能让我们可以测试当前的 App 呢,答案是肯定的。
Macaca 客户端本身就已经集成了 Webdriver 协议服务端的实现,通常在运行用例时,我们采用 Mocha 框架自动读取并运行 test 文件夹下的所有用例,除了这种方式,我们还可以选择仅开启一个 Macaca Server,然后利用 Webdriver 客户端的库,一步步地执行测试用例。
启动 Macaca 服务器
macaca server --verbose
从终端看到,当前的服务器已经成功启动并监听本地的 3456 端口。此时,我们便可以利用 Webdriver Client 端来发送请求给 3456 端口进行一系列操作。
Webdriver 协议采用了 CS 模型,利用 HTTP 协议进行通信,整个过程是异步无状态的,所以我们可以在开启 Macaca Server 后,利用 Node 的 REPL 环境进行测试。
整个过程如下图所示:
cd macaca-ios-test-sample
node
>
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 进行操作。
> 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'
});
> driver
.waitForElementByXPath('//UIATextField[1]')
.sendKeys('loginName')
.waitForElementByXPath('//UIASecureTextField[1]')
.sendKeys('123456')
.sleep(1000)
.sendKeys('\n')
.waitForElementByName('Login')
.click();
> 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()
> 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.