Macaca之测试用例(Node.JS版)
来源:互联网 发布:人物卡通设计软件 编辑:程序博客网 时间:2024/06/08 06:04
测试用例代码篇:
const path = require('path');const wd = require('wd');describe('MXD mobile DEMO1', function() { this.timeout(5 * 60 * 1000); // mocha 设置超时时间 var driver = wd.promiseChainRemote({ // 初始化 webdriver host: 'localhost', port: 3456 }); before(function() { // iOS需要传 udid 和 bundleId return driver.init({ platformName: 'iOS', udid: 'xxxxxxxx', //这里我都填写了正确的信息,考虑到隐私性,这里不写出来,实际没问题 bundleId: 'xxxxxxxxx' }); }); it('#1 test', function() { return driver .elementByNameIfExists('我') .elementByName('我') .click(); }); after(function() { // 所有测试用例的统一后置动作 return driver .sleep(1000) .quit(); });});
官方demo的用例
mobile-app-sample.test.js:
'use strict';require('should');const KEY_MAP = require('webdriver-keycode');var platform = process.env.platform || 'iOS';platform = platform.toLowerCase();const pkg = require('../package');/** * download app form npm * * or use online resource: https://npmcdn.com/ios-app-bootstrap@latest/build/ios-app-bootstrap.zip * * npm i ios-app-bootstrap --save-dev * * var opts = { * app: path.join(__dirname, '..', 'node_modules', 'ios-app-bootstrap', 'build', 'ios-app-bootstrap.zip'); * }; */// see: https://macacajs.github.io/desired-capsvar iOSOpts = { deviceName: 'iPhone 6s',//模拟器的名称,例如‘iPhone 6’或者‘Nexus 5x’ platformName: 'iOS', //当前用例运行的平台 {iOS/Android/Desktop} //autoAcceptAlerts: true, //自动接受所有的系统弹窗信息。默认是 false //autoDismissAlerts:true //自动拒绝所有的系统弹窗信息。默认是 false //reuse: 3, //0: 清楚数据并重装app。1: (默认) 卸载并重装 app。2: 仅重装 app。3: 在测试结束后保持app状态 //udid: '', //测试设备的唯一设备ID //bundleId: 'xudafeng.ios-app-bootstrap', //应用的 Bundle ID,例如 com.apple.Maps app: 'https://npmcdn.com/ios-app-bootstrap@latest/build/ios-app-bootstrap.zip' //.app 或者 .apk 文件的绝对地址或者远程地址,或者是包含上述文件格式的 Zip 文件};var androidOpts = { platformName: 'Android', autoAcceptAlerts: false, isWaitActivity: true, //reuse: 3, //0: 启动并安装app。{1 (默认):卸载并重装app。2: 仅重装 app。3: 在测试结束后保持app状态。} //udid: '', //package: 'com.github.android_app_bootstrap', //Android app 的 package name //activity: 'com.github.android_app_bootstrap.activity.WelcomeActivity', //启动时的 Activity name app: 'https://npmcdn.com/android-app-bootstrap@latest/android_app_bootstrap/build/outputs/apk/android_app_bootstrap-debug.apk'};const isIOS = platform === 'ios';const infoBoardXPath = isIOS ? '//*[@name="info"]' : '//*[@resource-id="com.github.android_app_bootstrap:id/info"]';const webviewButtonXPath = isIOS ? '//*[@name="Webview"]' : '//*[@resource-id="android:id/tabs"]/android.widget.LinearLayout[2]';const wd = require('macaca-wd');// override custom wdrequire('./wd-extend')(wd, isIOS);describe('macaca mobile sample', function() { this.timeout(10 * 60 * 1000); const driver = wd.promiseChainRemote({ host: 'localhost', port: 3456 }); driver.configureHttp({ timeout: 600 * 1000 }); before(function() { return driver .init(isIOS ? iOSOpts : androidOpts) .sleep(10 * 1000); }); after(function() { return driver .sleep(1000) .quit(); }); it('#1 should login success', function() { return driver /* .title() .then(data => { console.log(`current focus ${isIOS ? 'viewController' : 'activity'}: ${data}`); }) */ .getWindowSize() .then(size => { console.log(`current window size ${JSON.stringify(size)}`); }) .appLogin('中文+Test+12345678', '111111'); }); it('#2 should display home', function() { return driver .source() .then(res => { console.log(res); }) .takeScreenshot(); }); it('#3 should scroll tableview', function() { return driver .testGetProperty() .waitForElementByName('HOME') .click() .waitForElementByName('list') .click() .sleep(2000); }); it('#4 should cover gestrure', function() { return driver .waitForElementByName('Alert') .click() .sleep(5000) .acceptAlert() .sleep(1000) .customback() .waitForElementByName('Gesture') .click() .sleep(5000) .then(() => { return driver .touch('tap', { x: 100, y: 100 }) .sleep(1000) .elementByXPath(infoBoardXPath) .text() .then(text => { JSON.stringify(text).should.containEql('singleTap'); }); }) .then(() => { return driver .touch('press', { x: 100, y: 100, duration: 2 }) .sleep(1000); }) .then(() => { return driver .waitForElementByXPath(infoBoardXPath) .touch('pinch', { scale: 2, // only for iOS velocity: 1, // only for iOS direction: 'in',// only for Android percent: 0.2, // only for Android steps: 200 // only for Android }) .sleep(1000); }) /* // TODO Android rotate .then(() => { return driver .touch('rotate', { }) .sleep(1000); })*/ .customback() .then(() => { return driver .touch('drag', { fromX: 100, fromY: 600, toX: 100, toY: 100, duration: 3 }) .sleep(1000); }) .sleep(1000); }); it('#5 should go into webview', function() { return driver .customback() .sleep(3000) .elementByXPath(webviewButtonXPath) .click() .sleep(3000) .takeScreenshot() .changeToWebviewContext() .elementById('pushView') .click() .changeToWebviewContext() .waitForElementById('popView') .click() .sleep(5000) .takeScreenshot(); }); it('#6 should go into test', function() { return driver .changeToNativeContext() .waitForElementByName('Baidu') .click() .sleep(5000) .takeScreenshot(); }); it('#7 should works with web', function() { return driver .changeToWebviewContext() .title() .then(title => { console.log(`title: ${title}`); }) .url() .then(url => { console.log(`url: ${url}`); }) .refresh() .sleep(2000) .elementById('index-kw') .getProperty('name') .then(info => { console.log(`get web attribute name: ${JSON.stringify(info)}`); }) .waitForElementById('index-kw') .sendKeys('中文+Macaca') .elementById('index-bn') .click() .sleep(5000) .source() .then(html => { html.should.containEql('Macaca'); }) .execute(`document.body.innerHTML = "<h1>${pkg.name}</h1>"`) .sleep(3000) .takeScreenshot(); }); it('#8 should logout success', function() { return driver .changeToNativeContext() .waitForElementByName('PERSONAL') .click() .sleep(1000) .takeScreenshot() .waitForElementByName('Logout') .click() .sleep(1000) .takeScreenshot(); });});
测试案例解析:
1、 配置
var iOSOpts = { platformName: 'iOS', platformVersion: '9.3', deviceName: 'iPhone 5s', app: '/Users/chenximing/workspace/ios/macaca-test2/macaca-test-sample/app/ios-app-bootstrap.zip'};/* platformName:平台名称 platformVersion:iOS系统版本,框架好像没用到这个参数,所以这玩意不重要 deviceName:设备名称 app:被测app路径*/
2、测试案例
var wd = require('webdriver-client')(iOSOpts);describe('macaca mobile sample', function() { this.timeout(5 * 60 * 1000); var driver = wd.initPromiseChain(); driver.configureHttp({ timeout: 600000 }); before(function() { return driver .initDriver(); }); after(function() { return driver .sleep(1000) .quit(); }); it('#1 should login success', function() { return driver .login('12345678', '111111') .sleep(1000); });...});
这里可以细分为:
(1). driver初始化
var wd = require('webdriver-client')(iOSOpts);......var driver = wd.initPromiseChain();driver.configureHttp({ timeout: 600000});
webdriver-client是什么?
上篇说到macaca是c-s模式的测试框架,client负责被案例端调用的API,server负责调起instruments以及控制其执行测试。webdriver-client就是上面说到的client端,提供控制操作的API,《Macaca的API文档》。
(2). 测试框架
describe('macaca mobile sample', function() { this.timeout(5 * 60 * 1000); ...... before(function() { return driver .initDriver(); }); after(function() { return driver .sleep(1000) .quit(); }); it('#1 should login success', function() { return driver .login('12345678', '111111') .sleep(1000); }); ...});
在这里,Macaca使用一个第三方的测试框架Mocha,macaca-cli在run的时候加载该框架。
describe、before、after、it等关键字均为Mocha提供,和传统XUnit框架功能类似(Mocha默认是BDD模式,而XUnit是TDD模式),想了解更多,见Mocha主页。
(3). 测试案例
...it('#1 should login success', function() { return driver .login('12345678', '111111') .sleep(1000);});...
it部分就是测试案例。
代码解析:
登录应用
找到这两个字段,输入用户名和密码,并点击登录按钮
driver .waitForElementByXPath('//UIATextField[1]') .sendKeys('loginName') .waitForElementByXPath('//UIASecureTextField[1]') .sendKeys('123456') .sleep(1000) .sendKeys('\n') .waitForElementByName('Login') .click();
测试相关
切换到百度
的Tab,搜索TesterHome
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()
需要注意的是,大部分的 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.
进阶
1、BDD(Behavior-driven development)
为什么我会介绍BDD? 因为Mocha就基于BDD思想的测试框架,并且我估计会有人把 BDD 和 链式调用 的概念搞混。
BDD(Behavior Driven Development:行为驱动开发),是基于TDD发展的一种解决问题的思想,通过用类似自然语言方式描述软件行为,以达到可读性更高(让非技术人员也可以看懂)。
以上测试代码中,属于BDD部分由Mocha提供的,如:describe, it, before, after…这些均为BDD风格的接口。如果是TDD风格(如:XUnit)的接口则是:suite, test, setup, teardown…
2、链式调用
(1)什么是链式调用
driver .native() .elementByName('PERSONAL') .click() .sleep(1000) .takeScreenshot() .elementByName('Logout') .click() .sleep(1000) .takeScreenshot();
以上代码组织方式为:链式调用。
如果你之前把BDD和链式调用搞混,估计看过以下代码:
When(...).Then(...).And(...).Should(...)
这段代码就是BDD接口以链式方式调用,可读性非常高!但关于BDD的部分其实还是:When、Then、And、Should...
(2)为何Macaca测试案例使用链式调用风格?
某些情况下,使用链式调用方式书写代码是很舒服的,如C#的linq:
var rs = user.Where(x => x.Length == 3).Select(x => x).ToList();
但如果把所有测试操作(无论操作间有无关联)都用链式调用方式组合,就比较奇怪了。如:
return driver .webview() .elementById('pushView') .tap() .sleep(5000) .webview() .elementById('popView') .tap() .sleep(5000) .takeScreenshot();
上面2个webview element的操作是没有任何关系的。而使用链式调用的场景一般是前后依赖、连续操作、层级递进,如上面的linq例子:where的结果集,接着要进行数据提取,然后是再把集合封装为list结构。
所以,基于链式调用的原意,上面的案例的写法就有些奇怪了,并且Node.js的新手也不习惯。然而,为啥作者会写出这种的测试代码?原因在于:Node.js这个语言!
Node.js是异步编程语言,例子如下:
var el = driver.webview().elementById('hyddd') el.tap()
上面2句,同步编程语言是怎么理解呢?
- 获取hyddd的element;
- 对element进行tap()操作;
但换作异步编程语言呢?
- 获取hyddd的element;
el.tap()
同时于1执行,也就说,el还没赋值,2就已经开始执行了,完全没等1返回2就执行了;
没法好好玩耍了,如果原生Node.js程序时要处理同步场景,就会出现所谓的callback hell,为了避免callback hell,就出现了Promise模式。嗯,在上面的测试代码中是不是看到这个单词?它作用就是把异步模式变为同步模式,同时避免callback hell。而它的表现就是现在这种链式调用!!!所以测试案例长得比较奇怪是开发语言导致的。
参考文章:
编写移动端 Macaca 测试用例 [单步调试]
Macaca 异步测试用例编写进阶
那些年我们玩过的手势
如何从头编写你的 Macaca 用例
Macaca-iOS入门那些事2
- Macaca之测试用例(Node.JS版)
- Macaca之测试用例(Java版)
- macaca 测试web(3)
- macaca 测试web(3)
- Macaca自动化测试框架
- Macaca之常见参数
- Macaca之uirecorder使用
- macaca之zfb
- Macaca之2.0版本(iOS)完全安装手册
- macaca
- macaca
- Macaca Macaca 实现 IOS Monkey 测试 (支持 macaca2.x 版本)
- Node.js(安装,启动,测试)
- Node.js(安装,启动,测试)
- JavaScript(Node.js)+ Selenium自动化测试
- JavaScript(Node.js)+ Selenium自动化测试
- 安装测试node.js
- Node.js测试
- JavaScript闭包实例详解
- JuniperSRX------------用户管理
- Spring学习总结
- Qt5使用openssl实现RSA数字签名
- 在Android开发中使用icon font的代码和方法
- Macaca之测试用例(Node.JS版)
- 3D中的OBJ文件格式详解
- jQuery Validate详解
- Gson-记录一个空格引发的json血案
- Nuttx 之elf 多线程处理
- Solver及其参数配置
- MyBatis框架简介 、MyBatis基本应用
- MySQL
- DNS劫持是什么意思?DNS劫持是干什么用的?