史上最全 Appium 自動(dòng)化測(cè)試從入門(mén)到框架實(shí)戰(zhàn)精華學(xué)習(xí)筆記(三)

史上最全 Appium 自動(dòng)化測(cè)試從入門(mén)到框架實(shí)戰(zhàn)精華學(xué)習(xí)筆記(三)

本文為霍格沃茲測(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)

  1. Appium 基礎(chǔ) 1 (環(huán)境搭建和簡(jiǎn)介)
  2. Appium 基礎(chǔ) 2 (元素定位和元素常用方法)
  3. Appium 基礎(chǔ) 3 (手勢(shì)操作和 uiautomator 查找元素)
  4. Appium 基礎(chǔ) 4 (顯式等待)
  5. Appium 基礎(chǔ) 5 (toast 和參數(shù)化)
  6. Appium 基礎(chǔ) 6 (webview)
  7. Appium_ 企業(yè)微信練習(xí) (非 PO,增加和刪除聯(lián)系人)
  8. 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ù)用戶(hù)設(shè)置的顯示時(shí)間后自動(dòng)消失;
  • 本身是個(gè)系統(tǒng)級(jí)別的控件,它歸屬系統(tǒng) settings,當(dāng)一個(gè) App 發(fā)送消息的時(shí)候,不是自己造出來(lái)的這個(gè)彈框,它是發(fā)給系統(tǒng),由系統(tǒng)統(tǒng)一進(jìn)行彈框,這類(lèi)的控件不在 App 內(nèi)、需要特殊的控件識(shí)別方法;

Toast 定位

  • Appium 使用 UIAutomator 底層的機(jī)制來(lái)分析抓取 toast,并且把 toast 放到控件樹(shù)里面,但本身并不屬于控件

  • AutoMationName:UIAutomator2 這個(gè)是 Appium 本身的設(shè)置就自帶的,不需要額外添加,默認(rèn)就是UIAutomator2;

  • getPageSource 是無(wú)法找到 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)前的頁(yè)面,可以找到 Toast 的偽控件;

  • 打印 toast 的 text 出來(lái);

driver.page_source 打印出來(lái)的東西,包含 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.打開(kāi)appium的演示app        2.直接進(jìn)入到測(cè)試toast的界面        3.點(diǎn)擊顯示toast的按鈕,然后通過(guò)driver.page_source獲取頁(yè)面        4.找到toast的偽控件        5.打印出toast的值出來(lái)        :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è)布局頁(yè)面的xml出來(lái)        print(self.driver.page_source)        #打印出toast的值        print(self.driver.find_element(By.XPATH, '//*[contains(@text,"popup menu")]').text)

參數(shù)化

一些小細(xì)節(jié)

  • 參數(shù)化要解決的是一個(gè)用例可以復(fù)用的問(wèn)題,比如一個(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名稱(chēng)            "deviceName":"127.0.0.1:7555",            #包名            "appPackage":"com.xueqiu.android",            #activity名字            "appActivity":".view.WelcomeActivityAlias",            "noReset":"true",            "unicodeKeyboard":True        }        #運(yùn)行appium,前提是要打開(kāi)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的過(guò)程了,這個(gè)就顯得有點(diǎn)多余了    #但好像setup并不會(huì)初始化整個(gè)app,還會(huì)停留在前一個(gè)頁(yè)面上,所以還是加上比較好    def teardown(self):        self.driver.find_element(By.XPATH,'//*[@text="取消"]').click()    #這是參數(shù)化的函數(shù),第一部分是參數(shù)化的名字,得和下面的函數(shù)參數(shù)一模一樣,用字符串包含進(jìn)去    #列表里面的元祖接受具體的參數(shù)化的數(shù)據(jù),用逗號(hào)隔開(kāi),和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.打開(kāi)雪球app        2.點(diǎn)擊搜索輸入框        3.向搜索輸入框輸入“阿里巴巴”        4.在搜索的結(jié)果里選擇阿里巴巴,然后點(diǎn)擊        5.獲取這只上香港 阿里巴巴的股價(jià),并判斷這只股價(jià)的價(jià)格>200        6.通過(guò)參數(shù)化的方法,用一個(gè)用例判斷阿里巴巴和小米的股價(jià)        :return:        """        #顯示等待進(jìn)入主頁(yè),等主頁(yè)的元素都加載好了        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à)的元素,這里是通過(guò)父類(lèi)的方法去定位的        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

  • 客戶(hù)端代碼:

  • "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

案例:打開(kāi) mumu 自帶的瀏覽器,訪(fǎng)問(wèn)百度

  • 步驟:

  • 不通過(guò)包來(lái)打開(kāi)瀏覽器

  • 訪(fǎng)問(wèn)百度

  • 輸入 tongtong,并點(diǎn)擊搜索

  • 注意:

  • 第一次運(yùn)行 Appium,看后臺(tái)的路徑可以找到瀏覽器的 chromedriver 的版本,還可以找到 chromedriver 的路徑

  • https://blog.csdn.net/huilan_same/article/details/51896672

  • 這個(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):        #打開(kāi)移動(dòng)端的百度瀏覽器        self.driver.get("http://m.baidu.com")        #顯示等待找到搜索框是否可見(jiàn),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)

如何判斷頁(yè)面是不是 WebView

  • 斷網(wǎng)查看,如果斷網(wǎng)顯示網(wǎng)頁(yè)加載不了就是 WebView
  • 看加載條,有加載條通常是 WebView
  • 看頂部是否有關(guān)閉按鈕
  • 下拉刷新,頁(yè)面有刷新就是 WebView
  • 下拉刷新的時(shí)候是否有網(wǎng)頁(yè)提供者
  • 用工具查看,如果元素顯示 WebView,則是 WebView

WebView

  • 是 Android 系統(tǒng)提供能顯示頁(yè)面的系統(tǒng)控件(特殊的 view)
  • < android4.4 WebView 底層實(shí)現(xiàn) webkit 內(nèi)部
  • =android4.4 采用 chromium 作為 WebView 底層支持,支持 HTML5、CSS3、JS

  • WebAudio:圖形化的界面收聽(tīng)音頻
  • WebGL:頁(yè)面 3d 效果的渲染
  • WebRTC:直播等等,美顏

混合 WebView 測(cè)試條件

  • PC:

  • 能夠訪(fǎng)問(wèn) Google

  • 下載對(duì)應(yīng)版本的 chromedriver

  • 手機(jī)端:應(yīng)用代碼需要打開(kāi)WebView的開(kāi)關(guān)

  • 代碼中要添加 chromedriverExecutable

  • 有一些 WebView 可以被 UIAutomatorview 查找到,但都不推薦,可能會(huì)出現(xiàn)兼容性的問(wèn)題,比如 text 的顯示字符串會(huì)不一樣

  • 如何查找當(dāng)前 WebView 的網(wǎng)頁(yè)

  • adb shell

  • logcat | grep http

  • 就能找到訪(fǎng)問(wèn)的 HTTP 了


案例1 Appium 的 API 的混合 WebView

  • 打開(kāi) 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頁(yè)面,直接打印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)前的頁(yè)面布局,發(fā)現(xiàn)是一個(gè)webview的html的布局        print(self.driver.page_source)

案例2 雪球webview

  • 打開(kāi)應(yīng)用
  • 點(diǎn)擊交易
  • 點(diǎn)擊 A 股開(kāi)戶(hù)
  • 輸入用戶(hù)名和密碼
  • 點(diǎn)擊立即開(kāi)戶(hù)
  • 退出應(yīng)用
  • 注:打開(kāi)新的頁(yè)面其實(shí)就是一個(gè)新的窗口了,要切換窗口句柄了
#由于chrome識(shí)別不到雪球的webview,元素定位有問(wè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):        #打開(kāi)移動(dòng)端的百度瀏覽器        self.driver.get("http://m.baidu.com")        #顯示等待找到搜索框是否可見(jiàn),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 直接打開(kāi)了,不用設(shè)置;

  • 起碼模擬器和物理機(jī)需要打開(kāi) App 內(nèi)開(kāi)關(guān)(WebView 調(diào)試開(kāi)關(guān));

  • PC 瀏覽器定位元素

  • Chrome 瀏覽器-62版本才可以更好的看見(jiàn) webview 的內(nèi)部,其他的版本都有一些 bug;

  • 換成 chromium 瀏覽器可以避免很多坑,展示效果和速度要比 chrome 要快;

  • 代碼

  • 有的設(shè)備可以使用 find_element_acessibility_id(), 不同的設(shè)備渲染的頁(yè)面不同,兼容性不適合;

  • switch_to.context() 切換不同的 context,一個(gè)頁(yè)面來(lái)說(shuō);

  • switch.to_window() 切換不同的窗口句柄,對(duì)不同的頁(yè)面來(lái)說(shuō);

更多內(nèi)容,我們?cè)诤罄m(xù)文章分享。

(文章來(lái)源于霍格沃茲測(cè)試學(xué)院)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容