史上最全 Appium 自動化測試從入門到框架實戰精華學習筆記(二)
阿新 • • 發佈:2020-07-13
本文為霍格沃茲測試學院學員學習筆記,進階學習文末加群。
本系列文章彙總了從 Appium 自動化測試從基礎到框架高階實戰中,所涉及到的方方面面的知識點精華內容(如下所示),希望對大家快速總結和複習有所幫助。
Appium 自動化測試從基礎到框架實戰
- Appium 基礎 1 (環境搭建和簡介)
- Appium 基礎 2 (元素定位和元素常用方法)
- Appium 基礎 3 (手勢操作和 uiautomator 查詢元素)
- Appium 基礎 4 (顯式等待)
- Appium 基礎 5 (toast 和引數化)
- Appium 基礎 6 (webview)
- Appium_ 企業微信練習 (非 PO,增加和刪除聯絡人)
- Appium_ 企業微信練習 ( PO--增加聯絡人)
本文為第二篇,主要講解 Appium 手勢操作、查詢元素、顯示等待(附例項程式碼)。
Appium 的觸屏操作
滑動小案例
- 進入雪球應用
- 再主頁從下往上滑動
- 避免使用座標(程式碼用獲取螢幕的長寬來解決這個問題)
程式碼
fromtimeimportsleep
fromappiumimportwebdriver
fromappium.webdriver.common.touch_actionimportTouchAction
classTestFind():
defsetup(self):
self.desire_cap={
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"com.xueqiu.android",
"appActivity":".view.WelcomeActivityAlias",
"noReset":"true",
"unicodeKeyboard":True
}
self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
self.driver.implicitly_wait(5)
deftest_search(self):
"""
1.進入雪球應用
2.再主頁從下往上滑動
3.避免使用座標
:return:
"""
#由於雪球真的是太慢了,所以睡10秒
sleep(10)
#定義一個TouchAcion物件
aciton=TouchAction(self.driver)
#獲取整個螢幕的右下角的座標
window_rect=self.driver.get_window_rect()
#提取螢幕的最大的寬
width=window_rect["width"]
#提取螢幕的最大的高度
height=window_rect['height']
#x的座標定義為最大寬的一半,也就是中心的x座標
x1=int(width/2)
#定義起始的y座標,在4/5的底部位置
y_start=int(height*4/5)
#定義終點的y座標,在1/5頂部的位置,這樣就可以模擬從下往上滑動的動作
y_end=int(height*1/5)
#先press點選初始的座標,然後按住不放等2秒再move_to到終點座標,然後再release()釋放座標點,用perform()去執行一系列action操作
aciton.press(x=x1,y=y_start).wait(2000).move_to(x=x1,y=y_end).release().perform()
#重複兩次,看的效果更明顯
aciton.press(x=x1,y=y_start).wait(2000).move_to(x=x1,y=y_end).release().perform()
aciton.press(x=x1,y=y_start).wait(2000).move_to(x=x1,y=y_end).release().perform()
sleep(3)
滑動多點解鎖
- 得下載一個叫手勢密碼鎖的 App,百度一下有
- 進入解鎖的頁面
- 設定解鎖密碼為一個7字
- 意外發現 Appium 可以指定去不同的初始的 activity,好像也是看應用的
程式碼
fromtimeimportsleep
fromappiumimportwebdriver
fromappium.webdriver.common.mobilebyimportMobileByasBy
fromappium.webdriver.common.touch_actionimportTouchAction
classTestFind():
defsetup(self):
self.desire_cap={
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"cn.kmob.screenfingermovelock",
"appActivity":"com.samsung.ui.MainActivity",
"noReset":"true"
}
self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
self.driver.implicitly_wait(5)
deftest_search(self):
"""
1.進入解鎖的頁面
2.設定解鎖密碼為一個7字
3.意外發現appium可以指定去不同的初始的activity,好像也是看應用的
:return:
"""
sleep(3)
#定義一個TouchAcion物件
aciton=TouchAction(self.driver)
#找到7個座標點,通過連續的press,wait,move_to,最後釋放手勢release(),然後perform()執行即可
aciton.press(x=142,y=190).wait(200).move_to(x=408,y=190).wait(200).move_to(x=678,y=190).wait(200).move_to(x=678,y=464)\
.wait(200).move_to(x=678,y=740).release().perform()
sleep(2)
UIAutomator 查詢元素
優缺點
- 優點
- xpath 定位速度慢
- UIAutomator 是 Android 的工作引擎,速度快
- 滾動查詢很方便
- 缺點
- 表示式書寫複雜,容易寫錯 IDE 沒有提示
定位方式
- 通過 resource-id 定位
- 通過 classname 定位
- 通過 content-desc 定位
- 通過文字定位
- 組合定位
- 通過父子關係定位
用法
- driver.find_element_by_android_uiautomator("表示式")
- 注:外層要用單引號,內層的字串用雙引號,因為本來就是 Java,Java 雙引號才表示字串
- 通過文字定位
- new UiSelector().text("text文字")
- 通過 textContains 模糊匹配
- new UiSelector().textContains("text文字")
- 通過某個文字開頭匹配
- new UiSelector().textStartWith("text文字")
- 正則表示式匹配
- new UiSelector().textMatches("text文字")
- 組合定位
- 比如 id 與 text 的屬性組合:driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/login_account").text("我的")')
- 父子關係定位:childSelector,先定位到父類,再用 childSelector 來定位子類
- driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/login_account").childSelector(text("股票"))')
- 兄弟定位:fromParent
- driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/login_account").fromParent(text("股票"))')
程式碼
fromtimeimportsleep
fromappiumimportwebdriver
fromappium.webdriver.common.touch_actionimportTouchAction
classTestFind():
defsetup(self):
self.desire_cap={
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"com.xueqiu.android",
"appActivity":".view.WelcomeActivityAlias",
"noReset":"true",
"unicodeKeyboard":True
}
self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
self.driver.implicitly_wait(5)
deftest_search(self):
"""
1.開啟雪球app
2.點選我的,進入到個人資訊頁面
3.點選登入,進入到登入頁面
4.輸入使用者名稱,輸入密碼
5.點選登入
6.彈出手機號輸入失敗的提示,並assert這個提示對不對
:return:
"""
#雪球太慢了,只能10秒了,懶得用顯示等等
sleep(10)
#在首頁找到我的元素,然後點選
self.driver.find_element_by_android_uiautomator('newUiSelector().text("我的")').click()
#不睡2秒回導致下一個頁面的元素重新整理太快識別不到
sleep(2)
#識別賬號密碼登入的元素,然後點選
self.driver.find_element_by_android_uiautomator('newUiSelector().text("帳號密碼登入")').click()
#不睡2秒回導致下一個頁面的元素重新整理太快識別不到
sleep(2)
#輸入賬號名為tongtong
self.driver.find_element_by_android_uiautomator('newUiSelector().resourceId("com.xueqiu.android:id/login_account")').send_keys("tongtong")
#輸入密碼為tongtong
self.driver.find_element_by_android_uiautomator('newUiSelector().resourceId("com.xueqiu.android:id/login_password")').send_keys("tongtong")
#不睡2秒回導致下一個頁面的元素重新整理太快識別不到
sleep(2)
#點選登入按鈕
self.driver.find_element_by_android_uiautomator('newUiSelector().text("登入")').click()
#不睡2秒回導致下一個頁面的元素重新整理太快識別不到
sleep(2)
#找到錯誤提示框,裡面有一個確定的元素
login_incorrect=self.driver.find_element_by_android_uiautomator('newUiSelector().text("確定")')
#當確定的元素可見,表示登入失敗,用例pass
assertlogin_incorrect.is_displayed()
滑動元素查詢
- 有一些頁面有持續滑動的能力,比如微博,沒有分頁,可以一直滑動,UIAutomator 提供了滑動的很好的方法。
- driver.find_element_by_android_uiautomator(‘new UiScrollable(new UiSelector().’‘scrollable(true).instance(0)).’‘scrollIntoView(new UiSelector().textContains(“病人”).’‘instance(0));’).click()
- 注意:虛擬機器和真機不一樣,有時候真機的滑動是ok的,有時候虛擬機器的不ok
程式碼
fromtimeimportsleep
fromappiumimportwebdriver
fromappium.webdriver.common.touch_actionimportTouchAction
classTestFind():
defsetup(self):
self.desire_cap={
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"com.xueqiu.android",
"appActivity":".view.WelcomeActivityAlias",
"noReset":"true",
"unicodeKeyboard":True
}
self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
self.driver.implicitly_wait(5)
deftest_search(self):
"""
0.你的雪球app先關注一個人,然後往下滑,找到一個關鍵字,用textContains來模糊匹配
1.開啟雪球app
2.點選關注,讓螢幕往下滑,直到找到病人的模糊匹配的text元素後點擊
:return:
"""
#雪球太慢了,只能10秒了,懶得用顯示等等
sleep(10)
#點選關注的元素
self.driver.find_element_by_android_uiautomator('newUiSelector().text("關注")').click()
#睡4秒,怕跳轉頁面太快,搜尋不到元素
sleep(4)
self.driver.find_element_by_android_uiautomator('newUiScrollable(newUiSelector().'
'scrollable(true).instance(0)).'
'scrollIntoView(newUiSelector().textContains("病人").'
'instance(0));').click()
sleep(4)
強制等待、隱式等待、顯示等待
三者的特點
- 強制等待是 sleep,強烈不推薦,設定的時間太固定,如果是模擬器等待3秒,真機可能只需要等待2秒
- driver.implicitly.wat(timeout),貫穿全部元素的等待,只需要設定一次即可,通常是在建立 driver 的時候後的程式碼執行,是 dom 建立之後的等待;
- 顯示等待是在客戶端的等待:引用兩個包和一個例子
- from selenium.webdriver.support.wait import WebDriverWait
- from selenium.webdriver.support import expected_conditions
- WebDriverWait(self.driver,10).until(expected_conditions.element_to_be_clickable(locator))
顯示等待的簡介
- 顯示等待與隱式等待相對,顯示等待必須在每一個需要等待的元素前面進行宣告
- 是針對某個特定的元素設定等待時間,在設定時間內,預設美格一段時間檢測一次當前某個元素是否存在
- 如果在規定的時間內找到元素,則直接執行,即找到元素就執行相關操作
- 如果超過設定時間檢測不到就丟擲異常,預設檢測頻率為0.5s,預設丟擲的異常時NoSuchElementException
- 用到的兩個常用類
- WebDriverWait
- expected_condition
為什麼要用顯示等待,為什麼隱式等待無法替代顯示等待?
- 顯示等待可以等待動態載入的 AJax 元素,需要配合 expected_condition 來檢查條件
- 一般頁面上元素的呈現順序是
- 首先出現 title;
- 然後是 dom 樹的出現,presence 還不完整,dom 樹出現就是隱式等待了,但此時的元素可能還沒有是可點選的狀態,所以只用隱式等待,使用 click 方法,肯定會報錯的;
- CSS 出現:可見 visbility;
- JS 的出現,JS 特效執行:可點選 clickable;
- HTML 文件是自上而下載入的
- JS 檔案載入會阻塞 HTML 內容的載入,有些 JS 非同步載入的方式來完成 JS 的載入
- 樣式表下載完成之後跟之前的樣式表一起進行解析,會對之前那的與元素重新渲染
- presence-visibility-clickabe,元素出現-可見-可點選,是元素的三個性質,當 DOM 樹出現時,定位元素可能已經顯示出來了,但是可見和可點選的屬性可能還沒加載出來,這時候元素的一些方法是不可用的,比如 element.click(),要等到 JS 渲染出來以後,元素的 click 屬性才可以用
- 對應element.is_displayed()
- 對應element.is_selected()
- 對應element.is_enabled()
JS 的同步載入和非同步載入
- 同步載入:同步模式,又稱阻塞模式,會阻止瀏覽器的後續處理,停止了後續的解析,因此停止了後續的檔案載入(如影象)、渲染、程式碼執行。
- 非同步載入:非同步載入又叫非阻塞,瀏覽器在下載執行 JS 同時,還會繼續進行後續頁面的處理。
WebDriverWait 用法
- WebDriverWait(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None)
- driver:瀏覽器驅動
- timeout:超時時間,單位秒
- poll_frequency:檢查的間隔步長,預設是0.5s
- ignored_exceptions:超時最後的丟擲的異常,預設是NoSuchElementException
- 通常我們只會用到 driver 和 timeout
- WebDriverWait().unti(self, method, message='') or until_not()的方法:
- method:在等待期間,每個一段時間(init中的poll_frequency)呼叫這個傳入的方法,直到返回值不是 False
- message:如果超時,丟擲 TimeoutException,將 message 傳入異常
- until not 是當某個元素小時或什麼條件則繼續執行,引數也相同
expected_conditions 類
- Appium 直接幫我們封裝好了類,只需要傳引數即可,比如我們使用的是 click(),只需要判斷這個元素是否可點選屬性才繼續點選
- 用法:expected_conditions.element_to_be_clickable(locator),其中 locator 就是:(By.ID, "com.xueqiu.android:id/tv_search")
- 常用的幾個如下:
- expected_conditions.element_to_be_clickable:元素是否可點選
- expected_conditions.presence_of_element_located:元素是否被加到 Dom 樹裡面
- expected_conditions.visibility_of_element_located:元素是否可見
Lambda 獲取元素
#可以獲取到元素
element=WebDriverWait(self.driver,10).until(lambdax:x.find_element(By.XPATH,'//*[@text="我的"]'))
#這裡找到元素後,不用等待,實測證明過了
element.click()
例項程式碼
fromappiumimportwebdriver
fromselenium.webdriver.supportimportexpected_conditions
fromselenium.webdriver.support.waitimportWebDriverWait
fromappium.webdriver.common.mobilebyimportMobileByasBy
classTestFind():
defsetup(self):
self.desire_cap={
"platformName":"android",
"deviceName":"127.0.0.1:7555",
"appPackage":"com.xueqiu.android",
"appActivity":".view.WelcomeActivityAlias",
"noReset":"true",
"unicodeKeyboard":True
}
self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)
self.driver.implicitly_wait(5)
deftest_search(self):
"""
1.開啟雪球app
2.點選我的,進入到個人資訊頁面
3.點選登入,進入到登入頁面
4.輸入使用者名稱,輸入密碼
5.點選登入
6.彈出手機號輸入失敗的提示,並assert這個提示對不對
:return:
"""
#By.name方法不是對應text的,千萬不要用
#self.driver.find_element(By.NAME,"我的")
#用顯示等待,element_to_be_clickable(locator),裡面的locator記得用元祖
WebDriverWait(self.driver,15).until(expected_conditions.element_to_be_clickable((By.XPATH,'//*[@text="我的"]')))
#在首頁找到我的元素,然後點選
self.driver.find_element(By.XPATH,'//*[@text="我的"]').click()
'''
lambda返回元素
element=WebDriverWait(self.driver,10).until(lambdax:x.find_element(By.XPATH,'//*[@text="我的"]'))
這裡找到元素後,不用等待,實測證明過了
element.click()
'''
#顯示等待找到賬號密碼登入的元素
WebDriverWait(self.driver,15).until(expected_conditions.element_to_be_clickable((By.XPATH,'//*[@text="帳號密碼登入"]')))
#識別賬號密碼登入的元素,然後點選
self.driver.find_element_by_android_uiautomator('newUiSelector().text("帳號密碼登入")').click()
#顯示等待找到賬號的元素
WebDriverWait(self.driver,15).until(
expected_conditions.element_to_be_clickable((By.XPATH,'//*[@resource-id="com.xueqiu.android:id/login_account"]')))
#輸入賬號名為tongtong
self.driver.find_element_by_android_uiautomator('newUiSelector().resourceId("com.xueqiu.android:id/login_account")').send_keys("tongtong")
#輸入密碼為tongtong
self.driver.find_element_by_android_uiautomator('newUiSelector().resourceId("com.xueqiu.android:id/login_password")').send_keys("tongtong")
#顯示等待找到登入的按鈕
WebDriverWait(self.driver,15).until(
expected_conditions.element_to_be_clickable((By.XPATH,'//*[@text="登入"]')))
#點選登入按鈕
self.driver.find_element_by_android_uiautomator('newUiSelector().text("登入")').click()
#顯示等待找到確定的元素
WebDriverWait(self.driver,15).until(
expected_conditions.element_to_be_clickable((By.XPATH,'//*[@text="確定"]')))
#找到錯誤提示框,裡面有一個確定的元素
login_incorrect=self.driver.find_element_by_android_uiautomator('newUiSelector().text("確定")')
#當確定的元素可見,表示登入失敗,用例pass
assertlogin_incorrect.is_displayed()
更多內容,我們在後續文章分享。
想關注更多內容,可關注公眾號:霍格沃茲測試學院