Appium自動化測試-入門
一、Appium簡介
Appium是一個移動端的自動化框架,是跨平臺的。可用於IOS和Android以及firefox的作業系統。
• 原生應用是指用android或ios的sdk編寫的應用;
• 移動網頁web應用是指網頁應用,類似於ios中safari應用或者Chrome應用或者類瀏覽器的應用;
• 混合應用是指一種包裹webview的應用。
1.1 Appium架構原理
Appium是在手機作業系統自帶的測試框架基礎上實現的,Android4.2版本以上使用的是UIAutomator,Android4.2及以下使用的是基於Android Instrumentation框架實現的自動化測試工具;iOS是基於iOS自帶的UI自動化工具UIAutomation實現的。
Appium由客戶端和伺服器組成,客戶端與伺服器通過JSON Wire Protocol進行通訊。下圖簡單的介紹了各部分。
Appium Server:
Appium server使用node.js寫的http伺服器,遵守REST風格。主要作用是接受從Appium客戶端發起的連線,監聽客戶端傳送來的命令,將命令傳送給Bootstrap.jar(或Bootstrap.js)執行,並將執行結果通過HTTP應答反饋給Appium客戶端。
Bootstrap.jar:
在Android手機上執行的一個應用程式,它在手機上扮演TCP伺服器的角色。當Appium需要執行命令時,Appium伺服器會與Bootstrap.jar建立TCP通訊,Bootstrap.jar負責執行測試。
Appium Clients:
是一個擴充套件WebDriver 協議的庫,負責與Appium服務端建立連線,並將指令碼的指令發動到服務端。支援多種語言。
Session:
Appium的客戶端於服務端之間進行通訊都必須在一個Session的上下文中進行。客戶端在發起通訊的時候,會首先發動一個叫“Desired Capabilities”的JSON物件給伺服器。伺服器收到該資料後,會建立一個Session並將Session ID返回給客戶端。客戶端可以用此ID傳送命令。
Desired Capabilities:
是一組設定的鍵值對的集合,主要用於通知Appium伺服器建立需要的Session,其中一些設定可以在Appium執行過程中改變Appium伺服器的執行行為。
1.2 Appium優缺點
優點:
- 支援多種應用程式的測試
- 支援使用多種語言來編寫測試指令碼
- 被測試的應用程式不需要特殊的編譯
- Appium支援應用之間跳轉的測試
缺點:
- 由於服務端執行在電腦上,該工具必須連線電腦才可以執行
- 只能用於UI的自動化測試,在很多情況下的測試驗證只能通過驗證介面來進行
1.3 WebDriver
Appium採用底層驅動商提供統一的WebDriver API,它和Selenium有著千絲萬縷的聯絡,很多方法的使用都很相似,可以參考筆者之前寫過的Selenium文章。
Selenium自動化測試-入門
Selenium自動化測試-unittest單元測試框架使用
二、Appium環境搭建
2.1 安裝Appium執行環境
- Android執行環境
安裝Android SDK後,並將其加入到系統環境變數中。 - 安裝Python
- 安裝Node.js
是為了用命令列的方式啟動Appium。 - 安裝Appium伺服器
可以從此網站下載安裝http://appium.io/
2.2 Appium伺服器啟動
開啟Appium軟體後,點選右上角的三角形,可以開啟啟動伺服器,如下所示:
如果輸出類似如下資訊,沒有錯誤提示,就表示啟動成功了。
> Launching Appium server with command: C:\tools\Appium\node.exe lib\server\main.js --address 127.0.0.1 --port 4723 --platform-name Android --platform-version 23 --automation-name Appium --device-name "8c28b78c" --log-no-color
> info: Welcome to Appium v1.4.16 (REV ae6877eff263066b26328d457bd285c0cc62430d)
> info: Appium REST http interface listener started on 127.0.0.1:4723
> info: [debug] Non-default server args: {"address":"127.0.0.1","logNoColors":true,"deviceName":"8c28b78c","platformName":"Android","platformVersion":"23","automationName":"Appium"}
> info: Console LogLevel: debug
啟動之後,可以在瀏覽器裡面訪問http://localhost:4723/看看是否有反應,如果正常啟動的話,肯定是有反應的。我們需要在設定中改變一些設定,也可以將介面中的log資訊匯出到檔案中,便於我們處理。
三、編寫指令碼前的準備
3.1 檢視頁面元素
Native APP:
我們可以使用Android SDK安裝目錄下的uiautomatorviewer來檢視APP的頁面元素,~\sdk\tools\uiautomatorviewer.bat
。
也可以使用Appium inspector來檢視,但是沒有uiautomatorviewer那麼好用。
含有webview的APP:
可以通過Chrome的DevTools來獲取,在Chrome中輸入chrome://inspect/#devices
後,如果有連線上的裝置,可以點選inspect進入檢視頁面。
不過,有時通過這種方法是無法獲取到頁面的,原因可能是被測程式的WebView沒有開debug模式等。這時我們可以獲取當前頁面的URL然後通過Chrome或Firefox等來訪問並且檢視元素。
3.2 相關文件
這個網站上說明了Appium的方方面面,如設計理念、各個平臺的安裝、指令碼編寫等等。http://appium.io/slate/cn/master/?python#about-appium
通過appium在GitHub上的介紹我們可以獲取編寫指令碼的一些方法,這裡給出的是Python語言的連結。https://github.com/appium/python-client
3.3 簡單示例
用Python寫Appium的指令碼時,只需以下幾步即可以構造一個基本的用例,如下程式碼片斷所示:
#構造Desired Capabilities
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '6.0.1'
desired_caps['deviceName'] = '8c28b78c'
desired_caps['appPackage'] = 'com.ss.android.article.news'
desired_caps['appActivity'] = '.activity.SplashActivity'
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
#1.獲取元素
videoBtn = driver.find_element_by_name("視訊")
#2.操作元素
videoBtn.click()
#3.結果驗證
我們首先需要構造一個Desired Capabilities,設定一些引數,用它來連線到APP中,然後就是進行UI自動化操作的標準3步了。
- 獲取頁面控制元件
- 操作控制元件
- 控制元件資訊驗證
對這三步很熟悉了之後,我們再在這個基礎上做一些封裝,使得指令碼更加健壯,可維護性更高。接下來按照以上幾步來一步步做吧。
四、Desired Capabilities說明
Desired Capabilities就是一組設定,這些設定可以讓測試指令碼控制Appium的執行行為。下面對這些設定做一個簡單的說明。從其官方網站我們可以得到全面的資訊,網址為:http://appium.io/slate/en/master/?java#appium-server-capabilities
4.1 與Appium伺服器相關的
Capability | 是否為必填項 | 描述 | 值 |
---|---|---|---|
automationName | 否 | Appium使用的測試引擎 | Appium(預設) |
platformName | 是 | 被測裝置的系統平臺 | iOS,Android,Firefox OS,null(預設) |
platformVersion | 否 | 手機系統版本 | 如6.6.1,null(預設) |
deviceName | 否 | 測試裝置型別(測試Android時被忽略) | null(預設) |
app | 否 | 指向APP安裝檔案,Android中如果設定了appActivity和appPackage,則此會被忽略 | null(預設) |
browserName | 否 | 手機網頁測試時瀏覽器的名稱 | 設定為Safari在測iOS和Chrome時,設定為Browser在測Android時 |
newCommandTimeout | 否 | Appium伺服器等待Appium客戶端傳送新訊息的時間,單位為s | 60s(預設) |
language | 否 | (僅模擬器使用)設定模擬器的語言 | null(預設) |
locale | 否 | (僅模擬器使用)設定模擬器的使用國家 | null(預設) |
udid | 否 | (僅真機使用)測試裝置的ID | 在多臺裝置與同一臺電腦連線時必須指定 |
orientation | 否 | (僅模擬器使用)螢幕方向 | LANDSCAPE,PORTRAIT,null(預設) |
autoWebview | 否 | 直接切換到WebView上下文 | false(預設),true |
noReset | 否 | 在一個Session開始前不重置被測程式的狀態 | false(預設),true |
fullReset | 否 | 完全重置(Android通過解除安裝程式的方式),Session完成後會解除安裝程式 | false(預設),true |
~
4.2 僅對Android測試有效的設定
Capability | 是否為必填項 | 描述 | 值 |
---|---|---|---|
appActivity | 是 | 被測APP啟動的Activity名稱 | 如.MainActivity |
appPackage | 是 | 被測APP的包名 | 例如:com.example.android.myApp |
deviceReadyTimeout | 否 | 等待裝置ready的超時時間 | 5s(預設) |
ignoreUnimportantViews | 否 | 會忽略一些控制元件,加快執行 | false(預設),true |
disableAndroidWatchers | 否 | 只針對基於UIAutomator的測試有效,不會監控ANR和Crash,這將較少CPU消耗 | false(預設),true |
unicodeKeyboard | 否 | 是否支援Unicode的鍵盤,如果輸入中文,設定為是 | false(預設),true |
resetKeyboard | 否 | 測試結束後是否恢復鍵盤,為正常的手機鍵盤 | false(預設),true |
androidScreenshotPath | 否 | 截圖存放的目錄 | /data/local/tmp(預設) |
… | … | … | … |
~
關於Android測試的Capability非常的多,以上只是其中常用的一部分。還有iOS相關的沒有在這裡敘述了,有興趣的可以訪問前面給出的官網地址去檢視。
五、獲取控制元件
5.1 Native APP
API | 方法描述 |
---|---|
find_element_by_id(self,id) | 通過控制元件的resource id來查詢控制元件 |
find_element_by_name(self,name) | Native APP中,name就是控制元件的Text |
find_element_by_class_name(self,name) | 控制元件的class name,網頁測試也可以用此 |
find_element_by_accessibility_id(self,id) | 控制元件的accessibility_id就是Content Description |
find_element_by_android_uiautomator(self,uia_string) | 根據UIAutomator的語法查詢控制元件,是WebDriver在相容Appium時才新加的語法 |
~
頁面中同一個ID的控制元件可能不止一個,最常見的就是列表項。find_element_by_id
是查詢頁面中第一個ID為指定引數的控制元件,find_elements_by_id
是查詢頁面中所有ID為指定引數的控制元件,返回一個控制元件列表。其他的查詢方法類似。
5.2 Web&Hybrid APP
API | 方法描述 |
---|---|
find_element_by_xpath(self,xpath) | 通過控制元件的xpath來查詢控制元件 |
find_element_by_css_selector(self,css_selector) | 通過控制元件的css_selector來查詢控制元件 |
find_element_by_link_text(self,link_text) | 通過連結的text來查詢控制元件 |
find_element_by_partiallink_text(self,link_text) | 通過連結的部分文字來查詢控制元件 |
find_element_by_tag_name(self,tag_name) | 通過網頁元素的Tag查詢控制元件 |
~
這一部分的控制元件查詢和Selenium中的幾乎一樣,可以檢視筆者之前的相關文章。
5.3 獲取控制元件舉例
下面程式碼片段為獲取圖中底部tab的視訊按鈕的兩種方法。第一種用到了name屬性,先找到其父控制元件,進一步縮小範圍,因為頁面中可能在其他地方也有相同的name。第二種是用find_elements系列的方法,再拿到列表中的第2項,因為下方的幾個控制元件id都是相同的。
self.driver.find_element_by_id("android:id/tabs").find_element_by_name("視訊").click() self.driver.find_elements_by_id("com.ss.android.article.news:id/b5e")[1].click()
對於一些找不到方法去定位的元素怎麼辦呢?首先想到的就是座標定位,為了相容更多的機型,可以用相對座標或者距離元素的位置來定位。更高階的方法就是可以採用影象識別來確定要定位的元素,從而進行點選,可以參考這篇文章。http://tmq.qq.com/2017/02/test_guide/
六、操作控制元件
6.1 獲取控制元件資訊(部分)
API | 方法描述 |
---|---|
text(self) | 獲取控制元件顯示的文字資訊 |
is_enabled(self) | 判斷是否可用了,可用返回true |
is_selected(self) | 是否被選中了,是的話返回true |
id_displayed(self) | 判斷控制元件是否顯示,是的話返回true |
get_attribute(self,name) | 獲取控制元件某項資訊,如element.get_attribute(“displayed”)等同於id_displayed方法 |
parent(self) | 返回控制元件的父控制元件,返回值為一個控制元件物件 |
6.2 手勢操作(部分)
主要有點選、滑動、拖拽、放縮等常用的操作。
API | 方法描述 |
---|---|
click(self) | 點選控制元件 |
clear(self) | 清楚文字框控制元件的文字 |
send_keys(self,*value) | 傳送文字到控制元件中 |
tap(self,positions,duration=None) | positions是一個列表,每個列表是一個二元組最多可以同時點選5個點;duration為時間長短,給引數的話則是長按操作 |
swipe(self,start_x,start_y,end_x,end_y,duration=None) | 從一點滑動到另一點,時長為毫秒 |
flick(self,start_x,start_y,end_x,end_y) | 兩點快速的滑動 |
scroll(self,origin_ele,destination_ele) | 從origin_ele控制元件滾動到destination_ele控制元件 |
drag_and_drop(self,origin_ele,destination_ele) | 把origin_ele控制元件拖拽到destination_ele控制元件的位置 |
pinch(self,element=None,percent=200,steps=50) | 在指定控制元件上執行縮小操作,預設縮放比例為2,分50步完成 |
zoom(self,element=None,percent=200,steps=50) | 在指定控制元件上執行放大操作,預設縮放比例為2,分50步完成 |
6.3 系統操作API(部分)
系統操作用於模擬硬體操作、設定網路環境、獲取系統資訊等,下表簡單的介紹一下常用的方法。
API | 方法描述 |
---|---|
launch_app(self) | 啟動Capability中指定的APP |
is_app_installed(self,package_name) | 判斷應用程式是否安裝 |
install_app(self,app_path) | 安裝APP,app_path指的是電腦上的apk路徑 |
close_app(self) | 如果Capability指定的APP在執行,則關閉它 |
background_app(self,seconds) | 將APP放到後臺執行一段時間 |
reset(self) | 重置當前被測APP到初始狀態 |
current_activity(self) | 獲取當前正在顯示的Activity |
start_activity(self,app_package,app_activity,**opts) | 啟動某個Activity |
pull_file(self,path) | 拉取手機上的一個檔案,並以base64格式編碼返回資料,path為手機檔案路徑 |
pull_folder(self,path) | 拉取手機上的一個資料夾,打包後以base64格式編碼返回資料,path為手機上的資料夾路徑 |
push_file(self,path,base64data) | 將一個base64編碼格式的檔案從電腦推送到手機上的路徑path上 |
press_keycode(self,keycode,metastate=None) | 模擬傳送一個硬體碼到手機,如返回等 |
open_notification(self) | 開啟通知欄 |
network_connection(self) | 返回當前網路連線的型別 |
set_network_connection(self,connectionType) | 設定網路,值為:0 未設定,1 飛航模式,2 WiFi only, 4 Data only, 6 WiFi& Data |
get_screenshot_as_file(self,filename) | 截圖並儲存在電腦上,filename為路徑及截圖名稱 |
save_screenshot(filename) | 截圖並儲存在電腦上,filename為路徑及截圖名稱 |
七、控制元件資訊驗證
這裡我們要說的是查詢並操作控制元件後,怎麼確定我們的操作起了作用。在實際的測試中也把它叫做檢查點,檢查點的劃分和驗證是UI自動化中的一個重點也是難點。常用的有以下方法:
- 判斷某個控制元件是否顯示(操作之後新出現的控制元件)
- 判斷某個控制元件是否被選中
- 判斷某個開關控制元件是否處於check狀態
- 判斷某個控制元件是否enabled
- 截圖之後和正確的進行比對
…
可以將以上判斷的方式進行封裝,便於我們在if語句和assert中使用。關於截圖對比的方式,首先要有正確的操作截圖,然後再進行對比得出結論看看是否一致,會涉及一些演算法相關的知識。
八、常見問題
8.1 A new session could not be created
有一次在執行的過程中,發現輸出了以下錯誤;
selenium.common.exceptions.WebDriverException: Message: A new session could not be created. (Original error: An unknown server-side error occurred while processing the command. (Original error: unknown error: com.android.chrome is not installed on device 8c28b78c
(Driver info: chromedriver=2.18.343845 (73dd713ba7fbfb73cbb514e62641d8c96a94682a),platform=Windows NT 10.0 x86_64)))
可以發現主要的錯誤應該是com.android.chrome is not installed on device
,這個看起來應該是chrome瀏覽器的手機端,我們可以嘗試安裝它。但是,我記得手機上一直都沒有安裝過這個,最後又檢查了一下,發現原來是打開了Appium設定中的Browser,關閉此即可。
然而,除了這個原因有可能是別的原因,我們要具體分析錯誤輸出,還可以做一些事情來來降低這種情況的發生:
- 在初始化的setUp()方法中呼叫ADB命令強制關閉被測應用一次;
- 新增–session-override選項,命令列中或者Appium介面中;
- 在tearDown()方法中,關閉Appium的session,清理環境。
8.2 Permission to start activity denied.
在使用start_activity()方法來啟動另一個APP時,有時會遇到如下錯誤:
selenium.common.exceptions.WebDriverException: Message: Unable to launch the app: Error: Permission to start activity denied.
這時可以看到我們沒有許可權開啟這一個Activity,通常是因為此Activity在清單檔案裡面沒新增Android:exported=”true”,exported屬性就是設定是否允許activity被其它程式呼叫的。所以我們需要從啟動頁Activity開啟如下所示。這在一些情況下可能會有點麻煩。
app_package='com.gotokeep.keep'
app_activity='.activity.SplashActivity'
self.driver.start_activity(app_package,app_activity)
還有一種錯誤是找不到要開啟的Activity:
elenium.common.exceptions.WebDriverException: Message: Unable to launch the app: Error: Activity used to start app doesn’t exist or cannot be launched! Make sure it exists and is a launchable activity
這時我們要檢查Activity是否存在,並且路徑是否填寫正確。