• 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

    
    
  • 相关阅读:
    vue3 + ts+ axios +vite
    用查询批量导入EXCEL 时,获取EXCEL 表sheet名称
    DataTable中,标题Caption,ColumnName的区别
    查询语句,从2张表中,根据共同字段,如果其他两个指定字段值不相等,则,显示两个值
    springboot 工程出现 socket hang up
    springboot工程引用本地lib文件夾内的jar包,打包時把依賴jar包打入jar包
    kubernetes常用命令1命令补全
    kubernetes常用命令2巡检集群状态
    BigDecimal数字计算工具类
    Nginx+RTMP HLS降低延迟参数
  • 原文地址:https://www.cnblogs.com/finer/p/14811683.html
Copyright © 2020-2023  润新知