第34讲:更好用的自动化工具 airtest 的使用
在上一節(jié)課我們了解了 Appium 的用法,利用 Appium 可以方便地完成 App 的自動化控制,但在使用過程中或多或少還會有些不方便的地方,比如響應(yīng)速度慢,提供的 API 功能有限等。
本課時我們再介紹另外一個更好用的自動化測試工具,叫作 airtest,它提供了一些更好用的 API,同時提供了非常強大的 IDE,開發(fā)效率和響應(yīng)速度相比 Appium 也有提升。
1.Airtest 概況
AirtestProject 是由網(wǎng)易游戲推出的一款自動化測試框架,項目構(gòu)成如下。
- Airtest:是一個跨平臺的、基于圖像識別的 UI 自動化測試框架,適用于游戲和 App,支持平臺有 Windows、Android 和 iOS,基于 Python 實現(xiàn)。
- Poco:是一款基于 UI 控件識別的自動化測試框架,目前支持 Unity3D/cocos2dx/Android 原生 App/iOS 原生 App/微信小程序,也可以在其他引擎中自行接入 poco-sdk 來使用,同樣是基于 Python 實現(xiàn)的。
- AirtestIDE:提供了一個跨平臺的 UI 自動化測試編輯器,內(nèi)置了 Airtest 和 Poco 的相關(guān)插件功能,能夠使用它快速簡單地編寫 Airtest和 Poco代碼。
- AirLab:真機自動化云測試平臺,目前提供了 TOP100 手機兼容性測試、海外云真機兼容性測試等服務(wù)。
- 私有化手機集群技術(shù)方案:從硬件到軟件,提供了企業(yè)內(nèi)部私有化手機集群的解決方案。
總之,Airtest 建立了一個比較完善的自動化測試解決方案,利用 Airtest 我們自然就能實現(xiàn) App 內(nèi)可見即可爬的爬取。
2.本節(jié)內(nèi)容
本節(jié)我們會簡單介紹 Airtest IDE 的基本使用,同時介紹一些 Airtest 和 Poco 的基本 API 的用法,最后我們以一個實例來實現(xiàn) App 的模擬和爬取。
這里使用的平臺還是安卓平臺,請確保現(xiàn)在你準備好了一臺安卓的手機或模擬器。
3.Airtest 的安裝
在 Airtest 的官方文檔中已經(jīng)詳細介紹了 Airtest 的安裝方式,包括 AirtestIDE、Airtest Python 庫、Poco Python 庫。
如果我們只使用 AirtestIDE 來實現(xiàn)自動化模擬和數(shù)據(jù)爬取的話是沒問題的,因為它里面已經(jīng)內(nèi)置了 Python、 Airtest Python 庫、Poco Python 庫。
AirtestIDE 提供了非常便捷的可視化點選和代碼生成等功能,你沒有任何 Python 代碼基礎(chǔ)的話,僅僅使用 AirtestIDE 就可以完成 App 的自動化控制和數(shù)據(jù)的爬取了。
但是對于大量數(shù)據(jù)的爬取和頁面跳轉(zhuǎn)控制這樣的場景來說,如果僅僅依靠可視化點選和自動生成的代碼來進行 App 的自動化控制,其實是不靈活的。
進一步地,如果我們再加上一些代碼邏輯的話,比如一些流程控制、循環(huán)控制語句,我們就可以實現(xiàn)批量數(shù)據(jù)的爬取了,這時候我們就需要依賴于 Airtest、Poco 以及一些自定義邏輯和第三方庫來實現(xiàn)了。
所以,這里建議同時安裝 AirtestIDE、Airtest、Poco。
AirtestIDE 的安裝方式參見鏈接:https://airtest.doc.io.netease.com/tutorial/1_quick_start_guide/。
Airtest 的安裝命令如下:
pip3 install airtestPoco 的安裝命令如下:
pip3 install pocoui安裝完成之后,可以在 AirtestIDE 中把 Python 的解釋器更換成系統(tǒng)的 Python 解釋器,而不再是 AirtestIDE 內(nèi)置的 Python 解釋器,修改方法參見 https://airtest.doc.io.netease.com/IDEdocs/run_script/1_useCommand_runScript/。
3.AirtestIDE 體驗
在這里我以一臺安卓手機來演示 AirtestIDE 的使用。
首先參考 https://airtest.doc.io.netease.com/tutorial/1_quick_start_guide/#_4 來完成手機的連接,確保使用 adb 可以正常獲取到手機的相關(guān)信息,如:
如果能正常輸出手機相關(guān)信息,則證明連接成功,示例如下:
adb server version (40) doesn't match this client (41); killing... * daemon started successfully List of devices attached 6T9DYHNNDMUC8LBI device這里就能看到我的設(shè)備名稱為 6T9DYHNNDMUC8LBI。
然后啟動 AirtestIDE,新建一個腳本,界面如圖所示:
這時候在右側(cè)我們可以看到已經(jīng)連接的設(shè)備,如果沒有出現(xiàn),可以查看 https://airtest.doc.io.netease.com/IDEdocs/device_connection/2_android_faq/ 來排查一些問題。
接下來我們點擊設(shè)備列表右側(cè)的 connect 按鈕,就可以在 IDE 中看到手機的屏幕了,如圖所示。
另外可以觀察到,整個 IDE 被分成了三列。
- 左側(cè)上半部分:Airtest 輔助窗,可以通過一些點選操作實現(xiàn)基于圖像識別的自動化配置。
- 左側(cè)下半部分:Poco 輔助窗,可以通過一些點選操作實現(xiàn)基于 UI 控件識別的自動化配置。
- 中間上半部分:代碼區(qū)域,可以通過 Airtest 輔助窗和 Poco 輔助窗自動生成代碼,同時也可以自己編寫代碼,代碼是基于 Python 語言的。
- 中間下半部分:日志區(qū)域,會輸出運行時、調(diào)試時的一些日志。
- 右側(cè)部分:手機的屏幕。
在這里我們可以通過鼠標直接點觸右側(cè)部分的手機屏幕,可以發(fā)現(xiàn)真機或模擬器的屏幕也會跟著變化,而且響應(yīng)速度非常快。
接下來我們來實驗一下 Airtest 輔助器。Airtest 可以基于圖像識別來實現(xiàn)自動化控制,我們來體驗一下。
比如在這里我先點擊左側(cè)的 touch 按鈕,其含義就是點擊。這時候 AirtestIDE 會提示我們在右側(cè)屏幕截圖,比如這里我們截取“應(yīng)用商店”,這時候我們可以發(fā)現(xiàn) AirtestIDE 中便會出現(xiàn)了一行代碼。代碼的內(nèi)容為 touch,然后其參數(shù)就是一張可視化的圖片。
然后我們再選擇 wait,其含義就是等待某個內(nèi)容加載出來,同樣地進行屏幕截圖,如截取菜單欄的一部分,證明已經(jīng)成功進入了應(yīng)用商店首頁。
這里我們就通過一些可視化的配置完成了自動化的配置。
最后我們在代碼的開頭部分再加一個 keyevent,代表一些鍵盤事件,內(nèi)容如下:
keyevent("HOME")結(jié)果如下:
這樣我們就能實現(xiàn)這樣的自動化控制流程了:
- 進入手機首頁;
- 點擊“應(yīng)用商店”;
- 等待菜單內(nèi)容加載出來;
- 向上滑動屏幕。
怎么樣,是不是很簡單。如果你的手機內(nèi)容和本示例不一樣的話,可以靈活更換其中的配置內(nèi)容。
這時候,我們點擊運行按鈕,即可發(fā)現(xiàn) Airtest 便可以自動驅(qū)動手機完成一些自動化的操作了。以上便是 Airtest 提供的基于圖像識別技術(shù)的自動化控制。
但很多情況下圖像識別的速度可能不是很快,另外圖像的截圖也不一定是精確的,而且存在一定的風(fēng)險,比如有的圖像更換了,那可能就會影響自動化測試的流程。另外對于大批量的數(shù)據(jù)采集和循環(huán)控制,圖像識別也不是一個好的方案。
所以,這里再介紹一個基于 Poco 的 UI 控件自動化控制,其實說白了就是基于一些 UI 名稱和屬性的選擇器的自動化控制,有點類似于 Appium、Selenium 中的 XPath。
這里我們先點擊左側(cè) Poco 輔助窗的下拉菜單,更換到 Android,這時候 AirtestIDE 會提示我們更新代碼,點擊確定之后可以發(fā)現(xiàn)其自動為我們添加了如下代碼:
from poco.drivers.android.uiautomation import AndroidUiautomationPoco poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)這其實就是導(dǎo)入了 Poco 的 AndroidUiautomationPoco 模塊,然后聲明了一個 poco 對象。
接下來我們就可以通過 poco 對象來選擇一些內(nèi)容了。
我們此時點擊左側(cè)的控件樹,可以發(fā)現(xiàn)右側(cè)的手機屏幕就有對應(yīng)的高亮顯示,如圖所示。這就有點像瀏覽器開發(fā)者工具里面選取網(wǎng)頁源代碼,這里的 UI 控件樹就類似于網(wǎng)頁里面的 HTML DOM 樹。
接著我們點擊輔助窗的右上角的錄制按鈕,如圖所示。
錄制之后可以在右側(cè)屏幕進行一些點選或滑動的一些操作,在代碼區(qū)域就可以生成一些操作代碼,如圖所示。
這里也類似 Appium 里面錄制并生成操作代碼的過程。
比如這里經(jīng)過我的一些操作,代碼區(qū)域自動生成了如下代碼:
poco("com.xiaomi.market:id/inner_webview").swipe([0.013, -0.2461]) poco("com.miui.home:id/workspace").offspring("應(yīng)用商店").offspring("com.miui.home:id/icon_icon").click() poco("com.miui.systemAdSolution:id/view_skip").click() poco("com.xiaomi.market:id/inner_webview").swipe([0.0391, -0.3545]) poco("com.xiaomi.market:id/inner_webview").swipe([0.0807, -0.5098]) poco("com.xiaomi.market:id/inner_webview").swipe([0.0156, -0.3516]) poco("com.xiaomi.market:id/fragment_root_view").child("com.xiaomi.market:id/fragment_container").child("android.widget.LinearLayout").offspring("小米應(yīng)用商店").child("android.view.View").child("android.widget.ListView")[1].child("android.view.View")[3].child("android.view.View")[0].child("android.view.View").child("android.view.View")[1].child("android.view.View")[1].click() poco("com.xiaomi.market:id/top_bar_back_iv").click()通過這些內(nèi)容我們可以觀察到有這樣的規(guī)律:
poco 對象可以直接接收一個控件樹選擇器,然后就可以調(diào)用一些操作方法,如 swipe、click 等等完成一些操作。
另外 poco 對象還支持鏈式選擇,如 poco 對象的調(diào)用返回結(jié)果后面緊跟了 child 方法、offspring 的方法的調(diào)用,同時還支持索引選擇,其最終的返回結(jié)果依然可以調(diào)用一些操作方法,如 swipe、click 等完成一些操作。
所以,這里我們就可以初步得出如下結(jié)論:
- poco 對象支持通過傳入一些 UI Path 來進行元素選擇,最終會返回一個可操作對象。
- poco 對象返回的可操作對象支持鏈式選擇,如選擇其子孫節(jié)點、兄弟節(jié)點、父節(jié)點等等。
但其實可以觀察到現(xiàn)在利用錄制的方式自動生成的代碼并不太規(guī)范,也不太靈活。既然已經(jīng)是純編程方式實現(xiàn)自動化控制,那么我們有必要來了解下 Poco 的一些具體用法。
4.Poco
Poco 是一款基于 UI 控件識別的自動化測試框架,目前支持 Unity3D/cocos2dx/Android 原生 App/iOS 原生 App/微信小程序,同樣是基于 Python 實現(xiàn)的。
其 GitHub 地址為:https://github.com/AirtestProject/Poco。
首先可以看下 Poco 這個對象,其 API 為:
class Poco(agent, **options)一般來說我們會使用它的子類,比如安卓就會使用 AndroidUiautomationPoco 來聲明一個 poco 對象,這個就相當于手機操作的句柄,類似于是 Selenium 中的 webdriver 對象,通過調(diào)用它的一些選擇器和操作方法就可以完成手機的一些操作。
用法類似如下:
這里我們可以發(fā)現(xiàn),poco 本身就是一個對象,但它是可以直接調(diào)用并傳入 UI 控件的名稱的,這歸根結(jié)底是因為其實現(xiàn)了一個 call 方法,實現(xiàn)如下:
def __call__(self, name=None, **kw): if not name and len(kw) == 0:warnings.warn("Wildcard selector may cause performance trouble. Please give at least one condition to shrink range of results")return UIObjectProxy(self, name, **kw)可以看到其就是返回了一個 UIObjectProxy 對象,這個就對應(yīng)頁面中的某個 UI 組件,如一個輸入框、一個按鈕,等等。
接下來我們再看下 UIObjectProxy 的實現(xiàn),其文檔地址為:https://poco.readthedocs.io/en/latest/source/poco.proxy.html。
這里我們可以看到它實現(xiàn)了 getitem、iter、len 等方法,另外觀察到其還實現(xiàn)了 child、children、offspring 方法,這也就是 UIObjectProxy 可以實現(xiàn)鏈式調(diào)用和索引操作以及循環(huán)遍歷的原因。
接下來我們再介紹幾個比較常用的方法。
4.1child
選擇子節(jié)點,第一個參數(shù)是 name,即 UI 控件的名稱,如 android.widget.LinearLayout 等等,另外還可以額外傳入一些屬性來進行輔助選擇。
其返回結(jié)果同樣是 UIObjectProxy 類型。
4.2parent
選擇父節(jié)點,無需參數(shù),可以直接返回當前節(jié)點的父節(jié)點,同樣是 UIObjectProxy 類型。
4.3sibling
選擇兄弟節(jié)點,第一個參數(shù)是 name,即 UI 控件的名稱,另外還可以額外傳入一些屬性來進行輔助選擇。
其返回結(jié)果同樣是 UIObjectProxy 類型。
4.4click、rclick、double_click、long_click
點擊、右鍵點擊、雙擊、長按操作,UIObjectProxy 對象直接調(diào)用即可。其接受參數(shù) focus 指定點擊偏移位置,sleep_interval 代表點擊完成之后等待的秒數(shù)。
4.5swipe
滑動操作,其接收參數(shù) direction 代表滑動方向,focus 代表滑動焦點偏移量,duration 代表完成滑動所需時間。
4.6wait
等待此節(jié)點出現(xiàn),其接收參數(shù) timeout 代表最長等待時間。
4.7attr
獲取節(jié)點的屬性,其接收參數(shù) name 代表屬性名,如 visable、text、type、pos、size 等等。
4.8get_text
獲取節(jié)點的文本值,這個方法非常有用,利用它我們就可以獲得某個文本節(jié)點內(nèi)部的文本數(shù)據(jù)。
另外還有很多方法,這里暫時介紹這么多,更多的方法可以參考官方文檔介紹: https://poco.readthedocs.io/en/latest/source/poco.proxy.html。
5.實戰(zhàn)爬取
最后我們以一個 App 為例來完成數(shù)據(jù)的爬取。其下載地址為:https://app7.scrape.center/。
首先將 App 安裝到手機上,進行簡單的抓包發(fā)現(xiàn)其數(shù)據(jù)接口帶有加密,同時 App 的逆向分析也有一定的難度,所以這里我們來采取 Airtest 來實現(xiàn)模擬爬取。
我們的目標就是要把所有的電影名稱抓取下來,如圖所示:
整體思路如下:
- 由于存在大量相似的節(jié)點,所以需要用循環(huán)的方式來遍歷每個節(jié)點。
- 遍歷節(jié)點之后獲取到其真實的 TextView 節(jié)點,利用 get_text 方法提取文本值。
- 初始數(shù)據(jù)只有 10 條,數(shù)據(jù)的加載需要連續(xù)不斷上拉,因此需要增加滑動操作。
- 提取的數(shù)據(jù)可能有重復(fù),所以需要增加去重相關(guān)操作。
- 最后加載完畢之后,檢測數(shù)據(jù)量不再發(fā)生變化,停止抓取。
由于整體思路比較簡單,這里直接將代碼實現(xiàn)如下:
from airtest.core.api import * from poco.drivers.android.uiautomation import AndroidUiautomationPoco PACKAGE_NAME = 'com.goldze.mvvmhabit' poco = AndroidUiautomationPoco() poco.device.wake() stop_app(PACKAGE_NAME) start_app(PACKAGE_NAME) auto_setup(__file__) screenWidth, screenHeight = poco.get_screen_size() viewed = [] current_count, last_count = len(viewed), len(viewed) while True:last_count = len(viewed)result = poco('android.support.v7.widget.RecyclerView').child('android.widget.LinearLayout')result.wait(timeout=10)for item in result:text_view = item.child(type='android.widget.TextView')if not text_view.exists():continuename = text_view.get_text()if not name in viewed:viewed.append(name)print('名稱', name)current_count = len(viewed)print('開始滑動')swipe((screenWidth * 0.5, screenHeight * 0.7), vector=[0, -0.8], duration=3)print('滑動結(jié)束')sleep(5)if current_count == last_count:print('數(shù)量不再有變化,抓取結(jié)束')break整體思路如下:
- 首先在最開始的時候我們聲明了 AndroidUiautomationPoco 對象,賦值為 poco,即獲得了 App 的操作句柄。
- 接著調(diào)用了 stop_app 和 start_app 并傳入包名實現(xiàn)了 App 的重啟,確保是從頭開始抓取的。
- 接著我們定義了一個無限循環(huán),提取的是 android.support.v7.widget.RecyclerView 里面的 android.widget.LinearLayout 子節(jié)點,會一次性命中多個。
- 然后我們利用 for 循環(huán)遍歷了每個節(jié)點,獲取到了其中的 android.widget.TextView 節(jié)點,并用 get_text 提取了文本值,保存到 viewed 變量里面并去重。
- 遍歷完成一遍之后,調(diào)用 swipe 方法滑動手機,進行上拉加載,同時滑動完畢之后等待一段時間。
- 重復(fù)以上步驟,直到 viewed 的數(shù)量不再變化,終止抓取。
運行如上代碼便可以發(fā)現(xiàn)控制臺輸出了如下結(jié)果:
名稱 霸王別姬 名稱 這個殺手不太冷 名稱 肖申克的救贖 名稱 泰坦尼克號 名稱 羅馬假日 名稱 唐伯虎點秋香 名稱 亂世佳人 名稱 喜劇之王 名稱 楚門的世界 開始滑動 滑動結(jié)束 名稱 獅子王 名稱 V字仇殺隊 開始滑動 滑動結(jié)束 名稱 少年派的奇幻漂流 名稱 美麗心靈 名稱 初戀這件小事 名稱 借東西的小人阿莉埃蒂 名稱 一一 ...最后所有的電影名稱就被我們提取出來了。
6.總結(jié)
以上我們便講解了 AirtestIDE、Airtest、Poco 的基本用法,并用它們來完成了一個 App 數(shù)據(jù)的簡單爬取。
總結(jié)
以上是生活随笔為你收集整理的第34讲:更好用的自动化工具 airtest 的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第46讲:遇到动态页面怎么办?详解渲染页
- 下一篇: 第04讲: 基础探究,Session 与