Airtest之iOS API汇总

上期回顾:Airtest之安卓API汇总


2024.2.25更新:新增剪切板、Airtest1.3.3touch/swipe支持绝对坐标和相对坐标

2023.9.3更新:Airtest1.3.0.1新增iOS设备相关接口

 

以下基于airtest1.2.0
Airtest核心API文件路径:
your_python_path/site-packages/airtest/core/api.py
iOS API文件路径:
your_python_path/site-packages/airtest/core/ios/ios.py

ios.py中定义了一个IOS类,继承自Device基类,Device类里只有方法名定义,具体实现是在各个设备类里,比如苹果设备的实现就是在ios.py中的IOS类里。
初始化方法里定义了很多类变量,比如cap方式、_is_pad、info(设备信息)等,这个自己看代码吧,下面我们重点介绍一下iOS类里都有哪些方法。

def __init__(self, addr=DEFAULT_ADDR):
    super(IOS, self).__init__()

    # if none or empty, use default addr
    self.addr = addr or DEFAULT_ADDR

    # fit wda format, make url start with http://
    # eg. http://localhost:8100/ or http+usbmux://00008020-001270842E88002E
    if not self.addr.startswith("http"):
        self.addr = "http://" + addr

    """here now use these supported cap touch and ime method"""
    self.cap_method = CAP_METHOD.WDACAP
    self.touch_method = TOUCH_METHOD.WDATOUCH
    self.ime_method = IME_METHOD.WDAIME

    # wda driver, use to home, start app
    # init wda session, updata when start app
    # use to click/swipe/close app/get wda size
    wda.DEBUG = False
    self.driver = wda.Client(self.addr)

    # record device's width
    self._size = {'width': None, 'height': None}
    self._current_orientation = None
    self._touch_factor = None
    self._last_orientation = None
    self._is_pad = None
    self._device_info = {}

    info = self.device_info
    self.instruct_helper = InstructHelper(info['uuid'])
    # start up RotationWatcher with default session
    self.rotation_watcher = RotationWatcher(self)
    self._register_rotation_watcher()

    self.alert_watch_and_click = self.driver.alert.watch_and_click

在细讲之前,让我们再来回顾一下iOS设备的链接方法:airtest之使用tidevice工具轻松连接iOS

ios.py中方法详细说明

1.decorator_retry_session
装饰器,当因为session失效而操作失败时,尝试重新获取session,最多重试3次


2.decorator_retry_for_class
装饰器,为IOS类里的所有method添加装饰器 decorator_retry_session


以下为IOS类中内容

3.uuid
类属性

返回
连接字符串,如http+usbmux://231ad3452c53a702

实际案例

auto_setup(__file__, devices=["ios:///http+usbmux://231ad21953be1eaf92"])
print(G.DEVICE.uuid)

4.is_pad
类属性,判断是否是ipad(或 6P/7P/8P),如果是,在横屏+桌面的情况下,坐标需要切换成竖屏坐标才能正确点击(WDA的bug)

返回
是返回True,否返回False


5.device_info
类属性,获取设备信息

返回:

dict for device info,
eg. AttrDict({
    'timeZone': 'GMT+0800',
    'currentLocale': 'zh_CN',
    'model': 'iPhone',
    'uuid': '90CD6AB7-11C7-4E52-B2D3-61FA31D791EC',
    'userInterfaceIdiom': 0,
    'userInterfaceStyle': 'light',
    'name': 'iPhone',
    'isSimulator': False})

6.window_size()
返回窗口大小

返回
namedtuple:
   Size(wide , hight)

实际案例

auto_setup(__file__, devices=["ios:///http+usbmux://231ad21953be1eaf92"])
print(G.DEVICE.window_size())

7.orientation
return device oritantation status in LANDSACPE POR
类属性,返回设备方向状态

返回:    
竖屏返回PORTRAIT,横屏返回LANDSCAPE


8.get_orientation()
self.driver.orientation只能拿到LANDSCAPE,不能拿到左转/右转的确切方向
因此手动调用/rotation获取屏幕实际方向

返回:    
摄像头朝左的横屏返回LANDSCAPE
摄像头朝右的横屏返回UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT


9.display_info
类属性,显示信息

返回:    
{'width': 640, 'height': 1136, 'orientation': 'PORTRAIT', 'physical_width': 640, 
'physical_height': 1136, 'window_width': 320, 'window_height': 568}


10.touch_factor
类属性
use session.scale can get UIKit scale factor
so self._touch_factor = 1 / self.driver.scale, but the result is incorrect on some devices(6P/7P/8P)

返回:    
self._touch_factor = float(self._size['window_height']) / float(height)

 


11.get_render_resolution()
返回旋转后的渲染分辨率

返回:    
(offset_x, offset_y, offset_width and offset_height of the display)
如(0, 0, 640, 1136)


12.get_current_resolution()
返回当前分辨率

返回:
如:
竖屏(640, 1136)
横屏(1136, 640)


13.home()
点击home键


14.snapshot(filename=None, strType=False, quality=10, max_size=None)
截屏

参数:    
filename: 截图文件名称
quality: 截图质量,范围为[1, 99]
max_size: 限制图片最大尺寸,如1200

返回:    
display the screenshot

示例:
详见Airtest核心API汇总


15.touch(pos, duration=0.01)
点按

参数:    
pos: 坐标(x, y), 可以是相对坐标或绝对坐标
duration(optional): 点按持续时间

返回:    
None

示例:

touch((100, 100))  # 点击绝对坐标(100,100)
touch((0.5, 0.5), duration=1)  # 点相对坐标(0.5,0.5),屏幕中心

16.double_click(pos)
双击

参数:    
pos: 坐标(x, y)


17.swipe(fpos, tpos, duration=0, *args, **kwargs)
滑动

参数:    
fpos – start point,绝对/相对坐标(x, y)
tpos – end point,绝对/相对坐标(x, y)
duration (float) – start coordinate press duration (seconds), default is 0

返回:    
None

示例:

swipe((1050, 1900), (150, 1900))  # 绝对坐标滑动
swipe((0.2, 0.5), (0.8, 0.5))  # 相对坐标滑动

18.keyevent(keyname, **kwargs)
在设备上执行keyevent,只支持home/volumeUp/volumeDown

参数:    
keyname – home/volumeUp/volumeDown

示例:

keyevent("volumeUp")  # 音量增加
keyevent("volumeDown")  # 音量减少

19.text(text, enter=True)
输入文本

参数:
text:  要输入的文本
enter: 是否按回车

返回:    
None

示例:

text("qasite")  # 输入“qaiste”并回车
text("测试工程师小站", enter=False)  # 输入"测试工程师小站"

20.start_app(package, *args)
启动应用

参数:
package: the app bundle id, e.g com.apple.mobilesafari

返回:    
None

示例:

start_app('com.apple.mobilesafari')

21.stop_app(package)
停止应用

参数:    
package: the app bundle id, e.g com.apple.mobilesafari

返回:    
None

示例:

stop_app('com.apple.mobilesafari')

22.app_state(package)
获取应用当前状态

参数:    
package: the app bundle id, e.g com.apple.mobilesafari

返回:    
1(not running) 2(running in background) 3(running in foreground) 4(running)
我个人理解running in foreground是指有UI界面的app被HOME后,但仍在运行;running in background应该是本身就是以服务的形式在后台启动运行的0
如:

{
    "value": 4,
    "sessionId": "0363BDC5-4335-47ED-A54E-F76A65"
}

示例:

dev = device()
start_app("com.apple.mobilesafari")
sleep(2.0)
print("此时的包体状态为:"+str(dev.app_state("com.apple.mobilesafari")["value"]))

home()
sleep(2.0)
print("此时的包体状态为:"+str(dev.app_state("com.apple.mobilesafari")["value"]))

stop_app("com.apple.mobilesafari")
sleep(2.0)
print("此时的包体状态为:"+str(dev.app_state("com.apple.mobilesafari")["value"]))

输出:
此时的包体状态为:4
此时的包体状态为:3
此时的包体状态为:1

23.app_current()
返回当前运行的应用。可能在某些型号设备无效

返回:AttrDict

示例:

dev = device()
start_app("com.apple.mobilesafari")
print(dev.app_current())

keyevent("HOME")
sleep(1.0)
print(dev.app_current())

输出:
AttrDict({'processArguments': {'env': {}, 'args': []}, 'name': 'AX error -25205', 'pid': 226, 'bundleId': 'com.apple.mobilesafari'})
AttrDict({'processArguments': {'env': {}, 'args': []}, 'name': 'AX error -25205', 'pid': 58, 'bundleId': 'com.apple.springboard'})

24.is_locked()
判断设备是否锁屏。可能在某些型号设备无效

返回:    
锁屏返回True,没锁返回False


25.unlock()
解锁设备、解锁屏幕、按2下HOME键。可能在某些型号设备无效

返回:    
None


26.lock()
设备锁屏。可能在某些型号设备无效

返回:    
None

 


27.alert_accept()
点击有2个按钮的弹窗的右边的按钮。可能在某些型号设备无效
对于拥有2个按钮的iOS弹窗来说,一般情况下,确认按钮都在右边,所以alert_accept会点击右边的按钮。这只是一个方便使用的接口,不一定适用于所有的情况,如果遇到点击情况不符合预期,可以用按指定按钮名字来点击的接口alert_click()

返回:    
None


28.alert_dismiss()
点击有2个按钮的弹窗的左边的按钮。可能在某些型号设备无效
对于拥有2个按钮的iOS弹窗来说,一般情况下,取消按钮都在左边,所以alert_dismiss会点击左边的按钮。这只是一个方便使用的接口,不一定适用于所有的情况,如果遇到点击情况不符合预期,可以用按指定按钮名字来点击的接口alert_click()

返回
None


29.alert_wait(time_counter=2)
判断X秒内弹窗是否出现。可能在某些型号设备无效

参数:    
time_counter – 等待时间,默认2秒

返回:    
X秒内弹窗出现返回True,否则False


30.alert_buttons()
获取弹窗按钮文本。可能在某些型号设备无效

返回:    
如,("设置", "好")


31.alert_exists()
判断弹窗是否存在。可能在某些型号设备无效

返回:    
True or False


32.alert_click(buttons)
点击弹窗的指定按钮。可能在某些型号设备无效

参数:
buttons:按钮列表,如['设置', '允许', '好']

返回:    
按顺序查找弹窗是否有列表中的按钮,有则点击第一个匹配的;没有则报错

示例:

dev = device()
dev.alert_click(['允许','好'])

33.home_interface()
判断是否在HOME页。可能在某些型号设备无效

返回:    
True or False


34.alert.text
返回弹窗上的提示文字(非按钮)

示例:

dev = device()
print(dev.driver.alert.text)

35.alert_watch_and_click(buttons: Optional[list] = None,interval: float = 2.0)
监控弹窗出现并且点击指定按钮

参数
buttons: 要点击的按钮列表,如["确定", "允许", "以后"]
interval: 检查间隔,默认2秒

示例:

from airtest.core.ios.ios import IOS, wda
ios = IOS("http://localhost:8100/")

# 默认情况下监控此类弹窗:["使用App时允许", "好", "稍后", "稍后提醒", "确定", "允许", "以后"]
with ios.alert_watch_and_click():
    sleep(5)

# 监控指定弹窗出现并点击
with ios.alert_watch_and_click(["Cancel"]):
    sleep(5)

# 设置监控的时间间隔为2.0s
with ios.alert_watch_and_click(interval=2.0):
    sleep(5)

这里为什么要用with呢,因为其实现啊,看看源码:

# your_python_path/site-packages/wda/__init__.py
@contextlib.contextmanager
def watch_and_click(self,
                    buttons: Optional[list] = None,
                    interval: float = 2.0):
    """ watch and click button
    Args:
        buttons: buttons name which need to click
        interval: check interval
    """
    if not buttons:
        buttons = self.DEFAULT_ACCEPT_BUTTONS

    event = threading.Event()

    def _inner():
        while not event.is_set():
            try:
                alert_buttons = self.buttons()
                logger.info("Alert detected, buttons: %s", alert_buttons)
                for btn_name in buttons:
                    if btn_name in alert_buttons:
                        logger.info("Alert click: %s", btn_name)
                        self.click(btn_name)
                        break
                else:
                    logger.warning("Alert not handled")
            except WDARequestError:
                pass
            time.sleep(interval)

    threading.Thread(name="alert", target=_inner, daemon=True).start()
    yield None
    event.set()

新手的你可能还有疑问,那就了解一下with和yield吧,另外也去了解一下threading。
python的with用法Python yield 使用浅析


 

36.install_app(file_or_url)
从本地或网络安装IPA包

参数

file_or_url - 本地路径或网址

示例
# 支持ipa包安装
install_app(r"D:\demo\qasite.ipa") 
# 也支持通过下载链接安装APP
install_app("http://www.example.com/test.ipa") 

 


37.uninstall_app(bundle_id)

卸载安装包

示例
# 参数为Bundle ID即iOS包的包名
uninstall_app("com.qasite.wechat") 

 


38.list_app(type="user")

返回应用列表

参数:
type – 可输入参数"user", "system", "all"

 

返回:
应用列表

 

示例:

dev = device()
user_app = dev.list_app("user")
print(f"user_app:\n{user_app}")

 


39.set_clipboard(content, wda_bundle_id=None)

设置剪贴板内容

参数:
content - 要设置的内容wda_bundle_id – 当手机上装有多个WDA时才用,一般用不上

示例:

set_clipboard("qasite")

 


40.get_clipboard(wda_bundle_id=None)

获取剪贴板内容

 

参数:
wda_bundle_id – 当手机上装有多个WDA时才用,一般用不上

 

示例:

text = get_clipboard()
print(f'剪贴板内容:{text}')

 


41.get_ip_address()

获取手机IP

 


42.device_status()

获取设备状态

 

示例:

dev = device()
print(dev.device_status())


输出:{'message': 'WebDriverAgent is ready to accept commands', 'state': 'success', 'os': {'testmanagerdVersion': 18, 'name': 'iOS', 'sdkVersion': '15.4', 'version': '11.1.2'}, 'ios': {'ip': '192.168.1.114'}, 'ready': True, 'build': {'time': 'Oct 24 2022 10:57:05', 'productBundleIdentifier': 'com.facebook.WebDriverAgentRunner'}, 'sessionId': 'EEC9B3F-7761-BF91-F5976E9FD35'}


 

43.tidevice相关接口
Airtest1.3.0.1新增封装了tidevice功能接口

 

  • devices :列出USB连接的所有设备的 UDID 列表

 

from airtest.core.ios.ios import TIDevice

udid = TIDevice.devices()
print(f"TIDevice.devices():\n{udid}")

 

输出

['f83a2d08deb8c22ce6338e35328f5cfcaaf5d3f4']

如果是要查看当前连接的iOS设备,直接这样获取即可:print(dev.udid)

 

  • list_app :列出手机上安装的应用列表,支持对类型进行筛选,包括 user/system/all
    Airtest接口中已有一样的API,用那个就行了

  • list_wda :列出手机上安装的所有WDA的 bundleID
    一般我们也不会同时装好多个wda吧,基本不用

  • device_info :获取手机信息

print(f"TIDevice.device_info():\n{TIDevice.device_info(udid)}")
#  输出
{'productVersion': '15.7.3', 'productType': 'iPhone9,2', 'modelNumber': 'MNRL2', 'serialNumber': 'F2LSGNHG52', 'timeZone': 'Asia/Shanghai', 'uniqueDeviceID': 'f83a2d08deb8caaf5d3f4', 'marketName': 'iPhone 7 Plus'}

 

  • install_app :安装ipa包,支持本地路径或URL
    Airtest接口中已有一样的API,用那个就行了

  • uninstall_app:卸载 bundle_id 对应的包体
    Airtest接口中已有一样的API,用那个就行了

  • start_app :启动 bundle_id 对应的包体
    Airtest接口中已有一样的API,用那个就行了

  • stop_app :停止 bundle_id 对应的包体
    Airtest接口中已有一样的API,用那个就行了

  • ps :获取当前的进程列表
    想获取app包名时,也可以用此方法

print(f"TIDevice.ps():\n{TIDevice.ps(udid)}")
# 输出
[{'pid': 4848, 'name': 'StoreKitUIService', 'bundle_id': 'com.apple.ios.StoreKitUIService', 'display_name': 'iTunes'}, {'pid': 217, 'name': 'Spotlight', 'bundle_id': 'com.apple.Spotlight', 'display_name': '搜索'}, {'pid': 5176, 'name': 'TestFlight', 'bundle_id': 'com.apple.TestFlight', 'display_name': 'TestFlight'}, {'pid': 7699, 'name': 'AppStore', 'bundle_id': 'com.apple.AppStore', 'display_name': 'App Store'}, {'pid': 5050, 'name': 'SafariViewService', 'bundle_id': 'com.apple.SafariViewService', 'display_name': 'Safari浏览器'}, {'pid': 6800, 'name': 'Preferences', 'bundle_id': 'com.apple.Preferences', 'display_name': '设置'}]

 

  • ps_wda :获取当前启动中的WDA列表
    暂时没想到使用场景

  • xctest:启动WDA

import threading
import time
from airtest.core.ios.ios import TIDevice

wda_bundle_id = TIDevice.list_wda(self.udid)[0]
# 创建一个线程,执行xctest
t = threading.Thread(target=TIDevice.xctest, args=(self.udid, wda_bundle_id), daemon=True)
t.start()
time.sleep(5)
ps_wda = TIDevice.ps_wda(self.udid)
print(ps_wda)
self.assertIn(wda_bundle_id, ps_wda)
time.sleep(5)
# 终止线程
t.join(timeout=3)

 

 

---------------------------------------------------------------------------------

关注微信公众号即可在手机上查阅,并可接收更多测试分享~

posted @ 2021-09-20 21:03  ☆星空物语☆  阅读(961)  评论(0编辑  收藏  举报