前言:
前段時間學習Android自動化測試,網上關于Appium的介紹和學習資料比較散亂,大多不齊全,官網API看的也是一臉懵,所以決定自己整理一份文檔,供自己學習,也希望能幫助到正在學習Appium和自動化測試的朋友們。
在本地寫了一個md文檔之后,放到了GitHub上維護。鑒于簡書的顏值,我決定轉戰簡書寫博客,但是簡書的Markdown貌似不只是頁內錨點跳轉,所以本文的所有錨點都無效了。文章有些長,需要pdf的朋友們可以去我的GitHub上拿。
為什么選擇Appium
學習Android自動化測試的時候,看了google官方推薦的幾個工具和測試框架,比如Espresso和UI Automator,其中在使用UI Automator來做UI自動化測試的時候,發生一點問題:向控件輸入字符的時候無效!不管是中文還是英文都無法輸入,而且最終竟然還顯示測試通過。經過多方搜索都找不到解決辦法,最終還是選擇Appium,并且Appium是跨平臺的,可以測試ios和Android還有webApp;其中Android平臺上是基于UI Automator的,所以可以無縫切換使用UI Automator的API。
作者:GeeJoe
郵箱:geejoe_developer@outlook.com
GitHub:https://github.com/GeeJoe/AppiumDoc
更新時間:2017-09-14 16:44
Appium使用指南
- 環境配置
- 啟動Appium Server
- 學習DesiredCapabilities
- Session簡介
- 元素定位
- 使用UI Automator定位元素
- 使用JUnit組織測試用例
- 顯式等待和隱式等待
環境配置
- 安裝Nodejs
- 安裝Appium
- 配置IDE
安裝NodeJs
到 Nodejs官網 下載最新版本的NodeJs并直接安裝。安裝完畢后,打開命令行,輸入 node -v
,出現類似下面的信息說明安裝成功。
V4.0.0
安裝Appium
到 Appium 官網 載和你所使用系統一致的版本進行安裝。
驗證安裝
當確認Appium安裝完畢后,我們可以通過 appium-doctor
的命令來檢查當前appium安裝是否完善,當前的JDK、SDK等環境是否配置正確。
如果 appium-doctor
返回的內容是有錯的,請根據返回的具體的提示,將你的環境搭建完善。
如果返回的結果類似如下,說明安裝成功
...
...
Android Checks were successful.
All Checks were successful
需要注意的是,如果你是通過安裝包安裝的,使用 appium-doctor 命令時必須切換到
C:\Program Files (x86)\Appium\node_modules.bin 目錄;為了方便,需要把.bin目錄添加到環境變量Path中。
配置IDE
配置AndroidStudio
- 新建一個項目,AndroidStudio新建項目會自動生成一個 androidTest 包和 test 包
- 下載 Appium java client 及 Selenium Java standalone server 兩個庫對應的 jar 包
- 將兩個jar包復制到項目module里的 libs 目錄
- 選擇兩個jar包,單擊鼠標右鍵,選擇
Add as Library
- 配置成功,可以在 test 包中新建測試用例了
啟動Appium Server
通過圖形化界面啟動
雙擊Appium圖標,打開Appium應用
點擊箭頭所指的按鈕即可啟動
從命令行啟動
只需要一個命令:
appium
若顯示如下信息則啟動成功:
info: Welcome to Appium v1.4.16 (REV ae6877eff263066b26328d457bd285c0cc62430d)
info: Appium REST http interface listener started on 0.0.0.0:4723
info: Console LogLevel: debug
若需要退出,按下 ctrl+c
即可
配置啟動參數
啟動時可以手動配置參數,了解所有參數,可查閱 Appium 官方文檔
標志 | 默認值 | 描述 | 例子 |
---|---|---|---|
-a , --address | 0.0.0.0 | 監聽的 ip 地址。注意在圖形化界面上默認值為 127.0.0.1 | --address 0.0.0.0 |
-p , --port | 4723 | 監聽的端口。需要啟動多個 appium server 進行并行測試時需要保證每個server 的監聽端口不一樣。 | --port 4723 |
--log-timestamp | false | 在日志輸出里顯示時間戳 | |
--local-timezone | false | 在日志輸出的時間戳使用本地時間 | |
-g , --log | null | 將日志輸出到指定文件 | --g /path/to/appium.log |
--session-override | false | 允許 session 被覆蓋 (沖突的話) | |
--command-timeout | 60 | 默認所有會話的接收命令超時時間 (在超時時間內沒有接收到新命令,自動關閉會話)。 會被新的超時時間覆蓋 | |
這些參數同樣可以在圖形界面設置:
學習DesiredCapabilities
DesiredCapabilities簡介
DesiredCapabilities 攜帶了一些配置信息,在啟動session的時候是必須提供,如啟動模式、apk/app配置、package/activity配置、瀏覽器配置、鍵盤配置等。
Desired Capabilities關鍵字
Desired Capabilities的重要作用是在啟動時傳遞信息給Appium Server。
下表中列舉了Appium常用的 Desired Capabilities 關鍵字。
鍵 | 描述 | 值 |
---|---|---|
automationName |
自動化測試的引擎 |
Appium (默認)或者 Selendroid
|
platformName |
使用的手機操作系統 |
iOS , Android , 或者 FirefoxOS
|
platformVersion |
手機操作系統的版本 | 例如 7.1 , 4.4
|
deviceName |
使用的手機或模擬器類型 |
iPhone Simulator , iPad Simulator , iPhone Retina 4-inch , Android Emulator , Galaxy S4 , 等等.... 在 iOS 上,使用 Instruments 的 instruments -s devices 命令可返回一個有效的設備的列表。在 Andorid 上雖然這個參數目前已被忽略,但仍然需要添加上該參數 |
app |
本地絕對路徑或遠程 http URL 所指向的一個安裝包(.ipa ,.apk ,或 .zip 文件)。Appium 將其安裝到合適的設備上。請注意,如果您指定了 appPackage 和 appActivity 參數(見下文),Android 則不需要此參數了。該參數也與 browserName 不兼容。 |
/abs/path/to/my.apk 或 http://myapp.com/app.ipa
|
browserName |
做自動化時使用的瀏覽器名字。如果是一個應用則只需填寫個空的字符串 | 'Safari' 對應 iOS,'Chrome', 'Chromium', 或 'Browser' 則對應 Android |
newCommandTimeout |
用于客戶端在退出或者結束 session 之前,Appium 等待客戶端發送一條新命令所花費的時間(秒為單位) | 例如 60
|
language |
(Sim/Emu-only) 為模擬器設置語言 | 例如 fr
|
locale |
(Sim/Emu-only) 為模擬器設置所在區域 | 例如 fr_CA
|
udid |
連接真機的唯一設備號 | 例如 1ae203187fc012g
|
orientation |
(Sim/Emu-only) 模擬器當前的方向 |
豎屏 或 橫屏
|
autoWebview |
直接轉換到 Webview 上下文(context)。默認值為 false
|
true , false
|
noReset |
在當前 session 下不會重置應用的狀態。默認值為 false
|
true , false
|
fullReset |
(iOS)刪除所有的模擬器文件夾。(Android) 要清除 app 里的數據,請將應用卸載才能達到重置應用的效果。在 Android, 在 session 完成之后也會將應用卸載掉。默認值為 false
|
true , false
|
Android 獨有
鍵 | 描述 | 值 |
---|---|---|
appActivity |
Activity 的名字是指從你的包中所要啟動的 Android acticity。他通常需要再前面添加. (例如 使用 .MainActivity 代替 MainActivity ) |
MainActivity , .Settings
|
appPackage |
運行的 Android 應用的包名 |
com.example.android.myApp , com.android.settings
|
appWaitActivity |
用于等待啟動的 Android Activity 名稱 | SplashActivity |
appWaitPackage |
用于等待啟動的 Android 應用的包 |
com.example.android.myApp , com.android.settings
|
appWaitDuration |
用于等待 appWaitActivity 啟動的超時時間(以毫秒為單位)(默認值為 20000 ) |
30000 |
deviceReadyTimeout |
用于等待模擬器或真機準備就緒的超時時間 | 5 |
androidCoverage |
用于執行測試的 instrumentation 類。 傳送 -w 參數到如下命令 adb shell am instrument -e coverage true -w
|
com.my.Pkg/com.my.Pkg.instrumentation.MyInstrumentation |
enablePerformanceLogging |
(僅適用于 Chrome 與 webview)開啟 Chromedriver 的性能日志。(默認值為 false ) |
true , false
|
androidDeviceReadyTimeout |
用于等待設備在啟動應用后準備就緒的超時時間。以秒為單位。 | 例如 30
|
androidInstallTimeout |
用于等待在設備中安裝 apk 所花費的時間(以毫秒為單位)。默認值為 90000
|
例如 90000
|
adbPort |
用來連接 ADB 服務器的端口(默認值為 5037 ) |
5037 |
androidDeviceSocket |
開發工具的 socket 名稱。只有在被測應用是一個使用 Chromium 內核的瀏覽器時才需要。socket 會被瀏覽器打開,然后 Chromedriver 把它作為開發者工具來進行連接。 | 例如 chrome_devtools_remote
|
avd |
被啟動 avd 的名字 | 例如 api19
|
avdLaunchTimeout |
用于等待 avd 啟動并連接 ADB 的超時時間(以毫秒為單位),默認值為 120000 。 |
300000 |
avdReadyTimeout |
用于等待 avd 完成啟動動畫的超時時間(以毫秒為單位),默認值為 120000 。 |
300000 |
avdArgs |
啟動 avd 時使用的額外參數 | 例如 -netfast
|
useKeystore |
使用自定義的 keystore 給 apk 簽名,默認值為 false
|
true 或false
|
keystorePath |
自定義 keystore 的路徑, 默認路徑為 ~/.android/debug.keystore | 例如 /path/to.keystore
|
keystorePassword |
自定義 keystore 的密碼 | 例如 foo
|
keyAlias |
key 的別名 | 例如 androiddebugkey
|
keyPassword |
key 的密碼 | 例如 foo
|
chromedriverExecutable |
webdriver 可執行文件的絕對路徑(如果 Chromium 內嵌一個自己提供的 webdriver,則應使用他去替換掉 Appium 自帶的 chromedriver) | /abs/path/to/webdriver |
autoWebviewTimeout |
用于等待 Webview 上下文(context)激活的時間(以毫秒為單位)。默認值為 2000
|
例如 4
|
intentAction |
用于啟動 activity 的 intent action(默認值為 android.intent.action.MAIN ) |
例如 android.intent.action.MAIN , android.intent.action.VIEW
|
intentCategory |
用于啟動 activity 的 intent category。(默認值為 android.intent.category.LAUNCHER ) |
例如 android.intent.category.LAUNCHER , android.intent.category.APP_CONTACTS
|
intentFlags |
用于啟動 activity 的標識(flags)(默認值為 0x10200000 ) |
例如 0x10200000
|
optionalIntentArguments |
用于啟動 activity 的額外 intent 參數。請查看 Intent 參數 | 例如 --esn <EXTRA_KEY> , --ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> , 等等。 |
dontStopAppOnReset |
在使用 adb 啟動應用之前,不要終止被測應用的進程。如果被測應用是被其他鉤子(anchor)應用所創建的,設置該參數為 false 后,就允許鉤子(anchor)應用的進程在使用 adb 啟動被測應用期間仍然存在。換而言之,設置 dontStopAppOnReset 為 true 后,我們在 adb shell am start 的調用中不需要包含 -S 標識(flag)。忽略該 capability 或 設置為 false 的話,就需要包含 -S 標識(flag)。默認值為 false
|
true 或false
|
unicodeKeyboard |
使用 Unicode 輸入法。 默認值為 false
|
true 或false
|
resetKeyboard |
在設定了 unicodeKeyboard 關鍵字的 Unicode 測試結束后,重置輸入法到原有狀態。如果單獨使用,將會被忽略。默認值為 false
|
true 或false
|
noSign |
跳過檢查和對應用進行 debug 簽名的步驟。僅適用于 UiAutomator,不適用于 selendroid。 默認值為 false
|
true 或false
|
ignoreUnimportantViews |
調用 uiautomator 的函數 setCompressedLayoutHierarchy() 。由于 Accessibility 命令在忽略部分元素的情況下執行速度會加快,這個關鍵字能加快測試執行的速度。被忽略的元素將不能夠被找到,因此這個關鍵字同時也被實現成可以隨時改變的 設置 ( settings )。 默認值為 false
|
true 或 false
|
disableAndroidWatchers |
禁用 android 監視器(watchers)。監視器用于監視應用程序的無響應狀態(anr)和崩潰(crash),禁用會降低 Android 設備或模擬器的 CPU 使用率。該 capability 僅在使用 UiAutomator 時有效,不適用于 selendroid,默認設置為 false 。 |
true 或 false
|
chromeOptions |
允許對 ChromeDriver 傳 chromeOptions 的參數。了解更多信息請查閱 chromeOptions | chromeOptions: {args: ['--disable-popup-blocking']} |
recreateChromeDriverSessions |
當移除非 ChromeDriver webview時,終止掉 ChromeDriver 的 session。默認設置為 false
|
true 或false
|
nativeWebScreenshot |
在 web 的上下文(context),使用原生(native)的方法去截圖,而不是用過代理的 ChromeDriver。默認值為 false
|
true 或false
|
androidScreenshotPath |
在設備中截圖被保存的目錄名。默認值為 /data/local/tmp
|
例如 /sdcard/screenshots/
|
autoGrantPermissions |
讓Appium自動確定您的應用需要哪些權限,并在安裝時將其授予應用。默認設置為 false
|
true 或false
|
Session簡介
Session 是指我們的測試腳本從打開應用到最終執行完畢關閉應用的整個過程。
對于測試腳本,從申請到退出一個 session 的整個過程如下:
//打開一個應用,也可以稱為“申請一個Session”
AndroidDriver driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
//此處的所有代碼處于一個Session中
...
//關閉應用,也可以稱為“退出一個Session”
driver.quit();
元素定位
方法名 | 參數 | 描述 |
---|---|---|
findElementByName | String 元素name屬性 |
通過元素name屬性查找,在Android中一般可以用text代替 |
findElementByAndroidUIAutomator | String Ui Automator查找代碼 |
使用UI Automator來查找元素 |
findElementByClassName | String 類名,要寫全路徑:android.weight.TextView |
通過元素類名查找 |
findElementById | String 元素id,android:id/title |
通過元素id查找 |
findElementByAccessibilityId | String 元素的contentDescription屬性 |
通過contentDescription屬性查找 |
findEelementByXPath | String 元素的XPath |
通過XPath查找 |
findElementByCssSelector | WebView專用 | |
findElementByLinkText | WebView專用 | |
findElementByPartialLinkText | WebView專用 |
注: 每一個方法對應著一個
findElementsBy***
方法,后者返回一個Element集合List,表示一個符合查找規則的一個Element集合
findElementByName
在Android中,沒有合適的方法可以找到控件的Name屬性,但是大多數情況下可以用控件的text代替name。
findElementByAndroidUIAutomator
使用UI Automator查找控件:
WebElement el = driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
更多關于UI Automator定位元素的方法,詳見:UI Automator定位元素
findElementByClassName
WebElement el = driver.findElementByClassName("android.weight.TextView");
findElementById
WebElement el = driver.findElementById("android:id/title");
如果目標設備的API Level低于18則UIAutomatorViewer不能獲得對應的Resource ID,只有等于大于18的時候才能使用。
findElementByAccessibilityId
WebElement el = driver.findElementByAccessibilityId("menu_add_note_description");
Accessibility ID在Android上面就等同于contentDescription
findEelementByXPath
WebElement el = driver.findElementByXPath("http://android.widget.TextView[contains(@text,'Add note')]");
xPath是一種路徑,在uiautomatorviewer中可以查看當前頁面的元素層級,XPath就是用來描述這種層級關系的一種路徑表達方式,比如,下圖中的例子,想找列表中第二個note1,則可以這樣寫:
WebElement el = driver.findElementByXPath("http://android.widget.LinearLayout[1]/android.widget.FrameLayout/android.widget.ListView/android.widget.TextView[contains(@index,0)]");
findElementByCssSelector
這個方法是針對WebView容器下面的控件定位的,因為現在針對的是Native App暫時還沒有用到,所以先標記下,今后需要的時候加上去。
findElementByLinkText
這個方法是針對WebView容器下面的控件定位的,因為現在針對的是Native App暫時還沒有用到,所以先標記下,今后需要的時候加上去。
findElementByPartialLinkText
這個方法是針對WebView容器下面的控件定位的,因為現在針對的是Native App暫時還沒有用到,所以先標記下,今后需要的時候加上去。
UI Automater定位元素
UI Automator中主要通過UISelector類查找元素
通過文本信息定位
返回值 | 方法名 | 說明 | 用法 |
---|---|---|---|
UiSelector | text(String text) | 根據“控件text屬性的內容”構造出UiSelector對象 | 例如,一個控件text的值是“發現”,UiSelector s = new UiSelector().text("發現"); |
UiSelector | textContains(String text) | 根據“控件text屬性包含的內容”構造出UiSelector對象 | 同上例子:UiSelector s = new UiSelector().textContains("現"); |
UiSelector | textMatches(String regex) | 根據“控件text屬性正則表達式的內容”構造出UiSelector對象 | 正則表達式語法參考網上資料 |
UiSelector | textStartsWith(String text) | 根據“控件text屬性開始的內容”構造出UiSelector對象 | 同上例子:UiSelector s = new UiSelector().textStartsWith("發"); |
通過description定位
返回值 | 方法名 | 說明 |
---|---|---|
UiSelector | description(String desc) | 根據“控件content-desc屬性的內容”構造出UiSelector對象 |
UiSelector | descriptionContains(String desc) | 包含** |
UiSelector | descriptionMatches(String regex) | 正則 |
UiSelector | descriptionStartsWith(String desc) | 以**開始 |
通過ResourceId定位
返回值 | 方法名 | 說明 |
---|---|---|
UiSelector | resourceId(String id) | 根據資源id獲取對象,例如:UiSelector s = new UiSelector().resourceId("com.tencent.mm:id/b8m") |
UiSelector | resourceIdMatches(String regex) | 根據資源id的正則表達式獲取對象 |
通過類名定位
返回值 | 方法名 | 說明 |
---|---|---|
UiSelector | className(String className) | 根據控件類名找到對象 |
UiSelector | classNameMatches(String regex) | 根據類名的正則表達式獲取對象 |
UiSelector | instance(int instance) | 找到一個擁有相同屬性的對象集合中的對象,例如:UiSelector s = new UiSelector().className("android.widget.TextView").instance(1);可以找到頁面層級中第二個類名為TextView的元素 |
UiSelector | index(int index) | 用法和上面的instance差不多,谷歌的原文說這個方法是unreliable的,推薦使用instance方法 |
通過層級關系
返回值 | 方法名 | 說明 |
---|---|---|
UiSelector | fromParent(UiSelector s) | 獲取同一個父控件的其他子控件,即獲取兄弟控件 |
UiSelector | getFromParent(UiSelector s) | 獲取同一個父控件的其他子控件,即獲取兄弟控件 |
UiSelector | childSelector(UiSelector s) | 獲取子控件 |
UiSelector | getChild(UiSelector s) | 獲取子控件 |
使用JUnit組織測試用例
使用JUnit來組織Appium測試用例,可以添加@Before
、@Test
或者@After
等注解來使測試代碼的運行變得更加靈活。
比如在開始測試之前,需要配置Capability和連接Appium服務器,而且一般需要安裝應用或者打開應用的某個Activity。這時候可把這些操作放到一個方法中,并且方法添加@Before
注解,則在運行的時候,@Before
方法會先于@Test
方法執行。
同理,測試結束后需要關閉session,回收資源等等,可以把這些操作放到一個@After
方法中,@After
方法在所有@Test
方法結束之后執行。
顯式等待和隱式等待
有時候,由于網絡或者其他原因,頁面跳轉之后,某些元素沒有立即顯示出來,此時查找元素會失敗。這種情況就需要引入顯式等待
和隱式等待
線程等待
直接使用Thread.sleep();
隱式等待
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
全局等待30秒,不管元素是否已經加載
顯式等待
WebDriverWait
顯示等待,顯示等待時間可以通過 WebDriverWait
和 util
來決定,比如這個 timeOut
是60,如果該元素60s以內出現就不在等待。
WebDriverWait wait = new WebDriverWait(driver, 60);
WebElement e= wait.until(new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver d) {
return d.findElement(By.id("q"));
}
})