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