爱码哥 Android自动化测试解决方案

爱码哥 Android自动化测试解决方案

爱码哥是国内领先的移动应用快速开发平台。开发者稍作学习,就可以用xml + JavaScript定义原生的移动应用,同时生成iOS和Android上的app。由于这种开发方式的灵活快速,爱码哥在国内已经有了庞大的用户群体。(了解更多爱码哥的方案可访问官网https://www.imagjs.com/)

应用是有了,很多用户接下来面临新的挑战,就是针对众多不同系统版本、不同尺寸和分辨率的手机,如何验证移动应用在这些机型上都适配,例如在不同Android版本上是否所有功能都能正常使用,不会出现崩溃和未知错误。在不同的界面上显示是否正常。

如果这个兼容性验证工作让人来做,众多的手机一一测过来,耗时长,效率低,是不现实的。这个工作非自动化测试莫属。

为更好的帮助移动应用开发者提高应用的质量和兼容性。爱码哥联合聆播科技,推出了快速易学的爱码哥应用自动化解决方案。作为自动化测试的专家,聆播科技有多款自动化测试产品,用户可以使用JavaScript / Node.JS 开发针对移动应用、Web、API等类型的自动化脚本。爱码哥的用户只要掌握JavaScript,就可以开发从应用到自动化测试脚本全套内容。成为真正的全栈工程师。

这里提供的爱码哥应用测试方案使用CukeTest作为测试工具,它可以安装在多种平台上(包括Windows、Mac),能开发行为驱动的自动化脚本。编写iOS应用自动化测试的脚本需要在Mac平台上,而Android自动化测试的脚本根据用户习惯在任何一个平台上都可以。

下面就针对爱码哥的样例应用,给大家演示如何做Android的自动化测试。

先看下被测应用,这个是应用的首页:

爱码哥 Android自动化测试解决方案

下面是登陆页:

爱码哥 Android自动化测试解决方案

1. 环境准备

1. 下载CukeTest,下载地址:http://cuketest.com/, Windows版或Mac版都可以。

2. 自备一台Android设备,下载爱码哥app并安装。

3. PC端配置好Androd开发环境。

2. 设计场景

1. 打开CukeTest,创建一个空的项目。文件——新建项目——项目类型选择 Basic。

爱码哥 Android自动化测试解决方案

2. 创建成功之后,在feature文件中编辑场景,即测试用例的文字描述。这是行为驱动开发(BDD)的特点。BDD以场景为中心,驱动自动化测试脚本。既增强了可读性,易于脚本的维护和团队的沟通,也方便开发者整理测试脚本的思路。了解更多关于BDD的概念可以参考“为什么要行为驱动开发(BDD)”(http://cuketest.com/zh-cn/bdd/why_bdd.html)

刚创建项目默认视图为:

爱码哥 Android自动化测试解决方案

针对我们的app,我们要编写一个下图的简单操作场景:

爱码哥 Android自动化测试解决方案

我们也可以将CukeTest切换到文本界面,直接将如下代码复制进去。

# language: zh-CN

功能: app 功能验证

验证app 基本功能:

底部导航跳转

场景: 首页分类模块-子模块验证

假如切换到首页

当手机滑动到分类模块

那么此模版应该有12个子模块

场景: 用户登录场景

假如切换到首页

当点击登录/注册按钮

那么应该跳转到登录页面

同时手机号码输入"170****1097"

同时密码栏输入"123456"

当点击登录按钮

那么用户应该成功登录,并且首页显示手机号码"170****1097"

场景: 底部导航栏UI验证

假如切换到首页

那么底部导航栏应该有3个模板

那么底部导航栏的文字分别是 | 爱码哥 | 文档 | 我的 |

同时获取到底部所有导航栏并遍历点击

场景: 用户退出登录

假如切换到我的模块

当点击退出登录按钮

那么弹出确认退出提示,点击确认按钮

那么应该用户退出登录

3. 安装项目依赖

我们使用其它语言来写手机自动化代码的时候,需要用到一些第三方的依赖库。同样,我们在这个项目中也需要使用到第三方的库文件。

此次我们用的依赖库如下:

  • webdriverio: 封装的web和手机端的自动化操作控件库
  • @types/webdriverio:帮助我们在CukeTest中写代码时能够获得智能提示的库

项目根目录下执行 npm install webdriverio @types/webdriverio --save 即可。安装了这个包之后CukeTest会在你开发的时候自动提供这个库相关的智能提示。

4. 生成自动化代码样例

打开features/step_definitions/下的definitions1.js文件,逐一点击我们feature文件中步骤后面的灰色按钮,CukeTest会自动为我们生成自动化样例代码到definitions1.js文件中:

爱码哥 Android自动化测试解决方案

生成完成后,CukeTest会根据代码块自动生成对应的代码示例。

爱码哥 Android自动化测试解决方案

5.利用appium 进行元素定位

1. 打开 appium 桌面版,点击start server启动appium server:

爱码哥 Android自动化测试解决方案

2. File – New Session Window打开新的窗口,配置app启动参数,根据自己的手机配置。

爱码哥 Android自动化测试解决方案

主要参数说明

deviceName:手机序列串号

appPackage:被测应用的包名

appActivity:被测应用的入口

resetKeyboard:是否重置手机键盘

noReset:不要重新安装手机app

将手机与PC端通过数据线连接,点击【Start Session】 启动appium连接手机,此时appium 应该显示手机客户端打开的界面。

爱码哥 Android自动化测试解决方案

根据appium桌面端提供的ui元素追踪器,我们可以很方便的定位到每个元素。通过webdriverio(http://webdriver.io/api.html) 库提供的API,即可完成对app的自动化。

6. 代码部分

  1. 设置驱动, 新建features/support/android_driver.js

var webdriverio = require('webdriverio')

function createDriver() {

let options = {

desiredCapabilities: {

platformName: 'Android',

platformVersion: '5.1',

automationName: 'UiAutomator2',

deviceName: "Y15QKCPH278J4",

appPackage: "com.imagjs.android.mh20170602",

appActivity: "com.imagjs.imag.ImagActivity",

noReset: true,

unicodeKeyboard: true

},

host:"127.0.0.1",

port:4723

};

let client = webdriverio.remote(options);

return client;}exports.driver = createDriver();

2. 设置运行时,新建features/suport/hooks.js

const { After, BeforeAll, Before, AfterAll, setDefaultTimeout } = require('cucumber');

const { driver } = require('./android_driver');

let cuketest = require('cuketest');

// 设置默认的超时时间

setDefaultTimeout(60 * 1000);

BeforeAll(async function () {

//所有场景运行之前

await driver.init();

await cuketest.delay(5000);

return driver.timeoutsImplicitWait(5*1000)})

Before(function () {

//定义场景运行之前

})

After(async function () {

//定义场景运行之后

let screenshot = await driver.saveScreenshot();

this.attach(screenshot, 'image/png');

});

AfterAll(function () {

//perform some shared teardown

return driver.end();

})

3. 实现场景代码, 编辑 features/step_definitions/definitions1.js

var { Given, When, Then } = require('cucumber')

let { driver } = require('../support/android_driver')

let cuketest = require('cuketest')

let assert = require('assert');

async function getBottomList() {

let bottomList = driver.element('android=new UiSelector().resourceId("com.imagjs.android.mh20170602:id/main_bottom_tablayout").index(1)')

let list = await bottomList.elements('android=new UiSelector().className("android.widget.LinearLayout")')

return list;

}

Given(/^底部导航栏应该有(\d+)个模板$/, async function (num) {

let list = await getBottomList();

return assert.equal(list.value.length, num, "数值不相等")

});

Given(/^底部导航栏的文字分别是$/, async function (table) {

let text = await driver.getText('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.TextView')

console.log("text", text)

let asserVal = table.raw()[0]

for (let i = 0; i < text.length; i++) {

assert.equal(text[i], asserVal[i]);

}

});

Then(/^切换到首页$/, async function () {

await driver.click('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.LinearLayout[1]')});Given(/^获取到底部所有导航栏并遍历点击$/, async function () {

let list = await getBottomList();

for (let i = 1; i < list.value.length + 1; i++) {

await driver.click('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.LinearLayout[' + i + ']');

await cuketest.delay(1000)

}

});

When(/^手机滑动到分类模块$/, async function () {

await driver.swipe('android=new UiSelector().resourceId("com.imagjs.android.mh20170602:id/contents_view_pager")', 50, -500, 1000);

});

Then(/^此模版应该有(\d+)个子模块$/, async function (num) {

let eles = await driver.elements('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.support.v4.view.ViewPager/android.widget.LinearLayout/android.support.v4.view.ViewPager/android.view.View/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.LinearLayout[3]/android.support.v7.widget.RecyclerView/android.widget.LinearLayout')

return assert(eles.value.length, num, "子模块数量不一致");

});

When(/^点击登录\/注册按钮$/, async function () {

let selector = '/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.support.v4.view.ViewPager/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.RelativeLayout/android.widget.LinearLayout[1]/android.widget.TextView'

await driver.click(selector)

});

Then(/^应该跳转到登录页面$/, async function () {

await cuketest.delay(1000);

let selector = '/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.LinearLayout/android.support.v4.view.ViewPager/android.view.View/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.RelativeLayout[2]/android.widget.LinearLayout/android.widget.TextView'

let text = await driver.getText(selector)

return assert.equal(text,"www.imagjs.com")

});

Then(/^手机号码输入"([^"]*)"$/, async function (phone) {

let selector = '/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.LinearLayout/android.support.v4.view.ViewPager/android.view.View/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout[1]/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout[2]/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.EditText'

await driver.clearElement(selector)

await driver.element(selector).setValue(phone);

});

Then(/^密码栏输入"([^"]*)"$/, async function (passwd) {

let selector = '/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.LinearLayout/android.support.v4.view.ViewPager/android.view.View/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout[3]/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout[2]/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.EditText'

await driver.clearElement(selector);

await driver.element(selector).setValue(passwd)

});

When(/^点击登录按钮$/, async function () {

let selector = '/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.LinearLayout/android.support.v4.view.ViewPager/android.view.View/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout[5]/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.RelativeLayout[1]/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.Button'

await driver.click(selector)

});

Then(/^用户应该成功登录,并且首页显示手机号码"([^"]*)"$/, async function (phone) {

await cuketest.delay(2000);

let phoneSelector='/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.support.v4.view.ViewPager/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.RelativeLayout/android.widget.LinearLayout[1]/android.widget.TextView'

let text = await driver.getText(phoneSelector);

return assert.equal(text,phone)

});

Given(/^切换到我的模块$/, async function () {

let selector='/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.LinearLayout[3]'

await driver.click(selector);

});

When(/^点击退出登录按钮$/, async function () {

let selector = '/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.support.v4.view.ViewPager/android.widget.LinearLayout/android.support.v4.view.ViewPager/android.view.View/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.LinearLayout[4]/android.widget.LinearLayout[3]'

await driver.click(selector);

});

When(/^弹出确认退出提示,点击确认按钮$/, async function () {

await cuketest.delay(2000);

await driver.alertAccept();

});

Then(/^应该用户退出登录$/, async function () {

let selector='/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.RelativeLayout/android.support.v4.view.ViewPager/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.RelativeLayout/android.widget.LinearLayout[1]/android.widget.TextView'

let text = await driver.getText(selector)

return assert.equal(text,"登录/注册");

});

JavaScript语法,爱码哥的用户有没有感到很亲切?

7. 运行

先将appium启动, 再点击【运行项目】,可以看到手机会自动按照我们设计的场景运行起来。

运行完成后自动生成测试报告。

爱码哥 Android自动化测试解决方案

8.总结

这里使用了Node.js + Cucumber 开发了Appium的客户端脚本,使用CukeTest开发环境,优势:

  1. 完全的开源的代码,可以在多平台运行,包括Windows, Linux, Mac等。
  2. CukeTest提供的开发环境快速生成自然语言的测试用例和框架代码,及可视化报表、视频等。
  3. 最新的Node.js和JavaScript的异步语法async/await在脚本开发中得到了广泛的应用,再也不用为异步编程的难度而烦恼了。

原文链接:https://mp.weixin.qq.com/s/7QIwd7CfG_ZQSUByYT6Lag


分享到:


相關文章: