1. 程式人生 > 實用技巧 >史上最全 Appium 自動化測試從入門到框架實戰精華學習筆記(二)

史上最全 Appium 自動化測試從入門到框架實戰精華學習筆記(二)

本文為霍格沃茲測試學院學員學習筆記,進階學習文末加群。

本系列文章彙總了從 Appium 自動化測試從基礎到框架高階實戰中,所涉及到的方方面面的知識點精華內容(如下所示),希望對大家快速總結和複習有所幫助。

Appium 自動化測試從基礎到框架實戰

  1. Appium 基礎 1 (環境搭建和簡介)
  2. Appium 基礎 2 (元素定位和元素常用方法)
  3. Appium 基礎 3 (手勢操作和 uiautomator 查詢元素)
  4. Appium 基礎 4 (顯式等待)
  5. Appium 基礎 5 (toast 和引數化)
  6. Appium 基礎 6 (webview)
  7. Appium_ 企業微信練習 (非 PO,增加和刪除聯絡人)
  8. Appium_ 企業微信練習 ( PO--增加聯絡人)

本文為第二篇,主要講解 Appium 手勢操作、查詢元素、顯示等待(附例項程式碼)。

Appium 的觸屏操作

滑動小案例

  1. 進入雪球應用
  2. 再主頁從下往上滑動
  3. 避免使用座標(程式碼用獲取螢幕的長寬來解決這個問題)

程式碼

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)

滑動多點解鎖

  1. 得下載一個叫手勢密碼鎖的 App,百度一下有
  2. 進入解鎖的頁面
  3. 設定解鎖密碼為一個7字
  4. 意外發現 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()

更多內容,我們在後續文章分享。

想關注更多內容,可關注公眾號:霍格沃茲測試學院