本文為霍格沃茲測(cè)試學(xué)院學(xué)員學(xué)習(xí)筆記加缘。
本系列文章匯總了從 Appium 自動(dòng)化測(cè)試從基礎(chǔ)到框架高級(jí)實(shí)戰(zhàn)中杭跪,所涉及到的方方面面的知識(shí)點(diǎn)精華內(nèi)容(如下所示)醋界,希望對(duì)大家快速總結(jié)和復(fù)習(xí)有所幫助湖蜕。
Appium 自動(dòng)化測(cè)試從基礎(chǔ)到框架實(shí)戰(zhàn)
- Appium 基礎(chǔ) 1 (環(huán)境搭建和簡介)
- Appium 基礎(chǔ) 2 (元素定位和元素常用方法)
- Appium 基礎(chǔ) 3 (手勢(shì)操作和 uiautomator 查找元素)
- Appium 基礎(chǔ) 4 (顯式等待)
- Appium 基礎(chǔ) 5 (toast 和參數(shù)化)
- Appium 基礎(chǔ) 6 (webview)
- Appium_ 企業(yè)微信練習(xí) (非 PO教硫,增加和刪除聯(lián)系人)
- Appium_ 企業(yè)微信練習(xí) ( PO--增加聯(lián)系人)
本文為第三篇叨吮,主要講解 Appium Toast、參數(shù)化瞬矩、WebView(附實(shí)例代碼)茶鉴。
Toast
含義
- 為了給當(dāng)前視圖顯示一個(gè)浮動(dòng)的顯示塊,與 dialog 不同它永遠(yuǎn)不會(huì)獲得焦點(diǎn)景用;
- 顯示時(shí)間有限涵叮,根據(jù)用戶設(shè)置的顯示時(shí)間后自動(dòng)消失;
- 本身是個(gè)系統(tǒng)級(jí)別的控件伞插,它歸屬系統(tǒng) settings割粮,當(dāng)一個(gè) App 發(fā)送消息的時(shí)候,不是自己造出來的這個(gè)彈框媚污,它是發(fā)給系統(tǒng)穆刻,由系統(tǒng)統(tǒng)一進(jìn)行彈框,這類的控件不在 App 內(nèi)杠步、需要特殊的控件識(shí)別方法氢伟;
Toast 定位
Appium 使用 UIAutomator 底層的機(jī)制來分析抓取 toast,并且把 toast 放到控件樹里面朵锣,但本身并不屬于控件
AutoMationName:UIAutomator2 這個(gè)是 Appium 本身的設(shè)置就自帶的,不需要額外添加诚些,默認(rèn)就是UIAutomator2;
getPageSource 是無法找到 Toast 的诬烹;
必須使用 Xpath 去查找:
//*[@class="android.widget.Toast"]
//*[contains(@text,"xxxxx")]
實(shí)例:Appium 自帶的 App 測(cè)試 Toast
adb shell dumpsys window | findstr mCurrent
這個(gè)命令可以找到當(dāng)前的 activity,不知道 Android 高版本是不是還 ok绞吁,由于 API Demo 權(quán)限高幢痘,可直接跳到這個(gè) activity 運(yùn)行家破,其他 App 就不 ok 了颜说;
driver.page_source 可以打印當(dāng)前的頁面,可以找到 Toast 的偽控件汰聋;
打印 toast 的 text 出來门粪;
driver.page_source 打印出來的東西,包含 Toast
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><hierarchy index="0" class="hierarchy" rotation="3" width="810" height="1440"> <android.widget.FrameLayout index="0" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][810,1440]" displayed="true"> <android.view.ViewGroup index="0" package="io.appium.android.apis" class="android.view.ViewGroup" text="" resource-id="android:id/decor_content_parent" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][810,1440]" displayed="true"> <android.widget.FrameLayout index="0" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" resource-id="android:id/action_bar_container" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,41][810,136]" displayed="true"> <android.view.ViewGroup index="0" package="io.appium.android.apis" class="android.view.ViewGroup" text="" resource-id="android:id/action_bar" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,41][810,136]" displayed="true"> <android.widget.TextView index="0" package="io.appium.android.apis" class="android.widget.TextView" text="Views/Popup Menu" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[27,65][324,111]" displayed="true" /> </android.view.ViewGroup> </android.widget.FrameLayout> <android.widget.FrameLayout index="1" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" resource-id="android:id/content" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,136][810,1440]" displayed="true"> <android.widget.LinearLayout index="0" package="io.appium.android.apis" class="android.widget.LinearLayout" text="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,136][810,1440]" displayed="true"> <android.widget.Button index="0" package="io.appium.android.apis" class="android.widget.Button" text="Make a Popup!" content-desc="Make a Popup!" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[297,136][513,217]" displayed="true" /> </android.widget.LinearLayout> </android.widget.FrameLayout> </android.view.ViewGroup> </android.widget.FrameLayout> #這里就找到了Tast的控件了 <android.widget.Toast index="1" package="com.android.settings" class="android.widget.Toast" text="Clicked popup menu item Search" checkable="false" checked="false" clickable="false" enabled="false" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][0,0]" displayed="false" /></hierarchy>
代碼
from appium import webdriverfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom appium.webdriver.common.mobileby import MobileBy as Byclass TestFind(): def setup(self): self.desire_cap= { "platformName":"android", "deviceName":"127.0.0.1:7555", "appPackage":"io.appium.android.apis", "appActivity":"io.appium.android.apis.view.PopupMenu1", "noReset":"true", "unicodeKeyboard":True } self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap) self.driver.implicitly_wait(5) def test_search(self): """ 1.打開appium的演示app 2.直接進(jìn)入到測(cè)試toast的界面 3.點(diǎn)擊顯示toast的按鈕烹困,然后通過driver.page_source獲取頁面 4.找到toast的偽控件 5.打印出toast的值出來 :return: """ #點(diǎn)擊Make a Popup的控件 self.driver.find_element(By.XPATH,'//*[@text="Make a Popup!"]').click() #點(diǎn)擊search的控件 self.driver.find_element(By.XPATH, '//*[@text="Search"]').click() #打印整個(gè)布局頁面的xml出來 print(self.driver.page_source) #打印出toast的值 print(self.driver.find_element(By.XPATH, '//*[contains(@text,"popup menu")]').text)
參數(shù)化
一些小細(xì)節(jié)
- 參數(shù)化要解決的是一個(gè)用例可以復(fù)用的問題玄妈,比如一個(gè)用例重復(fù)使用不同的數(shù)據(jù),就可以使用參數(shù)化髓梅,比如同一個(gè)用例拟蜻,有搜索股價(jià),比較股價(jià)女淑,都是同一個(gè)方法,只是數(shù)據(jù)不太一樣辜御;
- @pytest.mark.parametrize('searchkey,type,price',[('alibaba','BABA',180),('xiaomi','01810',10)
- 用上面的方法去使用參數(shù)化鸭你;
- def test_search(self,searchkey,type,price) 函數(shù)的參數(shù)要和參數(shù)化的參數(shù)的數(shù)量一樣,字符串也要一樣擒权;
- 一個(gè)用例袱巨,有2組參數(shù)化,就會(huì)運(yùn)行兩次 setup 和 teardown 的方法碳抄;
- 使用 self.driver.find_element(By.ID,"com.xueqiu.android:id/search_input_text").send_keys(f"{searchkey}")愉老,使用f"{searchkey}"是一個(gè)好東西,可以搭配參數(shù)化使用剖效;
代碼
from appium import webdriverfrom appium.webdriver.common.mobileby import MobileBy as Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitimport pytestclass TestFind(): #設(shè)置caps的值 def setup(self): self.desire_cap= { #默認(rèn)是Android "platformName":"android", #adb devices的sn名稱 "deviceName":"127.0.0.1:7555", #包名 "appPackage":"com.xueqiu.android", #activity名字 "appActivity":".view.WelcomeActivityAlias", "noReset":"true", "unicodeKeyboard":True } #運(yùn)行appium嫉入,前提是要打開appium server self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap) self.driver.implicitly_wait(5) #這個(gè)加不加都行焰盗,因?yàn)閰?shù)化運(yùn)行都會(huì)有setup的,setup就是啟動(dòng)app的過程了咒林,這個(gè)就顯得有點(diǎn)多余了 #但好像setup并不會(huì)初始化整個(gè)app熬拒,還會(huì)停留在前一個(gè)頁面上,所以還是加上比較好 def teardown(self): self.driver.find_element(By.XPATH,'//*[@text="取消"]').click() #這是參數(shù)化的函數(shù)垫竞,第一部分是參數(shù)化的名字澎粟,得和下面的函數(shù)參數(shù)一模一樣,用字符串包含進(jìn)去 #列表里面的元祖接受具體的參數(shù)化的數(shù)據(jù)欢瞪,用逗號(hào)隔開,和list一樣 @pytest.mark.parametrize('searchkey,type,price',[ ('alibaba','BABA',180), ('xiaomi','01810',10) ]) #參數(shù)哈的函數(shù)的參數(shù)要和上面的參數(shù)名字保持一致 def test_search(self,searchkey,type,price): """ 1.打開雪球app 2.點(diǎn)擊搜索輸入框 3.向搜索輸入框輸入“阿里巴巴” 4.在搜索的結(jié)果里選擇阿里巴巴啸盏,然后點(diǎn)擊 5.獲取這只上香港 阿里巴巴的股價(jià)譬正,并判斷這只股價(jià)的價(jià)格>200 6.通過參數(shù)化的方法,用一個(gè)用例判斷阿里巴巴和小米的股價(jià) :return: """ #顯示等待進(jìn)入主頁粉怕,等主頁的元素都加載好了 WebDriverWait(self.driver, 15).until(expected_conditions.element_to_be_clickable((By.XPATH,'//*[@text="我的"]'))) #點(diǎn)擊搜索框 self.driver.find_element(By.ID,"com.xueqiu.android:id/tv_search").click() #向搜索框輸入阿里巴巴抒巢,小米等參數(shù)化的東西f"{searchkey}"是一個(gè)好用的東西 self.driver.find_element(By.ID,"com.xueqiu.android:id/search_input_text").send_keys(f"{searchkey}") #找到搜索框預(yù)覽結(jié)果的阿里巴巴,并點(diǎn)擊 self.driver.find_element(By.XPATH,f"http://*[@text='{type}']").click() #選擇HK股價(jià)的元素稚晚,這里是通過父類的方法去定位的 current_price=self.driver.find_element(By.XPATH,f"http://*[@text='{type}']/../../..//*[@resource-id='com.xueqiu.android:id/current_price']") #提取股價(jià)的text屬性 current_price=float(current_price.text) #判斷股價(jià)是否大于200 assert current_price > price
WebView
純 WebView 測(cè)試(只測(cè)試瀏覽器)的環(huán)境準(zhǔn)備
手機(jī)端
被測(cè)瀏覽器:(不可以是第三方瀏覽器)safari for ios and chrome型诚,chromium,or browser for Android
PC 端
安裝 chrome 瀏覽器或者 chromium
下載對(duì)應(yīng)手機(jī)瀏覽器對(duì)應(yīng)的 driver
客戶端代碼:
"browserName":"Browser" 或者 "browserName":"Chrome" 這個(gè)是指定的瀏覽器
"chromedriverExecutable":r"c:\chrome\chromedriver.exe" 這個(gè)是指定的chromedriver的路徑
如何查找app的版本:adb shell pm dump com.android.browser | findstr version
desire_cap
案例:打開 mumu 自帶的瀏覽器狰贯,訪問百度
步驟:
不通過包來打開瀏覽器
訪問百度
輸入 tongtong,并點(diǎn)擊搜索
注意:
第一次運(yùn)行 Appium傍妒,看后臺(tái)的路徑可以找到瀏覽器的 chromedriver 的版本摸柄,還可以找到 chromedriver 的路徑
這個(gè)網(wǎng)站的 chromedriver 和 chrome 版本的關(guān)系更加全
代碼
from time import sleepfrom appium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitclass TestFind(): def setup(self): self.desire_cap= { "platformName":"android", "platformVersion":"6.0", "deviceName":"127.0.0.1:7555", #想要使用原生的瀏覽器就選擇驱负,Browser患雇。想要選擇chrome瀏覽器就輸入Chrome "browserName":"Browser", "noRest":True, #這里是指定chromedriver的路徑踏揣,記得路徑要全到包括chromedriver.exe "chromedriverExecutable":r"c:\chrome\chromedriver.exe" } self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap) self.driver.implicitly_wait(5) def teardown(self): self.driver.quit() def test_browser(self): #打開移動(dòng)端的百度瀏覽器 self.driver.get("http://m.baidu.com") #顯示等待找到搜索框是否可見庆亡,expected_conditions里面?zhèn)鞯膌ocator必須是一個(gè)元祖 WebDriverWait(self.driver,10).until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR,"#index-kw"))) #搜索框輸入tongtong self.driver.find_element(By.CSS_SELECTOR,"#index-kw").send_keys("tongtong") sleep(2) #百度一下點(diǎn)擊一下 self.driver.find_element(By.CSS_SELECTOR, "#index-bn").click() sleep(3)
如何判斷頁面是不是 WebView
- 斷網(wǎng)查看捞稿,如果斷網(wǎng)顯示網(wǎng)頁加載不了就是 WebView
- 看加載條又谋,有加載條通常是 WebView
- 看頂部是否有關(guān)閉按鈕
- 下拉刷新彰亥,頁面有刷新就是 WebView
- 下拉刷新的時(shí)候是否有網(wǎng)頁提供者
- 用工具查看衰齐,如果元素顯示 WebView,則是 WebView
WebView
- 是 Android 系統(tǒng)提供能顯示頁面的系統(tǒng)控件(特殊的 view)
- < android4.4 WebView 底層實(shí)現(xiàn) webkit 內(nèi)部
=android4.4 采用 chromium 作為 WebView 底層支持废酷,支持 HTML5抹缕、CSS3、JS
- WebAudio:圖形化的界面收聽音頻
- WebGL:頁面 3d 效果的渲染
- WebRTC:直播等等趴俘,美顏
混合 WebView 測(cè)試條件
PC:
能夠訪問 Google
下載對(duì)應(yīng)版本的 chromedriver
手機(jī)端:應(yīng)用代碼需要打開WebView的開關(guān)
代碼中要添加 chromedriverExecutable
有一些 WebView 可以被 UIAutomatorview 查找到奏赘,但都不推薦,可能會(huì)出現(xiàn)兼容性的問題疲憋,比如 text 的顯示字符串會(huì)不一樣
如何查找當(dāng)前 WebView 的網(wǎng)頁
adb shell
logcat | grep http
就能找到訪問的 HTTP 了
案例1 Appium 的 API 的混合 WebView
- 打開 API demo 的 WebView
- 向輸入框輸入文本
- 點(diǎn)擊 i am link
- 退出應(yīng)用
代碼
from time import sleepfrom appium import webdriverfrom appium.webdriver.common.mobileby import MobileByclass TestFind(): def setup(self): self.desire_cap= { "platformName":"android", "platformVersion":"6.0", "deviceName":"127.0.0.1:7555", "noRest":True, "appPackage": "io.appium.android.apis", "appActivity":"io.appium.android.apis.view.webview1", #想要切換webview梁只,必須得指定chromdriver缚柳,或者你的默認(rèn)地址的chromedriver的版本和手機(jī)的版本是對(duì)應(yīng)的 "chromedriverExecutable": r"c:\chrome\chromedriver.exe" } self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap) self.driver.implicitly_wait(5) def teardown(self): self.driver.quit() def test_appium_api_webview(self): sleep(3) #進(jìn)入到webview頁面敛纲,直接打印contexts剂癌,肯定有兩個(gè),一個(gè)是原生的旁壮,一個(gè)是webview print(self.driver.contexts) #需要切換到webview的context的监嗜,通常是倒數(shù)第一個(gè)抡谐,記得要有chromedriverExecutable self.driver.switch_to.context(self.driver.contexts[-1]) sleep(2) #往輸入框輸入tongtong self.driver.find_element(MobileBy.ID,"i_am_a_textbox").send_keys("tongtong") #點(diǎn)擊鏈接 self.driver.find_element(MobileBy.ID,"i am a link").click() #打印出當(dāng)前的頁面布局,發(fā)現(xiàn)是一個(gè)webview的html的布局 print(self.driver.page_source)
案例2 雪球webview
- 打開應(yīng)用
- 點(diǎn)擊交易
- 點(diǎn)擊 A 股開戶
- 輸入用戶名和密碼
- 點(diǎn)擊立即開戶
- 退出應(yīng)用
- 注:打開新的頁面其實(shí)就是一個(gè)新的窗口了刽肠,要切換窗口句柄了
#由于chrome識(shí)別不到雪球的webview音五,元素定位有問題羔沙,所以代碼搞不定from time import sleepfrom appium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitclass TestFind(): def setup(self): self.desire_cap= { "platformName":"android", "platformVersion":"6.0", "deviceName":"127.0.0.1:7555", #想要使用原生的瀏覽器就選擇,Browser扼雏。想要選擇chrome瀏覽器就輸入Chrome "browserName":"Browser", "noRest":True, #這里是指定chromedriver的路徑,記得路徑要全到包括chromedriver.exe "chromedriverExecutable":r"c:\chrome\chromedriver.exe" } self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap) self.driver.implicitly_wait(5) def teardown(self): self.driver.quit() def test_browser(self): #打開移動(dòng)端的百度瀏覽器 self.driver.get("http://m.baidu.com") #顯示等待找到搜索框是否可見苍蔬,expected_conditions里面?zhèn)鞯膌ocator必須是一個(gè)元祖 WebDriverWait(self.driver,10).until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR,"#index-kw"))) #搜索框輸入tongtong self.driver.find_element(By.CSS_SELECTOR,"#index-kw").send_keys("tongtong") sleep(2) #百度一下點(diǎn)擊一下 self.driver.find_element(By.CSS_SELECTOR, "#index-bn").click() sleep(3)
WebView 遇到的坑
設(shè)備
Android 模擬器 6.0 默認(rèn)支持 WebView其障,mumu 直接打開了,不用設(shè)置蜈敢;
起碼模擬器和物理機(jī)需要打開 App 內(nèi)開關(guān)(WebView 調(diào)試開關(guān))汽抚;
PC 瀏覽器定位元素
Chrome 瀏覽器-62版本才可以更好的看見 webview 的內(nèi)部,其他的版本都有一些 bug否过;
換成 chromium 瀏覽器可以避免很多坑惭蟋,展示效果和速度要比 chrome 要快;
代碼
有的設(shè)備可以使用 find_element_acessibility_id(), 不同的設(shè)備渲染的頁面不同煤伟,兼容性不適合;
switch_to.context() 切換不同的 context围辙,一個(gè)頁面來說放案;
switch.to_window() 切換不同的窗口句柄,對(duì)不同的頁面來說掸冤;
更多內(nèi)容友雳,我們?cè)诤罄m(xù)文章分享。
(文章來源于霍格沃茲測(cè)試學(xué)院)