用途和特點(diǎn)
- 自動化完成一些重復(fù)性的任務(wù),比如微信客服機(jī)器人
- 爬蟲自動化爬取信息,為什么不通過網(wǎng)頁、HTTP爬取呢?有的系統(tǒng)產(chǎn)品沒有網(wǎng)頁端PC端只有app端
- 自動化測試,便于測試人員回歸部署驗(yàn)證等測試
Appium自動化方案的特點(diǎn)
那么多自動化app的工具為什么選則appium?
- 開源免費(fèi)
- 支持多個平臺(android、IOS)
- 支持多種類型自動化:支持蘋果安卓原生界面的自動化,支持應(yīng)用內(nèi)嵌WebView的自動化支持手機(jī)瀏覽器中的web網(wǎng)站自動化支持flutter應(yīng)用的自動化
- 支持多種編程語言
自動化原理
android版

IOS版

appium工作原理

大家看看這幅圖, 包含了 3個主體部分 : 自動化程序、Appium Server、移動設(shè)備
自動化程序
自動化程序是由我們來開發(fā)的,實(shí)現(xiàn)具體的 手機(jī)自動化 功能。
要發(fā)出具體的指令控制手機(jī),也需要使用 客戶端庫。
和Selenium一樣,Appium 組織 也提供了多種編程語言的客戶端庫,包括 java,python,js, ruby等,方便不同編程語言的開發(fā)者使用。
我們需要安裝好客戶端庫,調(diào)用這些庫,就可以發(fā)出自動化指令給手機(jī)。
Appium Server
Appium Server 是 Appium 組織開發(fā)的程序,它負(fù)責(zé)管理手機(jī)自動化環(huán)境,并且轉(zhuǎn)發(fā) 自動化程序的控制指令 給 手機(jī),并且轉(zhuǎn)發(fā) 手機(jī)給 自動化程序的響應(yīng)消息。
手機(jī)設(shè)備
我們這里說的手機(jī)設(shè)備,其實(shí)不僅僅是手機(jī),包括所有 蘋果、安卓的移動設(shè)備,比如:手機(jī)、平板、智能手表等。
為了直觀方便的講解,這里我們簡稱: 手機(jī)
當(dāng)然手機(jī)上也包含了 我們要自動化控制的 手機(jī)應(yīng)用APP。
手機(jī)設(shè)備為什么能 接收并且處理自動化指令呢?
因?yàn)椋珹ppium Server 會在手機(jī)上 安裝一個 自動化代理程序, 代理程序會等待自動化指令,并且執(zhí)行自動化指令
比如:要模擬用戶點(diǎn)擊界面按鈕,Appium 自動化系統(tǒng)的流程是這樣的:
自動化程序 調(diào)用客戶端庫相應(yīng)的函數(shù), 發(fā)送 點(diǎn)擊元素 的指令(封裝在HTTP消息里)給 Appium Server
Appium Server 再轉(zhuǎn)發(fā)這個指令給 手機(jī)上的自動化代理
手機(jī)上的自動化代理 接收到 指令后,調(diào)用手機(jī)平臺的自動化庫,執(zhí)行點(diǎn)擊操作,返回點(diǎn)擊成功的結(jié)果給 Appium Server
Appium Server 轉(zhuǎn)發(fā)給 自動化程序
自動化程序了解到本操作成功后,繼續(xù)后面的自動化流程
其中,自動化代理控制,使用的什么庫來實(shí)現(xiàn)自動化的呢?
如果測試的是蘋果手機(jī), 用的是蘋果的 XCUITest 框架 (IOS9.3版本以后)
如果測試的是安卓手機(jī),用的是安卓的 UIAutomator 框架 (Android4.2以后)
這些自動化框架提供了在手機(jī)設(shè)備上運(yùn)行的庫,可以讓程序調(diào)用這些庫,像人一樣自動化操控設(shè)備和APP,比如:點(diǎn)擊、滑動,模擬各種按鍵消息等。
環(huán)境搭建
安裝包在為了方便我放在阿里云盤上面了是地址:https://sharelinkpre.rongdasoft.com/share-link/index.html?q=158e744d8a404000
步驟如下
- 安裝client編程庫
Python語言開發(fā)則使用pip install appium-python-client
使用java語言開發(fā)則使用:導(dǎo)jar包方式進(jìn)行也可以使用maven進(jìn)行下載
<!-- https://mvnrepository.com/artifact/io.appium/java-client -->
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>6.1.0</version>
</dependency>
安裝appium Server
appium Server是用nodejs運(yùn)行的,基于js開發(fā)出來的.Appium組織為了方便大家安裝使用制作了一個可執(zhí)行程序appiumDesktoop
把nodejs運(yùn)行環(huán)境,appiumServer和一些工具打包在了里面了,只需要簡單的下載安裝就可以了
「Appium-windows-1.15.1.exe」https://www.aliyundrive.com/s/vqSgSGpw78y安裝AndroidJDK
百度androidSDK進(jìn)行下載,下載完畢后配置ANDROID_HOME,配置adb命令monkey命令等地址即可安裝javaSDK
需要用到androidSDK而androidSDK需要用到JAVA的JDK環(huán)境.所以在這里也需要安裝一下java的JDK
訪問地址:配置JAVA_HOME和bin和jre\bin即可:「jdk-8u211-windows-x64.exe」https://www.aliyundrive.com/s/FSMV3bbxCvd連接手機(jī)/模擬器
上述環(huán)境都準(zhǔn)備好,要自動化手機(jī)app需要,在你運(yùn)行程序的電腦上用USB線或者wifi連接上你的安卓手機(jī),進(jìn)入手機(jī)設(shè)置->關(guān)于手機(jī),不斷點(diǎn)擊版本號菜單,推出上級菜單,就會出現(xiàn)一個開發(fā)者模式設(shè)置按鈕進(jìn)入后啟動USB調(diào)試即可
連接好以后輸入adb devices -l 命令來列出連接在電腦上的安卓設(shè)備.,點(diǎn)擊回車如果輸出一下內(nèi)容則表示連接成功可以進(jìn)行自動化了
注意:輸入這個可以獲取當(dāng)前正在運(yùn)行app的報(bào)名和activity: adb shell dumpsys window | findstr mCurrentFocus
快速入門
from appium import webdriver
from selenium.webdriver.common.by import By
from appium.webdriver.extensions.android.nativekey import AndroidKey
desired_caps = {
'platformName': 'Android', # 被測手機(jī)是安卓
'platformVersion': '8', # 手機(jī)安卓版本
'deviceName': 'xxx', # 設(shè)備名,安卓手機(jī)可以隨意填寫
'appPackage': 'tv.danmaku.bili', # 啟動APP Package名稱
'appActivity': '.ui.splash.SplashActivity', # 啟動Activity名稱
'unicodeKeyboard': True, # 使用自帶輸入法,輸入中文時填True
'resetKeyboard': True, # 執(zhí)行完程序恢復(fù)原來輸入法
'noReset': True, # 不要重置App
'newCommandTimeout': 6000,
'automationName' : 'UiAutomator2'
# 'app': r'd:\apk\bili.apk',
}
# 連接Appium Server,初始化自動化環(huán)境
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
# 設(shè)置缺省等待時間
driver.implicitly_wait(5)
# 如果有`青少年保護(hù)`界面,點(diǎn)擊`我知道了`
iknow = driver.find_elements(By.ID, "text3")
if iknow:
iknow.click()
# 根據(jù)id定位搜索位置框,點(diǎn)擊
driver.find_element(By.ID, 'expand_search').click()
# 根據(jù)id定位搜索輸入框,點(diǎn)擊
sbox = driver.find_element(By.ID, 'search_src_text')
sbox.send_keys('開心的')
# 輸入回車鍵,確定搜索
driver.press_keycode(AndroidKey.ENTER)
# 選擇(定位)所有視頻標(biāo)題
eles = driver.find_elements(By.ID, 'title')
for ele in eles:
# 打印標(biāo)題
print(ele.text)
input('**** Press to quit..')
driver.quit()
appium2的find_element寫法
注意:Appium Python 現(xiàn)在已經(jīng)升級到 2.x 大版本,依賴 Selenium 4 以后, 下面這種 find_element_by* 方法都作為過期不贊成的寫法
driver.find_element_by_id('username').send_keys('byhy')
運(yùn)行會有告警,都要寫成下面這種格式
from selenium.webdriver.common.by import By
wd.find_element(By.ID, 'username').send_keys('byhy')
而后續(xù)還有 Appium獨(dú)有的查找方式,比如
driver.find_element_by_accessibility_id('byhy')
driver.find_element_by_android_uiautomator(code)
要改成
from appium.webdriver.common.appiumby import AppiumBy
driver.find_element(AppiumBy.ACCESSIBILITY_ID, 'byhy')
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, code)
# 這樣也可以根據(jù)ID
wd.find_element(AppiumBy.ID, 'username').send_keys('byhy')
定位元素
從示例代碼,大家就可以發(fā)現(xiàn),和Selenium Web自動化一樣,要操作界面元素,必須先 定位(選擇)元素。
Appium是基于Selenium的,所以 和 Selenium 代碼 定位元素的 基本規(guī)則相同:
- find_element_by_XXX 方法,返回符合條件的第一個元素,找不到拋出異常
- find_elements_by_XXX 方法,返回符合條件的所有元素的列表,找不到返回空列表
- 通過 WebDriver 對象調(diào)用這樣的方法,查找范圍是整個界面
- 通過 WebElement 對象調(diào)用這樣的方法,查找范圍是該節(jié)點(diǎn)的子節(jié)點(diǎn)
界面元素查看工具
做 Selenium Web 自動化的時候,要找到元素,我們是通過瀏覽器的開發(fā)者工具欄來查看元素的特性,根據(jù)這些特性(屬性和位置),來定位元素
Appium 要自動化手機(jī)應(yīng)用,同樣需要工具查看界面元素的特征。
常用的查看工具是: Android Sdk包中的 uiautomateviewer 和 Appium Desktop 中的 Appium Inspector
- uiautomateviewer:安卓查看APP界面元素,最常用的就是 Android SDK 中的工具 uiautomateviewer ,它在SDK目錄目錄 的 tools\bin 目錄中
和Selenium一樣,我們要定位選擇元素,也是根據(jù)元素的特征,包括
元素的屬性
元素的相對位置(相對父元素、兄弟元素等) - Appium Inspector:Appium Desktop 中的 Appium Inspector 也可以查看元素。它的一個優(yōu)點(diǎn)是可以直接驗(yàn)證 選擇表達(dá)式是否能定位到元素
- 根據(jù)ID
在Selenium Web自動化教程里,我們說過,如果能根據(jù)ID選擇定位元素,最好根據(jù)ID,因?yàn)橥ǔ碚fID是唯一的,所以根據(jù)ID選擇 效率高。
在安卓應(yīng)用自動化的時候,同樣可以根據(jù)ID查找。
但是這個ID ,是安卓應(yīng)用元素的 resource-id 屬性:
如:resource-id com.phi.letter.letterphi:id/data_title
其中data_title就是該元素的ID
from appium.webdriver.common.appiumby import AppiumBy
eles = driver.find_elements(AppiumBy.ID, 'data_title')
- 根據(jù)CLASS NAME
安卓界面元素的 class屬性 其實(shí)就是根據(jù)元素的類型,類似web里面的tagname, 所以通常不是唯一的。
通常,我們根據(jù)class 屬性來選擇元素, 是要選擇多個而不是一個。
當(dāng)然如果你確定 要查找的 界面元素的類型 在當(dāng)前界面中只有一個,就可以根據(jù)class 來唯一選擇。
使用如下代碼
from appium.webdriver.common.appiumby import AppiumBy
driver.find_elements(AppiumBy.CLASS_NAME,'android.widget.TextView')[3].click()
- 根據(jù)ACCESSIBILITY ID
元素的 content-desc 屬性是用來描述該元素的作用的。
如果要查詢的界面元素有 content-desc屬性,我們可以通過它來定位選擇元素。
使用如下代碼
from appium.webdriver.common.appiumby import AppiumBy
driver.find_element(AppiumBy.ACCESSIBILITY_ID, '找人')
- Xpath
Appium 也支持通過 Xpath選擇元素。
但是其可靠性和性能不如 Selenium Web自動化。因?yàn)閃eb自動化對Xpath的支持是由瀏覽器實(shí)現(xiàn)的,而Appium Xpath的支持是 Appium Server實(shí)現(xiàn)的。
畢竟,瀏覽器產(chǎn)品的成熟度比Appium要高很多。
當(dāng)然,Xpath是標(biāo)準(zhǔn)語法,所以這里表達(dá)式的語法規(guī)則和 以前學(xué)習(xí)的Selenium里面Xpath的語法是一樣的,比如
from appium.webdriver.common.appiumby import AppiumBy
driver.find_element(AppiumBy.XPATH, '//ele1/ele2[@attr="value"]')
注意:
selenium自動化中, xpath表達(dá)式中每個節(jié)點(diǎn)名是html的tagname。
但是在appium中, xpath表達(dá)式中 每個節(jié)點(diǎn)名 是元素的class屬性值。
比如:要選擇所有的文本節(jié)點(diǎn),就使用如下代碼
# 定位text
driver.find_element_by_xpath("http://*[@text='掃一掃']").click()
# 定位 resource-id
driver.find_element_by_xpath("http://*[@resource-id='com.taobao.taobao:id/tv_scan_text']").click()
# 也可以聯(lián)合@resource-id屬性和@text文本屬性來下定位
driver.find_element_by_xpth("http://*[@resource-id='com.taobao.taobao:id/tv_scan_text'][@text='掃一掃']").click()
# 定位搜索框 //class屬性
driver.find_element_by_xpath("http://android.widget.EditText").click()
# 定位搜索框 //*[@class='class屬性']
driver.find_element_by_xpath("http://*[@class='android.widget.EditText']").click()
dd=driver.find_element(AppiumBy.XPATH,'//androidx.recyclerview.widget.RecyclerView/android.view.ViewGroup[1]/android.widget.TextView[1]')# xpath是從1開始的
print(dd.text)
contains模糊定位
# contains匹配text
driver.find_element_by_xpath('//*[contains(@text, "注冊/登錄")]').click()
time.sleep(3)
# contains匹配textcontent-desc
driver.find_element_by_xpath("http://*[contains(@content-desc, '幫助')]").click()
安卓UIAutomator
根據(jù)id,classname, accessibilityid,xpath,這些方法選擇元素,其實(shí)底層都是利用了安卓 uiautomator框架的API功能實(shí)現(xiàn)的。
參考 這里的谷歌安卓官方文檔介紹:https://developer.android.google.cn/training/testing/ui-automator
也就是說,程序的這些定位請求,被Appium server轉(zhuǎn)發(fā)給手機(jī)自動化代理程序,就轉(zhuǎn)化為為uiautomator里面相應(yīng)的定位函數(shù)調(diào)用。
其實(shí),我們的自動化程序,可以直接告訴 手機(jī)上的自動化代理程序,讓它 調(diào)用UI Automator API的java代碼,實(shí)現(xiàn)最為直接的自動化控制。
主要是通過 UiSelector 這個類里面的方法實(shí)現(xiàn)元素定位的,比如
code = 'new UiSelector().text("業(yè)務(wù)").className("android.widget.TextView")'
ele=driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, code)
ele.click()
就是通過 text 屬性 和 className的屬性 兩個條件 來定位元素
UiSelector里面有些元素選擇的方法 可以解決 前面解決不了的問題。
比如
text 方法
可以根據(jù)元素的文本屬性查找元素
textContains
根據(jù)文本包含什么字符串
textStartsWith
根據(jù)文本以什么字符串開頭
textmartch 方法
可以使用正則表達(dá)式 選擇一些元素,如下
code = 'new UiSelector().textMatches("^我的.*")'
UiSelector 的 instance 和 index 也可以用來定位元素,都是從0開始計(jì)數(shù), 他們的區(qū)別:
instance是匹配的結(jié)果所有元素里面 的第幾個元素
index則是其父元素的幾個節(jié)點(diǎn),類似xpath 里面的*[n]
UiSelector 的 childSelector 可以選擇后代元素,比如
code = 'new UiSelector().resourceId("tv.danmaku.bili:id/recycler_view").childSelector(new UiSelector().className("android.widget.TextView"))'
ele = driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, code)
注意: childSelector后面的引號要框住整個 子 uiSelector 的表達(dá)式
目前有個bug:只能找到符合條件的第一個元素,參考appium 在github上的 issues:
https://github.com/appium/java-client/issues/150
界面操作
- click點(diǎn)擊
最常見的操作之一,使用 WebElement 對象的 click 方法 - tap點(diǎn)按
WebElement 對象的 tap 方法和 click 類似,都是點(diǎn)擊界面。
但是最大的區(qū)別是, tap是 針對坐標(biāo) 而不是針對找到的元素。
為了保證自動化代碼在所有分辨率的手機(jī)上都能正常執(zhí)行,我們通常應(yīng)該使用click方法。
但有的時候,我們難以用通常的方法定位元素, 可以用這個tap方法,根據(jù)坐標(biāo)來點(diǎn)擊
既然tap是用坐標(biāo)來點(diǎn)擊界面的,我們怎么知道這個元素的坐標(biāo)呢?
大家還記得用inspect 查看該元素的屬性中,有一個 bounds 屬性嗎?
它就是表示元素的左上角,右下角坐標(biāo)的 坐標(biāo)。
我們還可以使用 UIAutomatorviewer 直接光標(biāo)移動,看右邊的屬性提示。
tap 方法可以像這樣進(jìn)行調(diào)用
driver.tap([(850,1080)],300)
它 有 兩個參數(shù):
第一個參數(shù)是個列表,表示點(diǎn)擊的坐標(biāo)。
注意最多可以有5個元素,代表5根手指點(diǎn)擊5個坐標(biāo)。所以是list類型。
如果我們只要模擬一根手指點(diǎn)擊屏幕,list中只要一個元素就可以了
第二個參數(shù) 表示 tap點(diǎn)按屏幕后 停留的時間。
如果點(diǎn)按時間過長,就變成了長按操作了。 - 輸入
使用 WebElement 對象的 send_keys 方法 - 滑動
我們做移動app測試的時候,經(jīng)常需要滑動界面。
怎么模擬滑動呢? WebDriver對象的 swipe方法,就提供了這個功能
比如
driver.swipe(start_x=x, start_y=y1, end_x=x, end_y=y2, duration=800)
前面4個參數(shù) 是 滑動起點(diǎn) 和 終點(diǎn) 的x、y坐標(biāo)。
第5個參數(shù) duration是滑動從起點(diǎn)到終點(diǎn)坐標(biāo)所耗費(fèi)的時間。
注意這個時間非常重要,在屏幕上滑動同樣的距離,如果時間設(shè)置的很短,就是快速的滑動。
比如:一個翻動新聞的界面,快速的滑動,就會是掃動的動作,會導(dǎo)致內(nèi)容 慣性 滾動很多。 - 按鍵
前面的示例代碼中已經(jīng)使用過 調(diào)用 press_keycode 方法,就能模擬 按鍵動作,包括安卓手機(jī)的實(shí)體按鍵和 鍵盤按鈕。
from appium.webdriver.extensions.android.nativekey import AndroidKey
# 輸入回車鍵,確定搜索
driver.press_keycode(AndroidKey.ENTER)
按鍵的定義,可以參考這篇文檔 https://github.com/appium/python-client/blob/master/appium/webdriver/extensions/android/nativekey.py
- 長按\雙擊\移動
Appium的 TouchAction 類提供了更多的手機(jī)操作方法,比如:長按、雙擊、移動
from appium.webdriver.common.touch_action import TouchAction
# ...
actions = TouchAction(driver)
actions.long_press(element)
actions.perform()
- 查看通知欄
打開通知欄
安卓手機(jī), 查看通知欄的動作可以是從屏幕頂端下滑來查看通知。
我們剛剛學(xué)過滑動,感興趣的朋友可以自己試試,關(guān)鍵是要找對滑動的起始點(diǎn)和滑動距離。
更方便的,我們可以使用如下代碼,直接打開通知欄
driver.open_notifications()
或者使用滑動
driver.swipe(start_x=10, start_y=0, end_x=10, end_y=1000, duration=500)
收起通知欄
收起通知欄,可以使用前面介紹的模擬按鍵, 發(fā)出返回按鍵 的方法
driver.back()
driver.keyevent(4)
我們自動化過程中,可能需要截屏手機(jī),并且下載到指定目錄中,就可以在我們的Python程序中這樣寫
import os
os.system('adb shell screencap /sdcard/screen3.png && adb pull /sdcard/screen3.png')
特別是,還可以通過adb 使用 am(activity manager) 和pm (package manager) 兩個工具, 可以啟動 Activity、強(qiáng)行停止進(jìn)程、廣播 intent、修改設(shè)備屏幕屬性、列出應(yīng)用、卸載應(yīng)用等。
關(guān)閉APP
使用 driver.terminate_app(appPackage) 即可
內(nèi)嵌網(wǎng)頁自動化
- 內(nèi)嵌網(wǎng)頁的混合App
很多移動App 都是 Hybrid(混合) 應(yīng)用。
混合應(yīng)用主要是指 它的一部分是原生界面和代碼,而另一部分是內(nèi)嵌網(wǎng)頁 。
現(xiàn)在基本上需要打開網(wǎng)頁瀏覽的app都是 混合app,比如微信、支付寶等。
微信的sms界面是原生代碼實(shí)現(xiàn)的,而打開某個朋友圈,或者別人發(fā)來的的鏈接部分則是 web部分。
App中的內(nèi)嵌的展示網(wǎng)頁內(nèi)容的模塊,我們稱之為 webview 。
我們自動化的時候如果需要操作內(nèi)嵌webview中的網(wǎng)頁內(nèi)容,該怎么做呢? - 修改編譯App
前面講過,Appium 的原則是不修改應(yīng)用本身,就可以對應(yīng)用進(jìn)行自動化。
但是,這里要違背一下 appium的原則。
要對App內(nèi)嵌網(wǎng)頁進(jìn)行自動化,首先要請 開發(fā)人員修改源代碼,保證對webview 對象加入setWebContentsDebuggingEnabled 的調(diào)用。
安卓應(yīng)用,修改java代碼,如下所示:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 找到 webview 對象
WebView myWebView = (WebView) findViewById(R.id.jcywebview);
// 添加打開 webview內(nèi)容debug開關(guān)
myWebView.setWebContentsDebuggingEnabled(true);
};
然后,編譯出一個支持自動化的版本。
否則無法對webview中的內(nèi)容進(jìn)行自動化操作。
- 用Chrome查看App內(nèi)嵌網(wǎng)頁
編譯產(chǎn)生了 支持自動化 的App,接下來我們就可以安裝到手機(jī)上,進(jìn)行內(nèi)嵌網(wǎng)頁的自動化了。
App內(nèi)嵌網(wǎng)頁的自動化,也是使用 Selenium。 和電腦上自動化Chrome瀏覽器本質(zhì)是一樣的。
因?yàn)?安卓手機(jī)內(nèi)嵌webview 基本上就是 手機(jī)版的 Chrome瀏覽器 。
但是,因?yàn)榫W(wǎng)頁內(nèi)容在手機(jī)中呈現(xiàn),不能像電腦瀏覽器那樣,打開開發(fā)者工具,查看元素內(nèi)容。
那我們怎么查看元素的屬性特征,來定位選擇元素呢?
這里面要分兩種情況:
webview不依賴App環(huán)境
這種情況:App的內(nèi)嵌 webview 和 native部分沒有交互 的。
webview 只是打開一個固定的網(wǎng)址(一般是該公司的手機(jī)網(wǎng)址)而已。
這種情況,我們要查看webview內(nèi)容非常簡單。
因?yàn)槠鋵?shí)和手機(jī)沒有關(guān)系。
直接用Chrome瀏覽器F12里面的手機(jī)模式打開對應(yīng)的網(wǎng)頁,即可。-
webview依賴App環(huán)境
首先,確保我們的應(yīng)用運(yùn)行,然后應(yīng)用訪問webview頁面,打開電腦瀏覽器地址欄輸入 chrome://inspect ,出現(xiàn)如下所示界面
image.png(括號中說的是webview版本號)
webview版本也比較老的,就會有問題, 盡量使用新手機(jī)進(jìn)行自動化
點(diǎn)擊 inspect即可查看
webview自動化代碼 和 電腦上瀏覽器的自動化 基本差不多。
但是有一點(diǎn)要注意:
手機(jī)App中 webview里面的網(wǎng)頁內(nèi)容, 是在一個獨(dú)立于應(yīng)用native部分的環(huán)境里面的。
而缺省情況下,find_element_by_xxx 這樣的代碼選擇元素, Appium 只會在 native 部分的界面尋找元素。 肯定找不到元素。
Appium 把一個界面環(huán)境 稱之為一個 context 。
native 部分的 context 名字為 NATIVE_APP , 而webview部分的context則為 WEBVIEW_XXX (XXX部分是 應(yīng)用的 app package名)
我們怎么查看當(dāng)前有哪些context呢?
我們的代碼通過 driver 對象的 contexts 屬性來獲取,也就是 driver.contexts。
driver 對象的 current_context 屬性對應(yīng)當(dāng)前的 context 對象。
大家可以打開自動化代碼, 添加如下內(nèi)容
print(driver.contexts)
執(zhí)行一下,解釋一下,可以發(fā)現(xiàn)結(jié)果如下。
['NATIVE_APP', 'WEBVIEW_stetho_com.phi.letter.letterphi']
我們的應(yīng)用中, webview 的 context 就是 WEBVIEW_stetho_com.phi.letter.letterphi
而當(dāng)前的context 是’NATIVE_APP', 所以當(dāng)前的自動操作都是在native context里面的進(jìn)行的。
要對該webview里面的網(wǎng)頁內(nèi)容進(jìn)行自動化操作,必須先將當(dāng)前的context切換為 webview的context,怎么切呢?
使用 switch_to.context
driver.switch_to.context('WEBVIEW_stetho_com.phi.letter.letterphi')
使用該語句如果webview版本和chrome驅(qū)動版本不一致則會報(bào)版本錯誤問題此時我們需要查看報(bào)錯的版本,下載對應(yīng)的chrome版本驅(qū)動包即可
版本對應(yīng)查看地址:https://raw.githubusercontent.com/appium/appium-chromedriver/master/config/mapping.json
chrome驅(qū)動包下載地址:http://chromedriver.storage.googleapis.com/index.html

先點(diǎn)擊 Advanced 設(shè)置項(xiàng)

然后在 下圖位置 寫上你的 老版本的 chromedriver 的路徑

好,現(xiàn)在我們修改代碼,如下所示
from selenium.webdriver.common.by import By
driver.switch_to.context('WEBVIEW_com.example.jcy.wvtest')
driver.find_element(By.ID, 'index-kw').send_keys('開心的小哈')
driver.find_element(By.ID, 'index-bn').click()
執(zhí)行一下,發(fā)現(xiàn)可以自動化了
那么怎么切換回 native app 進(jìn)行自動化呢?
當(dāng)然是 繼續(xù)使用 switch_to.context ,如下
driver.switch_to.context('NATIVE_APP')
手機(jī)瀏覽器網(wǎng)頁自動化
有的公司開發(fā)了手機(jī)版網(wǎng)站,直接用手機(jī)瀏覽器打開的,比如,xiaomi 京東等。
并不是 手機(jī)App
這種手機(jī)網(wǎng)頁,我們怎么 程序自動化呢?
首先,必須在手機(jī)上安裝谷歌瀏覽器。
以百度為例,
首先啟動 Appium Desktop。
然后,我們的自動化程序代碼如下所示:
from appium import webdriver
from selenium.webdriver.common.by import By
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '7'
desired_caps['deviceName'] = 'test'
desired_caps['browserName'] = 'Chrome'
desired_caps['newCommandTimeout'] = 6000
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
driver.implicitly_wait(10)
driver.get('http://www.baidu.com')
driver.find_element(By.ID, 'index-kw').send_keys('開心的小哈')
driver.find_element(By.ID, 'index-bn').click()
driver.quit()
更多了解:AndroidViewClient UiAutomator2
在使用過程中出現(xiàn)的問題
- 點(diǎn)擊web頁面元素時如果使用ID,NAME定位結(jié)果出現(xiàn)
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.InvalidArgumentException: Message: invalid argument: invalid locator
(Session info: chrome=88.0.4324.93)
Stacktrace:
Backtrace:
GetHandleVerifier [0x000BB963+483]
GetHandleVerifier [0x000BB941+449]
GetHandleVerifier [0x00463308+3832712]
GetHandleVerifier [0x004863A7+3976231]
GetHandleVerifier [0x004A8822+4116642]
GetHandleVerifier [0x0049B43A+4062394]
GetHandleVerifier [0x004A7159+4110809]
GetHandleVerifier [0x0049B2EB+4062059]
GetHandleVerifier [0x0047ED14+3945876]
GetHandleVerifier [0x0047FBCE+3949646]
GetHandleVerifier [0x0047FB59+3949529]
Ordinal0 [0x0007B5CC+46540]
Ordinal0 [0x00079F53+40787]
Ordinal0 [0x00079B12+39698]
GetHandleVerifier [0x00381468+2907368]
GetHandleVerifier [0x001C71EE+1096302]
GetHandleVerifier [0x00183E8D+821005]
GetHandleVerifier [0x0018396B+819691]
GetHandleVerifier [0x00183881+819457]
GetHandleVerifier [0x001AF463+998627]
BaseThreadInitThunk [0x76B4FA29+25]
RtlGetAppContainerNamedObjectPath [0x77B57A7E+286]
RtlGetAppContainerNamedObjectPath [0x77B57A4E+238]
(No symbol) [0x00000000]
問題原因?qū)е?如果 chromedriver 在 W3C 模式下工作,則會出現(xiàn)這種情況。W3C標(biāo)準(zhǔn)只聲明CSS和XPATH定位器,其中id和名稱的位置已被刪除為過時,因?yàn)镃SS涵蓋了它們。
有三種可能的解決方法:
- 更新不受支持的定位器
- 將 chromedriver 強(qiáng)制執(zhí)行 JSONWP 模式(將 chromedriver 選項(xiàng)設(shè)置為w3cfalse)
- 將過時的定位器類型傳遞給客戶端代碼中的轉(zhuǎn)換方法,該方法會自動將它們升級到CSS定位器(這是Selenium lib當(dāng)前正在做的事情)
其中第二個需要在開啟時配置
desired_caps['chromeOptions'] = {'w3c': False}
desired_caps['showChromedriverLog'] = data['showChromedriverLog']
即可;
