tidevice 助你在非Mac環境執行iOS自動化


前言

一直以來,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



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM