前言
一直以來,iOS自動化的實現&執行都依賴 Mac 系統,其主要原因是因為需要通過 xcodebuild 編譯&安裝 WDA (WebDriverAgent) 到 iOS 設備中,通過WDA實現對被測應用進行操作。
導致想要做iOS自動化 就必須擁有 Mac 設備的現象
常用電腦非 Mac 的同學 想要做 iOS 自動化的時候, 就需要再申請一台Mac設備 ,可能會出現資源利用不充分的情況
雲測試平台要搭建 IOS 自動化 服務環境時,也只能批量申請 Mac 設備,經費在燃燒
一個月前,阿里團隊開源了一個內部使用的 iOS自動化工具 :tidevice (https://github.com/alibaba/taobao-iphone-device
) ,讓我們可以更方便、簡單的脫離Mac的限制。
本文會和大家分享下 tidevice 調研記錄 以及 如何集成到現有方案中
tidevice 能做什么
設備信息獲取
應用安裝、卸載、啟動、停止、查看應用信息、已安裝應用列表
啟動 WebDriverAgent (不依賴 xcodebuild , 跨平台)
運行 XcTest UITest
性能數據采集
設備截圖、設備日志 ...
tidevice 核心原理
usbmux通信協議:
實現 Mac/Windows/Linux 與 iOS設備服務間的通信
Mac
-
usbmuxd 是蘋果的一個服務,這個服務主要用於在USB協議上實現多路TCP連接,將USB通信抽象為TCP通信。蘋果的iTunes、Xcode,都直接或間接地用到了這個服務。
Linux / Windows
-
本身是沒有 usbmux的,不過都有開源項目的實現,可以直接使用/參考
Windows 另外依賴 AppleApplicationSupport和AppleMobileDeviceSupport 兩個服務,安裝Itunes 環境即可安裝對應服務
usbmux 本身是socket 套接字,通過截獲、破解等手段,結合開源界的成果,用python 進行模擬,從而實現了當前工具已有的所有功能
tidevice 環境安裝
必須已有 Python 3.6+ 環境
tidevice 安裝
-
pip3 install -U "tidevice[openssl]” (推薦)
pip3 install -U tidevice (缺少設備配對功能)
usbmuxd
-
Mac 自帶:/var/run/usbmux
Linux/Windows: 參考官方建議的
https://github.com/alibaba/taobao-iphone-device/issues/7
tidevice 現支持cmd 及 Python 模擬實現
因為轉轉客戶端UI自動化框架 是Python工程,所以當發現tidevice 也是Python工程后,對tidevice功能的具體實現很感興趣,希望可以直接調用其內部實現進行集成,而不是封裝 cmd ,在調研的同時,根據轉轉側需要,進行了部分功能的重新封裝。
tidevice 支持的所有cmd 都在
tidevice.__main__
中定義實現以下 cmd 的Python實現,會直接參考/復用 main 中實現 ,大家也可以根據自己的封裝習慣,重新封裝
設備管理
查看已連接設備列表
cmd:
tidevice list
Python:
from tidevice import Usbmux
print(Usbmux().device_list())
監聽設備連接狀態
當有設備連接、斷開連接時,都會實時返回內容
cmd:
tidevice watch
Python:
from tidevice import Usbmux
for info in Usbmux().watch_device():
print(info)
Usbmux().watch_device() 返回 generator , 利用for 循環調用 ,但這里會阻塞后續邏輯,所以最好用Process 單獨監聽處理
等待任意設備連接,連接一台就結束等待
cmd:
tidevice wait-for-device
Python:
from tidevice import Usbmux
for info in Usbmux().watch_device():
if info["MessageType"] == "Attached":
break
等待指定設備連接
cmd:
tidevice -u $UDID wait-for-device
Python:
from tidevice import Usbmux
for info in Usbmux().watch_device():
if info["MessageType"] != "Attached":
continue
udid = info["Properties"]["SerialNumber"]
if udid == "$UDID":
break
打印設備日志
cmd:
tidevice -u $UDID syslog
Python:
from tidevice import Device
d = Device("udid")
d.start_service("com.apple.syslog_relay")
while True:
print(s.recv().decode("utf-8"))
應用管理
安裝應用
cmd:
tidevice --udid $UDID install example.ipa
Python:
from tidevice import Device
Device("udid").app_install(ipa_url_or_path)
卸載應用
cmd:
tidevice --udid $UDID uninstall com.example.demo
Python:
from tidevice import Device
Device("udid").app_uninstall("com.example.demo")
啟動應用
cmd:
tidevice --udid $UDID launch com.example.demo
Python:
from tidevice import Device
# 如果是已啟動&最上層應用,則不變;如果已啟動&退到后台,拉起,如果未啟動,啟動
pid = Device("udid").app_start("com.example.demo")
# 強制重新啟動
pid = Device("udid").app_start("com.example.demo", kill_running=True)
停止應用
cmd:
tidevice --udid &UDID kill com.example.demo
Python:
from tidevice import Device
# 如果傳 app_start生成的 pid ,可以直接殺掉,如果傳 com.example.demo 也可以殺掉
Device("udid").app_stop(pid_or_name)
查看已安裝應用
cmd:
tidevice —udid &UDID applist
Python:
from tidevice import Device
Instruments = Device("udid").connect_instruments()
# 設備上全部App信息列表 包含 系統應用和插件,通過 Type 可以區分App
apps = instruments.app_list()
# 只篩選用戶安裝的App列表
user_app_list = [app for app in apps if app["Type"] == "User"]
查看應用信息
名稱、版本、權限、依賴、插件 等包信息
cmd:
tidevice appinfo com.example.demo
Python:
from tidevice import Device
Device("udid").installation.lookup("com.example.demo")
通過ipa下載鏈接分析ipa信息
cmd:
tidevice parse ipa_url
Python:
import httpio
from tidevice._ipautil import IPAReader
fp = httpio.open(ork, block_size=1)
ir = IPAReader(fp)
ir.get_infoplist()
通過ipa文件分析ipa信息
cmd:
tidevice parse ipa_path
Python:
from tidevice._ipautil import IPAReader
fp = open(ipa_path, "rb")
ir = IPAReader(fp)
ir.get_infoplist()
執行自動化
執行XCTest (需要先確保手機上已經安裝有WebDriverAgent,並且知道bundleId)
cmd:
tidevice xctest -B com.facebook.wda.WebDriverAgent.Runner
Python:
from tidevice import Device
Device("udid").xctest("com.facebook.wda.WebDriverAgent.Runner")
無論是在代碼中通過cmd 執行,還是通過python庫執行xctest,都會阻塞后續操作,所以建議使用 Process
執行XCTest 修改監聽端口為8200,並顯示調試日志
cmd:
idevice XCTestCase -B com,facebook.wda.WebDriverAgent.Runner -e USB_PORT:8200 —debug
Python:
from tidevice import Device
Import logging
logger = logging.getLogger("tidevice.xctest")
Device("udid").xctest("com.facebook.wda.WebDriverAgent.Runner", log=logger, evn={"USB_PORT": 8200})
Relay 轉發請求到手機,類似於iproxy
cmd:
tidevice relay 8100 8100
Python:
from tidevice import Device
from tidevice._relay import relay
d = Device("udid")
relay(d, 8100, 8100)
同執行xctest ,無論是在代碼中通過cmd執行,還是通過python庫執行relay, 都會阻塞后續操作,所以建議使用Process
Relay 轉發請求並把傳輸的內容用hexdump的方法打印出來
cmd:
tidevice relay -x 8100 8100
Python:
from tidevice import Device
from tidevice._relay import relay
d = Device("udid")
relay(d, 8100, 8100, debug=True)
運行XCTest 並在PC上監聽8200端口轉發到手機8100服務
cmd:
tidevice wdaproxy -B com.facebook.wda.WebDriverAgent.Runner —port 8200
Python:
-
方式1:
tidevice.__main__
中cmd_wdaproxy方式
from tidevice._wdaproxy import WDAService
from tidevice import Device
d = Device("udid")
serv = WDAService(d, "com.facebook.wda.WebDriverAgent.Runner")
cmd = [sys,executable, "-m" , "tidevice" , "-u" d.udid, "relay", "8200", "8100"]
p = subprocess.Popen(cmd, stdout=sys.stfout, stderr=sys.stderr)
serv.start()
while serv._service.running:
time.sleep(1)
p and p.terminate()
serv.stop()
方式2:通過 xctest relay 結合
from tidevice import Device
from tidevice._relay import relay
from multiprocessing import Process
d = Device("udid")
p1 = Process(target=device.xctest, args=("com.facebook.wda.WebDriverAgent.Runner", None, None, {"USB_PORT":8200}))
p2 = Process(target=relay, args=(d, 8200, 8100, False))
p1.start()
p2.start()
..
p1.teminate()
p2.terminate()
運行后,可以通過 http://127.0.0.1:8200/status
判斷
運行XCTest UITest
cmd:
tidevice xctest —bundle-id philhuang.testXCTestUITests.xcrunner —target-bundle-id philhuang.testXCTest
Python:
from tidevice import Device
d = Device("udid")
d.xctest("philhuang.testXCTestUITests.xcrunner", target_bundle_id="philhuang.testXCTest")
獲取設備信息
基礎信息
型號、設備名、系統版本、手機號、序列號、時區、藍牙地址、Wifi地址等
cmd:
tidevice info
Python:
from tidevice import Device
# 內容會比cmd 全很多, 但是需要理解每個字段的含義
Device("udid").device_info()
查看設備電源信息
cmd:
tidevice info --domain com.apple.mobile.battery --json
Python:
from tidevice import Device
import json
domain_info = Device("udid").device_info("com.apple.mobile.battery")
print(json.dumps(domain_info))
主要通過 --domain 參數 查看指定domain的信息
裝有libimobiledevice 的電腦,可以執行 ideviceinfo -h 查看都有哪些domain
具體的domain信息 ,可以根據需求進行封裝
系統信息
cmd:
tidevice sysinfo
Python:
from tidevice import Device
Device("udid").instruments.system_info()
電池信息
cmd:
tidevice battery
Python:
from tidevice import Device
Device("udid").get_io_power()
這里比 Device("udid").device_info("com.apple.mobile.battery")
獲取的信息更全面,后者只是基礎的當前電量和當前充電狀態信息
fps 數據采集
cmd:
tidevice dumpsfps
Python:
from tidevice import Device
d = Device("udid")
for data in d.instruments.iter_opengl_data():
if is instance(data, str):
continue
print(data["CoreAnimationFranesPerSecond"])
在 Device().instruments
中已經封裝好了部分性能數據采集方法,可以二次封裝使用
設備操作
截屏保存在當前目錄
cmd:
tidevice screenshot
Python:
from tidevice import Device
import time
filename = "screenshot_" + str(time.time()) + ".jpg"
Device("udid").screenshot().conver("RGB").save(filename)
截屏保存在指定目錄
cmd:
tidevice screenshot /xxxx/xxx/screenshot.jpg
Python:
from tidevice import Device
file_path = "/xxxx/xxx/screenshot.jpg"
Device("udid").screenshot().conver("RGB").save(file_path)
關機
cmd:
tidevice shutdown
Python:
from tidevice import Device
Device("udid").shutdown()
重啟設備
cmd:
tidevice reboot
Python:
from tidevice import Device
Device("udid").reboot()
設備文件管理
cmd:
tidevice -u $UDID fsync
Python:
from tidevice import Device
Device("udid").sync
應用文件管理
cmd:
tidevice -u $UDID fsync -B com.example.app
Python:
from tidevice import Device
Device("udid").app_sync("com.example.app")
配對設備
如果已信任的設備不建議使用, 會重新信任,如果是遠程設備,則不能自動操作信任窗口
cmd:
tidevice -u $UDID pair
Python:
from tidevice import Device
Device("udid").pair()
tidevice 集成
轉轉方案當前底層使用的是Appium框架,同類方案可以參考
集成思路
首先要提前將 WDA 安裝到 iOS設備中 並在設置中信任開發者,確保WDA可以正常啟動
將 WDA 的bundle_id (Bundle Identifier) 作為 一個配置/常量,保存在 框架工程中, tidevice wdaproxy 需要
在 通過 webdriver 啟動driver 前,通過 tidevice 的 cmd 或者 自己封裝實現的 wdaproxy 啟動 WDA & 端口轉發 ,這時候 local_port(本地端口)需要 記錄
在 通過 webdriver 啟動driver 前,修改 driver 啟動需要的配置
-
添加 webDriverAgentUrl : "http://127.0.0.1:" + str(local_port)
如果有設置 useNewWDA 為 True 的話,需要 改為 False
wdaLocalPort 也可以不設置了
使用新的配置 啟動 driver ,就可以執行測試邏輯了
所有任務執行完成后,最好主動檢查&回收 tidevice 進程
建議
如果Mac環境 建議先保留原有的流程
-
在集成后實際測試使用過程中發現,tidevice wdaproxy 方式,wda不是很穩定,偶爾會出現通信異常,重啟wda的現象,具體原因還沒有分析出來,如果后續多次驗證其穩定后,可以再根據實際需求決定是否全都使用tidevice wdaproxy
如果Mac環境保留原有的流程 , 別忘記增加 是Linux/Windows 還是 Mac 環境的判斷
總結
tidevice工具,使iOS自動化擺脫了Mac的限制,給iOS自動化方案建設更多的可能
可以在Windows 環境 通過 tidevice wdaproxy 啟動WDA, 使用Appium Client 分析頁面 & 編寫Case
可以在Linux 服務器上 通過 tidevice 搭建 iOS設備自動化 遠程執行環境
可以在Mac/Windows/Linux 通過 tidevice 采集性能數據,集成到自動化測試流程中
在看了項目源碼后,發現 tidevice 提供的 cmd 只使用到部分底層代碼實現,可以根據自己的需求,立刻封裝出更多功能,也可以參考源碼通過 usbmux 實現更多功能
最后,感謝阿里團隊開源tidevice工具,希望本篇可以幫助到大家
還在等什么,快去試用、集成、看源碼吖~
如果喜歡這篇分享的話,辛苦關注、轉發、在看、點贊、評論、贊賞吖~
end