Appium在Android UI測試中的應用實踐
Android 測試工具與 Appium 簡介
Appium 是一個 C/S 架構(gòu)的,支持 Android/iOS Native, Hybrid 和 Mobile Web Apps 的測試框架,與測試程序通過 Selenum Webdriver 協(xié)議通訊。Webdriver 的好處是通過 HTTP RPC 的方式調(diào)用 Server 上的過程,編寫測試腳本不受語言的限制,無論是 Python, Java, NodeJS 均可以方便的編寫測試。本文中將使用 Python 進行編程。
起因是因為市場部的同事拋來如下需求:批量添加一些微信好友。直接抓取請求進行重放的方法是不靠譜的,微信與服務端的通訊均加密,Pass??紤]使用 xposed 等框架 hook 相關(guān)函數(shù)進行操作。但是 xposed 需要越獄,且開發(fā)復雜,Pass。后來想到了使用 UI 測試工具進行模擬操作,開發(fā)較為簡單。
Android UI 測試工具有很多種,如 Monkey, UIAutomator, Selendroid, Robotium 等。其中 UIAutomator, Monkey, Selendroid 均為非侵入式的 UI 測試,也就是不需要修改源代碼,只要安裝了目標程序就可以進行測試。Robotium 需要與源碼一同編譯測試。Appium 實際上就是一個測試工具的統(tǒng)一調(diào)度軟件,將不同的非侵入式測試工具整合在一起,對外提供統(tǒng)一的 API。在 Android 2.3 以前的版本,Appium 會調(diào)用 Selendroid ,之后的版本會直接使用 UIAutomator,iOS 下使用 UIAutomation。Appium 還支持 FirefoxOS 的 UI 測試。
安裝 Appium
官網(wǎng)給出了命令行下的安裝方法。但實際上 Appium 有 GUI 版本,更適合在 Windows/MacOS 下使用。Windows 下需要安裝 .NET Framework。
- > brew install node # get node.js
- > npm install -g appium # get appium
- > npm install wd # get appium client
- > appium & # start appium
- > node your-appium-test.js
Appium 需要依賴 Android SDK 編譯在手機端運行的兩個插件,因此需要首先安裝相應的 Android SDK 版本。這里直接使用了 Android Studio 中自帶的 SDK Manager。在 SDK Manager 中選擇和測試機相對應的 SDK Platform 和較新的 Build-tools,如果需要使用模擬器測試還要裝對應的 ARM/x86 System Image,以及 Intel HAXM Installer,用于加速 x86 虛擬機。Appium 使用 adb 來與目標機器通訊,因此對于真機和模擬器操作幾乎都是相同的,如何建立模擬器在此不再贅述。
安裝完成后需要在 Appium GUI 中配置 Android SDK 目錄,隨后選擇 Android,點擊 Launch 就可以啟動 Appium Server。
Appium Server 默認會監(jiān)聽 http://localhost:4723 ,用于 RPC 通訊。下面我們就可以打開熟悉的編程環(huán)境,編寫 UI 測試用例了。這里使用 Python 進行編寫,需要先安裝 Appium 的 Python Client ,然后再 python 中使用 appium.webclient 就可以連接 Appium server了。
- pip install Appium-Python-Client
使用 Appium 進行 UI 控制
根據(jù)注釋修改相應屬性后即可運行測試。手機需要打開 ADB 調(diào)試,執(zhí)行完以下代碼后,Appium 會在手機上安裝 Appium Settings 和 Unlock 兩個程序,隨后微信會被啟動。
- from appium import webdriver
- desired_caps = {}
- desired_caps['platformName'] = 'Android' #測試平臺
- desired_caps['platformVersion'] = '5.1' #平臺版本
- desired_caps['deviceName'] = 'm3_note' #設備名稱,多設備時需區(qū)分
- desired_caps['appPackage'] = 'com.tencent.mm' #app package名
- desired_caps['appActivity'] = '.ui.LauncherUI' #app默認Activity
- dr = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) #啟動Remote RPC
Selenum Webdriver 使用了一種類似于 JS 中的 DOM 模型的方法來選擇頁面中的元素。dr 為當前正在活動的 activity 對象,可以使用 findElementByXXX 的方法來獲取 Activity 中的元素。所有 Element 后帶 s 的函數(shù),均獲得所有匹配的元素,不帶 s 的函數(shù)獲得***個匹配的元素。
查詢函數(shù)
1. findElement(s)ByName
在 Android 中基本沒用。Android UI 沒有 Name 這個屬性。有說可以使用 text 值獲取。但我并沒有成功
2. findElement(s)ByClassName
通過類名來獲取元素,用法如下:
- item_list = dr.find_elements_by_class_name("android.widget.LinearLayout")
- item_list[2].click()
3. findElementById
通過 resource_id 來獲取元素,每個 Activity 中都是***的,用法如下
- t = dr.find_element_by_id("com.tencent.mm:id/f7")
- t.send_keys(wechatId)
4. findElement(s)ByAccessbiltiyId
在 Android 上 AccessbilityID 實際就是 contentDescription 。這個屬性是為了方便視力受損人士使用手機所設置。開啟 TTS 后系統(tǒng)會朗讀相關(guān)控件的 contentDescription。
5. findElement(s)ByXPath
通過 XML Path 描述來尋找元素。我沒有成功的獲取到,可能是 XPath 寫的有問題。
- s = dr.find_element_by_xpath("//android.widget.TextView[contains(@text,'搜索')]")
- s.click()
6. findElementByAndroidUIAutomator
通過 UIAutomator 的選擇器來獲取元素。因為 Appium 在 Android 上實際是調(diào)用的 UIAutomator,所以可以通過 UIAutomator 的選擇器來選擇元素。
- el = dr.find_element_by_android_ui_automator("new UiSelector().text(\"搜索\")")
- el.click()
操作函數(shù)
操作函數(shù)用于操作選定的元素,有很多,以下僅列舉幾個,更多的請查閱手冊。
- click
- send_keys
- clear
查詢函數(shù)返回的元素對象可以像 JS 中的 dom 元素一樣,繼續(xù)使用查詢函數(shù)來選定其子元素。用例如下。
- search = dr.find_element_by_id("com.tencent.mm:id/aqw").find_element_by_class_name("android.widget.RelativeLayout")
- search.click()
如何確定查詢規(guī)則
了解了相關(guān)的函數(shù)后,下面就應對 UI 進行定位了。如果是自己團隊開發(fā)的程序,推薦讓開發(fā)同學在所有的空間上都添加 resource_id 進行絕對定位。如果碰到?jīng)]有談價 resource_id 的元素,那就要使用別的辦法進行定位了。
1. UI Automator Viewer
UI Automator Viewer 是 Android 官方的 UI 定位工具,位于 sdk/tools 下。運行后會打開 viewer 界面。點擊獲取按鈕即可獲取當前正在運行的 Activity 的 UI 結(jié)構(gòu)。
2. AppiumDriver getPageSource
AppiumDriver(Client) 可以很方便的獲得當前正在運行的 Activity 的 UI 描述,隨后可根據(jù)返回的 XML 文檔來尋找元素。
- print dr.page_source
確定元素位置后,即可根據(jù)前述的 Find 方法來查找/選擇元素
編寫完整的測試代碼
正確的獲取元素之后便可以獲取元素相關(guān)的信息,隨后使用各語言常用的測試框架編寫測試即可,如 Java 的 JUnit,Nodejs 的 Mocha 等。
這里我使用 Appium 主要是為了模擬用戶點擊添加微信好友,所以完整的程序并沒有使用到測試框架。相關(guān)的 UI 元素獲取/操作方法供大家參考。
- # coding:utf-8
- from appium import webdriver
- from time import sleep
- def addFriend(dr, id, dryRun=False):
- succ = False
- wechatId = str(id)
- dr.find_element_by_accessibility_id(r"更多功能按鈕").click()
- item_list = dr.find_elements_by_class_name("android.widget.LinearLayout")
- try:
- item_list[2].click()
- except:
- print "Error! in item list len"
- return succ
- el = dr.find_element_by_class_name("android.widget.ListView")
- item_list = el.find_elements_by_class_name("android.widget.LinearLayout")
- try:
- item_list[1].click()
- except:
- print "Error! in item list len"
- return succ
- t = dr.find_element_by_id("com.tencent.mm:id/f7")
- t.send_keys(wechatId)
- search = dr.find_element_by_id("com.tencent.mm:id/aqw").find_element_by_class_name("android.widget.RelativeLayout")
- search.click()
- try:
- freq = dr.find_element_by_id('com.tencent.mm:id/aqq')
- assert freq.text == u"操作過于頻繁,請稍后再試。"
- print "Frequency too high! Sleep 300s"
- sleep(60)
- return succ
- except:
- pass
- try:
- dr.find_element_by_id('com.tencent.mm:id/a8x').click()
- addBtn = dr.find_element_by_id('com.tencent.mm:id/eu')
- if not dryRun:
- addBtn.click()
- succ = True
- print "Success Send Requests:" + wechatId
- except:
- print "No Such User Or Already a Friend:" + wechatId
- while True:
- try:
- dr.find_element_by_id('com.tencent.mm:id/fb').click()
- except:
- try:
- dr.find_element_by_id('com.tencent.mm:id/f4').click()
- except:
- break
- return True
- def resetActivity(dr, desired_caps):
- dr.start_activity(desired_caps['appPackage'], desired_caps['appActivity'])
- desired_caps = {}
- desired_caps['platformName'] = 'Android'
- desired_caps['platformVersion'] = '5.1'
- desired_caps['deviceName'] = 'm3_note'
- desired_caps['appPackage'] = 'com.tencent.mm'
- desired_caps['appActivity'] = '.ui.LauncherUI'
- print "Trying connect to phone..."
- dr = {}
- try:
- dr = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
- except Exception, e:
- print "Cannot Connect to phone :", e
- exit()
- print "Successfully connect to phone."
- print "Reading friend list..."
- friendList = []
- fp = open("friends.txt")
- line = fp.readline().strip()
- while line:
- friendList.append(line)
- line = fp.readline().strip()
- print "Finish reading friends. Total: " + str(len(friendList))
- print "Wait for Wechat's splash screen...."
- for i in range(0, 10):
- print 10 - i
- sleep(1)
- succ_list = []
- fail_list = []
- for i in friendList:
- try:
- succ = addFriend(dr, i, dryRun=False)
- if succ:
- succ_list.append(i)
- else:
- fail_list.append(i)
- except:
- fail_list.append(i)
- resetActivity(dr, desired_caps)
- print "Succeed List:"
- print "\n".join(succ_list)
- print "Failed List:"
- print "\n".join(fail_list)
- dr.close()
【編輯推薦】