愛碼哥 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


分享到:


相關文章: