https://github.com/openatx/facebook-wda
前置条件
- 安装
- 安装依赖
- brew install carthage
- brew install node
- brew install libimobiledevice --HEAD
- 克隆wda代码
- git clone https://github.com/appium/WebDriverAgent
- 初始化wda
- cd ./WebDriverAgent
- ./Scripts/bootstrap.sh
- 打开Xcode
- 方法1:在WebDriverAgent文件下打开 WebDriverAgent.xcodeproj 文件
- 方法2:直接打开xcode,选择WebDriverAgent文件
- 配置xcode
- 配置WebDriverAgentLib和WebDriverAgentRunner
- 参考:https://testerhome.com/topics/7220
- 账号:apple id
- 选择WebDriverAgentRunner和设备
- 无报错提示后command + U运行
- 手机会安装wda无图标APP,首次安装,需要进行信任
- 如果更改了build号,需要先删除手机旧wda
- iproxy 8100 8100 # 端口转发
- 检查wda环境
- 浏览器输入localhost:8100/status,出现josn格式返回 (需先接口转发)
- 获取控件元素
- 使用atx,同Android的uiautomator2
- python3 -m weditor
- 每次使用
- 开启Xcode
- 选择手机,(product-destination)
- command + U(product-test)运行成功(bulid success 然后自动手机装wda程序)
- 如果提示报错,需要手机信任(设置-通用-描述文件与设备管理)
- 控制台运行 iproxy 8100 8100 (端口转化,港版的手机不需要)
- 控制台运行 python3 -m weditor (开启weditor,使用http://localhost:8100连接)
配置
- import wda
- wda.DEBUG = False # 默认为false
- wda.HTTP_TIMEOUT = 60.0 # 默认为60s
连接手机
- 创建一个连接
- 使用Client
- 先引用 import wda
- c = wda.client('http://localhost:8100’)
- c = wda.client()
- 不设置参数,则默认从 $DEVICE_URL 获取,
- 如果$DEVICE_URL为空,则默认为http://localhost:8100
- 如果连接出错,会报wda.WDAError错误
- 使用USBClient
- USBClient继承自Client
- USBClient通过unix:/var/run/usbmuxd连接到wda-server服务
- import wda
- 当只有一个设备连接时,可以不用填参数
- 支持指定设备的udid和wda的端口号
- c = wda.USBClient(“539c5fffb18f2be0bf7f771d68f7c327fb68d2d9”, port=8100)
- 支持通过device_url访问
- c = wda.Client(“usbmux://{udid}:8100”.format(udid=“539c5fffb18f2be0bf7f771d68f7c327fb68d2d9”))
Client
- 显示状态
- 等待wda准备好
- c.wait_ready(timeout=300) # 等待300s,不设置默认120s
- c.wait_ready(timeout=300, noprint=True) # 安静的等待,无进度输出
- 按home键
- 健康检查
- c.healthcheck() (health-健康)
- 获取页面源 (source-资源)
- c.source() # 格式为xml
- c.source(accessible=True) # 默认为false ,格式为json
- 锁定屏幕
- c.locked() #检查屏幕锁定状态 返回 true或false
- c.lock() #锁定屏幕
- c.unlock() #解锁屏幕
- 截图
- c.screenshot(’’)
- 参数为图片命名
- c.screenshot(“screen.png”)
- c.screenshot(‘screen.jpg’) # 不推荐使用
- 如果想截图为jpg格式,可以先转化为PIL.Image,然后另存为jpg格式
- c.screenshot().save(“screen.jpg”) (save-保存)
- 其他
- c.appium_settings() # 获取appium的配置
- c.appium_settings({“mjpegServerFramerate”: 20}) # 修改配置
Session
- 从0.7.0版本开始,所有的Session方法都移动至Client类,现在的Session是Client的别名
- 打开APP
- 参数为包名
- 方法1
- 方法2
- s = c.session(‘com.cctv.yangshipin.app.iphone’)
- print(s.orientation)
- s.close()
- 打开手机safari浏览器的指定页面
- 第一个参数为Safari浏览器包名,第二个参数为指定的页面
- s = c.session(‘com.apple.mobilesafari’, [’-u’, 'https://www.baidu.com’])
- print(s.orientation)
- s.close()
- 其他Session操作
- 详见https://github.com/appium/WebDriverAgent
- 显示当前应用信息
- c.app_current() (current-当前)
- 在wda中自动处理报警
- 在启动APP时,增加alert_actio参数
- 参数值为"accept"或者 “dismiss” (accept-接受)(dismiss-关闭)
- s = c.session(‘com.cctv.yangshipin.app.iphone’, alert_action='accept’)
- 在不终止应用的情况下,再次启动APP
- c.session().app_activate(“com.apple.Health”)
- 同:app_launch() (activate-启用) (launch-启动)
- 终止APP
- c.session().app_terminate("com.apple.Health”) (terminate-终止)
- 获取应用状态
- c.session().app_state(“com.apple.Health”)
- 返回value值:1-未启动 2-在后台 4-运行中
Session 操作 (operations-操作)
- 前置
- s = c.session(‘com.cctv.yangshipin.app.iphone’)
- 设置默认元素超时30s
- s.implicitly_wait(30.0) (implicitl-隐含的)
- 返回当前的bundleId和sessionId
- 按home键
- s.home() # 同 c.home() # 有的没有这个对象
- s.press("home”) # 比较快
- 锁屏-同Client下的锁屏
- s.lock() # 锁屏
- s.unlock() #解锁
- s.locked() #检查屏幕锁定状态 返回 true或false
- 返回电池和设备信息
- s.battery_info() # return like {“level”: 1, “state”: 2} (battery-电池)
- s.device_info() # return like {“currentLocale”: “zh_CN”, “timeZone”: “Asia/Shanghai”}
- 更新剪切板
- s.set_clipboard(“Hello world”) (clipboard-剪切板)
- s.get_clipboard() 不存在这个方法
- 截图
- s.screenshot().save("s.png”)
- 有时屏幕截图旋转是错误的,但是我们可以将其旋转到正确的方向
- from PIL import Image (transpose-转置) (rotate-旋转)
- s.screenshot().transpose(Image.ROTATE_90).save("correct.png”) # 旋转90度截图
- 方向
- 打印方向
- 旋转方向
- s.orientation = wda.LANDSCAPE # 还有很多方向
- 停止APP一段时间
- s.deactivate(5.0) # 停止5s (deactivate-停用)
- 获取屏幕的宽、高
- print(s.window_size()) # 返回Size(width=414, height=896)
- 获取UIKit比例因子,第一次需要大约1s,下次使用缓存的值
- print(s.scale) (scale-比例)
- 模拟触摸
- 点击
- 非常类似tap(),但是支持float和int参数
- s.click(200, 200)
- s.click(0.5, 0.5) # click center of screen
- s.click(0.5, 200)
- 双击
- 拖动
- s.swipe(x1, y1, x2, y2, 0.5) # 从x拖动的y,时长为0.5s
- s.swipe(0.5, 0.5, 0.5, 1.0) # 从中间滑动到下方
- s.swipe_left()
- s.swipe_right()
- s.swipe_up()
- s.swipe_down()
- 长按
- s.tap_hold(x, y, 1.0) # 长按1s
- 隐藏键盘
- 在模拟器中不生效
- s.keyboard_dismiss()
- 调节音量
- s.press(“volumeUp”)
- s.press(“volumeDown”)
元素查找
- 如果找不到元素,会引发WDAElementNotFoundError错误
- 检测是否存在
- s(id=“URL”).exists # 返回true或false
- 常规属性检索
- 使用id查找
- 使用classname查找
- 使用name
- s(name=‘URL’)
- s(nameContains=‘UR’) # 模糊匹配 (Contains-包含)
- s(nameMatches=".RL") # 匹配正则表达式 (Matches-匹配)
- 使用label (label-标签)
- s(label=“label”)
- s(labelContains=“URL”) # 模糊匹配
- 使用value
- s(value=“Enter”)
- s(valueContains=“RL”)
- 使用是否可见、是否启用 (visible-可见)
- s(visible=True, enabled=True)
- 使用索引,索引必须至少结合标签,值等。
- 结合搜索条件,以上的属性可组合
- s(className=‘Button’, name=‘URL’, visible=True, labelContains=“Addr”)
- xpath检索
- 方式一:
- s(xpath=’//Button[@name=“URL”]’)
- 方式二:
- s.xpath(’//Button[@name=“URL”]’)
- predicate (predicate-使以…为依据)
- s(predicate= 'name LIKE “UR*” ')
- s('name LIKE “UR*” ')
- predicate是第一个参数,没有该参数默认为 predicate= is ok
- s(classChain=’**/Button[
name == "URL"
]’)
元素行为(tap, scroll, set_text等)
- 获取元素信息
- 前提
- e = s(text=‘Dashboard’).get(timeout=10.0)
- 获取元素属性
- e.className
- e.name
- e.visible
- e.value
- e.label
- e.text
- e.enabled
- e.displayed
- e.bounds (bounds-范围)
- 搜索元素,然后点击
- e = s(text=‘Dashboard’).get(timeout=10.0)
- 当元素在10s内找到时,返回元素对象,否则报WDAElementNotFoundError错误
- s(text='Dashboard’) 是选择器
- e 是元素对象
- e.top() # 点击元素
- 使用python的magic技巧,实现搜索元素点击
- 使用magic函数的 _getattr_ 方法 ,可以不加.get()
- s(text=‘Dashboard’).tap() # 同 s(text=‘Dashboard’).get().tap()
- 注意:无法对get属性使用magic技巧
- 即,访问属性,必须使用get(),不能省略
- s(text=‘Dashboard’).get().value
- s(text=‘Dashboard’).value # 用法不对,总是返回none
- 判断元素存在再点击
- s(text=‘Dashboard’).click_exists() #如果没有找到,则立刻返回
- s(text=‘Dashboard’).click_exists(timeout=5.0) # 等待5s
- 其他元素操作
- 检查元素是否存在
- print(s(text=“Dashboard”).exists)
- 查找所有匹配的元素,返回元素数组
- s(text=‘Dashboard’).find_elements()
- 使用查找到的第二个元素
- s(text=‘Dashboard’)[1].exists
- 使用child查找子元素
- s(text=‘Dashboard’).child(className=‘Cell’).exists
- 设置默认超时10s
- 做元素操作
- e.tap()
- e.click() # tap的别名
- e.clear_text()
- e.set_text(“Hello world”)
- e.tap_hold(2.0) # 按住2s
- 滚动元素,使其可见
- e.scroll() (scroll-滚动)
- 方向可以为:“up”, “down”, “left”, “right”
- 根据方向滚动,滚动距离默认为其高度或宽度
- 设置文字
- e.set_text(“Hello WDA”) # 正常使用
- e.set_text(“Hello WDA\n”) # 使用enter发送文本
- e.set_text("\b\b\b") # 删除3个字符
- 等待元素消失
- s(text=‘Dashboard’).wait_gone(timeout=10.0)
- 滑动
- s(className=“Image”).swipe(“left”) # 与scroll()类似
- Pinch (scale-捏)
- s(className=“Map”).pinch(2, 1) # scale=2, speed=1
- s(className=“Map”).pinch(0.1, -1) # scale=0.1, speed=-1
- 布尔属性
- e.accessible
- e.displayed
- e.enabled
- 字符串属性
- e.text # ex: Dashboard
- e.className # ex: XCUIElementTypeStaticText
- e.value # ex: github.com
- 返回元祖
- rect = e.bounds # 如果 Rect(x=144, y=28, width=88.0, height=27.0)
- rect.x # 返回 144
- Alert (Alert-警报)
- print(s.alert.exists) # 打印存在的警告
- print(s.alert.text) # 打印警告文本
- s.alert.accept() # 打印第一个警告
- s.alert.dismiss() # 打印第二个警告
- s.alert.wait(5) # 如果警报器在5s内返回true,否则返回false (默认值为20s)
- s.alert.wait() # 等待警报器2s
- s.alert.buttons() # 返回警告按钮, 例如返回: [“设置”, “好”]
- s.alert.click(“设置”)
- s.alert.click([“设置”, “信任”, “安装”]) # 放arg类型为list时,单击第一个匹配项,如果没有匹配项,则引发ValueError错误
- 警报监视器
- 设置指定的
- 默认的监控按钮
- # [“使用App时允许”, “好”, “稍后”, “稍后提醒”, “确定”, “允许”, “以后”]
- with c.alert.watch_and_click(interval=2.0): # 默认每2s检查一次
-
# ... operations
回调
- 回调操作: register_callback
- 回调函数中的参数名称可以是
- client: wda.Client
- url: str, eg: http://localhost:8100/session/024A4577-2105-4E0C-9623-D683CDF9707E/wda/keys
- urlpath: str, eg: /wda/keys (without session id)
- with_session: bool #如果网址包含id
- method: str, eg: GET
- response: dict # 仅回调 .HTTP_REQUEST_AFTER
- err: WDAError # 仅Callback.ERROR
def _cb(client: wda.Client, url: str):
if url.endswith("/wda/keys"):
print(“send_keys called”)
c.register_callback(wda.Callback.HTTP_REQUEST_BEFORE, _cb)
c.send_keys(“Hello”)
# 取消注册
c.unregister_callback(wda.Callback.HTTP_REQUEST_BEFORE, _cb)
c.unregister_callback(wda.Callback.HTTP_REQUEST_BEFORE) # ungister all
c.unregister_callback() # unregister all callbacks
- 支持的回调有
- wda.Callback.HTTP_REQUEST_BEFORE
- wda.Callback.HTTP_REQUEST_AFTER
- wda.Callback.ERROR
- 默认代码内置了两个回调函数 wda.Callback.ERROR,使用c.unregister_callback(wda.Callback.ERROR)可以去掉这两个回调
- 当遇到invalid session id错误时,更新session id并重试
- 当遇到设备掉线时,等待wda.DEVICE_WAIT_TIMEOUT时间 (当前是30s,以后可能会改的更长一些)
Ios常见应用包名(build id)
iMovie
com.apple.iMovie
Apple Store
com.apple.AppStore
Weather
com.apple.weather
相机Camera
com.apple.camera
iBooks
com.apple.iBooks
Health
com.apple.Health
Settings
com.apple.Preferences
Watch
com.apple.Bridge
Maps
com.apple.Maps
Game Center
com.apple.gamecenter
Wallet
com.apple.Passbook
电话
com.apple.mobilephone
备忘录
com.apple.mobilenotes
指南针
com.apple.compass
浏览器
com.apple.mobilesafari
日历
com.apple.mobilecal
信息
com.apple.MobileSMS
时钟
com.apple.mobiletimer
照片
com.apple.mobileslideshow
提醒事项
com.apple.reminders
Desktop
com.apple.springboard (Start this will cause your iPhone reboot)
腾讯QQ
com.tencent.mqq
微信
com.tencent.xin
部落冲突
com.supercell.magic
钉钉
com.laiwang.DingTalk
Skype
com.skype.tomskype
Chrome
com.google.chrome.ios