当前位置:   article > 正文

uiautomator2使用 安卓设备的UI自动化测试_uiautomator2 测试任务平台

uiautomator2 测试任务平台

https://www.cnblogs.com/Ronaldo-HD/p/9907747.html

众所周知,安卓单台设备的UI自动化测试已经比较完善了,有数不清的自动化框架或者工具。但是介绍多设备管理的内容并不多,当手里的手机多了之后,要做自动化测试平台,这块的东西又不得不碰,摆脱USB限制,接入WiFi,才能更加自由

浅谈自动化测试工具 python-uiautomator2 · TesterHome

安装

安装 adb

windows下载安装adb(极其简单)_adb工具_x2584179909的博客

安装 uiautomator2

GitHub - openatx/uiautomator2: Android Uiautomator2 Python Wrapper

  1. # Since uiautomator2 is still under development, you have to add --pre to install the development version
  2. pip install --upgrade --pre uiautomator2
  3. # Or you can install directly from github source
  4. git clone https://github.com/openatx/uiautomator2
  5. pip install -e uiautomator2

在手机安装 ATX-agent

atx-agent是运行在设备上的驻守程序,go开发,用于保活设备上相关的服务

 usb调试

usb连手机,手机的【开发者选项】里 打开允许【USB调试

cmd 执行 adb devices,可以看到设备对应的 序列码,说明已连接上

cmd 执行 python -m uiautomator2 init。会在手机上安装两个APK:ATX-agent(小黄车)和 `com.github.uiautomator.test`(不可见)

这两个apk使用同一个证书签名的。 不可见的应用实际上是一个测试包,包含有所有的测试代码,核心的测试服务也是通过其启动的。 但是运行的时候,系统却需要那个小黄车一直在运行(在后台运行也可以)。一旦小黄车应用被杀,后台运行的测试服务也很快的会被杀掉。就算什么也不做,应用应用在后台,也会很快被系统回收掉。

至此已经可以用usb调试手机了,直接跳转到下面 安装weditor

adb wifi调试

如果想后续不用usb 而用wifi调试,还需要下面的步骤:

        很多地方都是写 u2.connect('192.168.0.100'),尝试后报RuntimeError: USB device XXXXX is offline

        首先,电脑和手机要在同一局域网内(连同一个wifi),此时电脑能ping到手机'192.168.0.100',但还不够。需要开启远程adb

        参考下文

https://www.cnblogs.com/lxmtx/p/16071938.html

        wifi连接adb需要tcpip连接模式,在数据线连接时就需要设定端口,使用如下命令(每台手机都需要这样设置一次,可以写个脚本批量设置)

cmd

>adb tcpip 5566

        手机与电脑连接同一局域网,比如手机连接wifi后ip为 192.168.0.102,使用adb连接手机设备(手机ip在wlan上能找到、atx-agent里也有写)

cmd

 >adb connect 192.168.0.102:5566

        此时adb devices,就能看到一个局域网设备

        后续才能用uiautomator2.connect()或weditor界面 操作手机

  1. import uiautomator2 as u2
  2. d = u2.connect('192.168.0.100:5566')
  3. d.app_current()

(如果是在weditor已打开的情况下配置的adb tcpip5566,则要重启weditor后才能成功connect '绿')

        然后手机上的atx-agent才会启动。(未连接电脑前,atx-agent里面的uiautomator服务是不会启动的)


使用adb conncet数周后可能又连不上

  1. >adb connect 192.168.2.80:5566
  2. cannot connect to 192.168.2.80:5566: 由于目标计算机积极拒绝,无 法连接。 (10061)

这时再接usb,即解决

>adb tcpip 5566

常用adb命令:

  1. adb kill-server //结束adb服务
  2. adb start-server //启动adb服务
  3. adb devices //获取adb设备列表
  4. adb connect xxx //创建与xxx的设备连接 connect to a device via TCP/IP [default port=5555]
  5. #如果已经kill-server,那么start-server和devices和connect的效果都是一样的,都能直接开启adb
  6. adb disconnect xxx //断开与xxx的设备连接 disconnect from given TCP/IP device [default port=5555], or all
  7. #当有其他进程正在使用这个连接时,断开将无效。如weditor依然在connect,或者cmd中还有d
  8. #kill-server也没用,weditor或cmd里一有操作就会再次启动adb
  9. [I 230717 19:47:55 page:204] Serial: android:192.168.0.100:5566
  10. * daemon not running; starting now at tcp:5037
  11. * daemon started successfully
  12. [W 230717 19:47:59 __init__:218] [pid:13800] atx-agent has something wrong, auto recovering

更多adb问题见:Android adb网络连接Offline和 adb断开连接

更多的adb指令:adb help 

  1. https://www.jianshu.com/p/63c4d5c31909
  2. >adb forward tcp:8888 tcp:9999 #执行完该命令后,转发PC机8888端口的数据到手机的9999端口
  3. 8888
  4. >adb forward --list #查看一下转发是否成功,只有通过USB成功连接了手机该命令才能成功
  5. 35b14bdb tcp:8888 tcp:9999 #可以看到转发成功
  6. >netstat -a |findstr 8888 #查看8888端口的状态
  7. TCP 127.0.0.1:8888 DESKTOP-IB06ARQ:0 LISTENING
  8. #可以看到本地的8888端口是处于LISTENING状态
  9. #确认了转发成功后,PC机作为Client端,手机作为Server端建立Socket连接,就可以进行通信了
  10. >adb forward --remove tcp:8888 #在通信完毕后,停止转发
  11. >adb forward --list #再次使用adb forward --list看不到连接就是移除成功。

wifi调试

今天看Connect to a device,又试了一下ip直连,不用adb wifi。发现可以执行

u2.connect('192.168.0.100')

但后续的代码都报USB device XXXXX is offline。。。什么鬼,成功仅限于执行上面这行

还是要先 adb connect 192.168.0.100:5566才行


安装 weditor

  • weditor 类似于uiautomatorviewer,是专门为uiautomator2开发的辅助编辑器
  1. pip install --pre --upgrade weditor
  2. 出错就
  3. git clone https://github.com/openatx/weditor
  4. pip install -e weditor

使用

启动 weditor

cmd >python -m weditor (or 直接>weditor)

如果不能成功启动,则重新

uiautomator2 init

weditor

会打开一个浏览器页面localhost:17310

17310这个端口是为了纪念,项目的创建日期 2017/03/10

weditor的使用:

Android后面填 用adb devices得到的 序列号 或 ip

【connect】如果没有绿色树叶图标则未连接上。

【静态】和【实时】。【实时】则weditor的监视窗口随时与手机屏幕保持一致,但hierarchy刷新会比较慢;【静态】则需要手动点击【dump hierarchy】,监视窗口才会读取手机当前屏幕,hierarchy刷新比较快

weditor使用过程中,手机的ATX要保持运行(后台即可),保活uiautomator,这样weditor的连接才不会中断。

测试代码可以在weditor编辑窗口写,也可以在cmd->python进python shell写。在weditor的好处是,输入d就有api提示。还可以点击监视窗口查看控件信息


可以直接在cmd使用的指令

不用进python shell. github.com/README.md#command-line

  1. screenshot: 截图
  2. $ uiautomator2 screenshot screenshot.jpg
  3. current: 获取当前包名和activity
  4. $ uiautomator2 current
  5. uninstall: 卸载 #没试过
  6. $ uiautomator2 uninstall <package-name> # 卸载一个包
  7. $ uiautomator2 uninstall <package-name-1> <package-name-2> # 卸载多个包
  8. $ uiautomator2 uninstall --all # 全部卸载
  9. stop: 停止应用
  10. $ uiautomator2 stop com.example.app # 停止一个app
  11. $ uiautomator2 stop --all # 停止所有的app #慎用

uiautomator2 api

随着版本升级,设置过期的配置时,会提示Deprecated,但是不会抛异常。

基操
  1. >adb devices
  2. >adb connect 192.168.2.80:5566
  3. >weditor
  1. # coding: utf-8
  2. #
  3. import uiautomator2 as u2
  4. d = u2.connect("--serial-here--") # 只有一个设备时可以省略参数
  5. d = u2.connect() # 一个设备时
  6. d = u2.connect("10.1.2.3:5566") # 通过设备的IP连接(需要在同一局域网且设备上的atx-agent已经安装并启动)
  7. #Retrieve the device info
  8. d.info #Get basic information, return a dictionary
  9. d.device_info #Get detailed information
  10. d.info.get('screenOn')
  11. d.window_size() #截取屏幕尺寸 return (1080, 2340)
  12. d.wait_activity(".ApiDemos", timeout=10) #return True of False
  13. #10秒在手机当前页面找到activity就返回True. activity可以在weditor找到
  14. d.serial #192.168.2.80:5566 设备序列号 或 ip
  15. d.wlan_ip #192.168.2.80

 New command timeout :若客户端在 设定时间内没有发出新命令, uiautomator 服务就会结束。

  1. d.set_new_command_timeout(300) # change to 5 minutes, unit seconds
  2. #默认3分钟
  1. d.app_current() # 获取前台应用的 packageName, activity, pid
  2. d.app_list_running() #查看所有正在运行的 app
  3. d.app_info("com.examples.demo") #查看 app 信息
  4. img = d.app_icon("com.tencent.mobileqq") # save app icon
  5. #img.save("icon.png") #这个我也不知道图片存到哪
  6. img.save("D:\username\icon.png") #如果出现SyntaxError: (unicode error),换个路径试试
  7. d.app_start('com.tencent.qqmusic') # 启动应用。不需要在当前页面寻找
  8. #d.app_start("com.example.hello_world", ".MainActivity")
  9. d.app_start("com.tencent.mobileqq",'.activity.SplashActivity')
  10. d.app_start("com.example.app", stop=True) # 启动应用前 先停止应用
  11. pid = d.app_wait("com.example.android") # 等待应用运行, return pid(int)
  12. if not pid:
  13. print("com.example.android is not running")
  14. else:
  15. print("com.example.android pid is %d" % pid)
  16. d.app_wait("com.example.android", front=True) # 等待应用前台运行
  17. d.app_wait("com.example.android", timeout=20.0) # 最长等待时间20s(默认)
  18. #相当于`am force-stop`,因此你可能丢失数据
  19. d.app_stop("com.example.app") # 停止应用。无论应用在前、后台都会被停止,但 无法杀掉后台记录。
  20. d.app_stop_all() #这条慎用。。。系统壁纸都给我重置了,输入法也要重设默认
  21. d.app_stop_all(excludes=['com.examples.demo']) # stop all app except for com.examples.demo
  22. d.app_clear("com.example.app") #清后台 equivalent to `pm clear`
  23. d.app_install('http://some-domain.com/some.apk') #安装应用
  1. #Open Scheme
  2. #等同于在cmd直接adb shell am start -a android.intent.action.VIEW -d "appname://appnamehost"
  3. d.open_url("https://www.baidu.com") #系统自带浏览器打开网页
  4. d.open_url("taobao://taobao.com") # open Taobao app
  5. d.open_url("appname://appnamehost")
  1. d.push('foo.txt的', '/sdcard/') #推送文件到手机里的 文件夹
  2. d.push('foo.txt的', '/sdcard/bar.txt') #推送 并重命名
  3. d.push('foo.sh', '/data/local/tmp/',mode = 0o755) # push and change file access mode
  4. # push fileobj
  5. with open("foo.txt", 'rb') as f:
  6. d.push(f, "/sdcard/")
  7. d.pull('/sdcard/tmp.txt', 'tmp.txt') #从设备中拉取文件:
  8. # FileNotFoundError will raise if the file is not found on the device
  1. Debug HTTP requests
  2. Trace HTTP requests and response to find out how it works.
  3. 跟踪 HTTP 请求和响应以了解其工作原理
  4. >>> d.debug=False #默认
  5. >>> d.info
  6. {'currentPackageName': 'com.android.systemui', 'displayHeight': 2182, 'displayRotation': 0, 'displaySizeDpX': 384, 'displaySizeDpY': 832, 'displayWidth': 1080, 'productName': 'OnePlus7Pro_CH', 'screenOn': False, 'sdkInt': 30, 'naturalOrientation': True}
  7. >>> d.debug = True #开启debug,会获得每次的http信息
  8. >>> d.info
  9. 22:51:54.997 $ curl -X POST -d '{"jsonrpc": "2.0", "id": "6b3c790115b09ee646131b1fb1eb1f33", "method": "deviceInfo", "params": {}}' 'http://127.0.0.1:56116/jsonrpc/0'
  10. 22:51:56.534 Response (1536 ms) >>>
  11. {"jsonrpc":"2.0","id":"6b3c790115b09ee646131b1fb1eb1f33","result":{"currentPackageName":"com.android.systemui","displayHeight":2182,"displayRotation":0,"displaySizeDpX":384,"displaySizeDpY":832,"displayWidth":1080,"productName":"OnePlus7Pro_CH","screenOn":false,"sdkInt":30,"naturalOrientation":true}}
  12. <<< END
  13. {'currentPackageName': 'com.android.systemui', 'displayHeight': 2182, 'displayRotation': 0, 'displaySizeDpX': 384, 'displaySizeDpY': 832, 'displayWidth': 1080, 'productName': 'OnePlus7Pro_CH', 'screenOn': False, 'sdkInt': 30, 'naturalOrientation': True}
  1. '''
  2. Set default element wait time, unit seconds 设置元素查找等待时间(默认20s)
  3. This function will have influence on click, long_click, drag_to, get_text, set_text, clear_text, etc.
  4. '''
  5. d.implicitly_wait(10.0) # 也可以通过d.settings['wait_timeout'] = 10.0 修改
  6. d(text="Settings").click() # if Settings button not show in 10s, UiObjectNotFoundError will raised
  7. print("wait timeout", d.implicitly_wait()) # get default implicit wait
  1. d.set_clipboard('text', 'label') #有第一个参数就够了。就是要复制到剪贴板的内容
  2. d.clipboard #查看剪贴板的内容
  3. d.screen_on() #打开屏幕
  4. d.screen_off() #关闭屏幕
  5. d.unlock() #解锁屏幕,源码就是power键+右滑,所以有锁的手机并不能解锁
  6. d.press("back") # 模拟点击返回键
  7. d.press("home") # 模拟Home键
  8. '''以下功能不同手机有区别
  9. power 锁屏键
  10. volume_up
  11. volume_down
  12. volume_mute 直接静音
  13. 菜单键: menu 一加是到壁纸设置的界面
  14. left、right、up、down 方向键可以控制写大段落文字时的光标,也可以切换app内的界面,比如微信
  15. enter 回车
  16. delete ( or del) 等于 退格键backspace
  17. recent (recent apps) 查看后台软件
  18. camera 一加手机无反应
  19. def press(self, key: Union[int, str], meta=None): 源码在uiautomator2/__init__.py#L1152
  20. 即press能接受str或int, 还能接受int+meta键同时按下,meta就是功能键,alt之类的
  21. '''
  22. #int即软键盘(拨号、打字时弹出的软键盘)上的字符 对应的keycode
  23. #keycode查询:https://developer.android.com/reference/android/view/KeyEvent.html#META_ALT_ON
  24. d.press(0x07, 0x02) # press keycode 0x07('0') with ALT(0x02),只有在拨号时能输出0,打字时无效
  25. d.press(0x07) #拨号、打字时都能输出0
  26. #我也不知道加alt有什么用。。。? 可能是为全键盘手机考虑的吧
  1. #Gesture interaction with the device
  2. d.click(x, y)
  3. d.click(10, 20) #像素
  4. d.click(0.9, 0.1) #整个屏幕的占比
  5. d.double_click(x, y)
  6. d.double_click(x, y, 0.1) # default duration between two click is 0.1s
  7. d.long_click(x, y)
  8. d.long_click(x, y, 0.5) # long click 0.5s (default)
  9. d.swipe(sx, sy, ex, ey)
  10. d.swipe(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)
  11. d.swipe(10, 20, 80, 90) # 从(10, 20)滑动到(80, 90)
  12. #滑动解锁
  13. d.swipe_points([(x0, y0), (x1, y1), (x2, y2)], 0.2)) #swipe from point(x0, y0) to point(x1, y1) then to point(x2, y2)
  14. d.swipe_points([(0.501, 0.333),(0.278, 0.437),(0.516, 0.556),(0.739, 0.448),(0.514, 0.448)], 0.2) #0.2s
  15. d.swipe_ext("right") # 手指右滑,4选1 "left", "right", "up", "down"
  16. d.swipe_ext("right", scale=0.9) # 默认0.9, 滑动距离为屏幕宽度的90%
  17. d.swipe_ext("right", box=(0, 0, 100, 100)) # 在 (0,0) -> (100, 100) 这个区域内做滑动
  18. # 实践发现上滑或下滑的时候,从中点开始滑动成功率会高一些
  19. d.swipe_ext("up", scale=0.8) # 代码会vkk???
  20. # 还可以使用Direction作为参数
  21. from uiautomator2 import Direction
  22. d.swipe_ext(Direction.FORWARD) # 页面下翻, 等价于 d.swipe_ext("up"), 只是更好理解
  23. d.swipe_ext(Direction.BACKWARD) # 页面上翻
  24. d.swipe_ext(Direction.HORIZ_FORWARD) # 页面水平右翻
  25. d.swipe_ext(Direction.HORIZ_BACKWARD) # 页面水平左翻
  26. #drag是对sxsy处的app进行拖动(如果sxsy刚好有app),拖动到exey位置;swipe只是单纯的滑动。
  27. d.drag(sx, sy, ex, ey) #同样支持百分比 和 像素
  28. d.drag(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)
  29. #Touch and drap (Beta)
  30. #这个接口属于比较底层的原始接口
  31. d.touch.down(10, 10) # 模拟按下
  32. time.sleep(.01) # down 和 move 之间的延迟,自己控制
  33. d.touch.move(15, 15) # 模拟移动
  34. d.touch.up() # 模拟抬起
  1. #滑动解锁 全程
  2. d.screen_on()
  3. d.swipe_ext("right", 0.6)
  4. d.swipe_ext("up", 0.6)
  5. d.swipe_points([(0.501, 0.333),(0.278, 0.437),(0.516, 0.556),(0.739, 0.448),(0.514, 0.448)], 0.2)
Stop UiAutomator

直接在小黄车 停止服务,        或者用下面的命令

  1. #停用UiAutomator的守护程序
  2. d.uiautomator.stop()
  3. #d.service("uiautomator").stop() #DeprecationWarning: Call to deprecated method service. (You should use d.uiautomator.start() instead)
  4. #这会关闭atx-agent,atx里刷新服务状态会看到{'running':False}
  5. #在weditor里connect或者任意操作,就能重启atx-agent并恢复uiautomator服务
  6. # d.uiautomator.start() # 启动
  7. # d.uiautomator.running() # 是否在运行
Screen-related
  1. #检索/设置设备方向
  2. orientation = d.orientation
  3. print(orientation)
  4. # set orientation and freeze rotation. 忘记上次用什么app实现转向的了
  5. # notes: setting "upsidedown" requires Android>=4.3.
  6. d.set_orientation('n') # or "natural "
  7. d.set_orientation("l") # or "left"
  8. d.set_orientation("r") # or "right"
  9. d.set_orientation("u") # or "upsidedown" (can not be set)
  1. #关闭、开启 屏幕自动跟随旋转
  2. # freeze rotation
  3. d.freeze_rotation() #关闭 旋转
  4. # un-freeze rotation
  5. d.freeze_rotation(False) #开启 旋转
  1. #截图,默认返回pillow格式的图片
  2. # take screenshot and save to a file on the computer, require Android>=4.2.
  3. d.screenshot("home.jpg")
  4. # get PIL.Image formatted images. Naturally, you need pillow installed first
  5. image = d.screenshot() # default format="pillow"
  6. image.save("home.jpg") # or home.png. Currently, only png and jpg are supported
  7. # get opencv formatted images. Naturally, you need numpy and cv2 installed first
  8. import cv2
  9. image = d.screenshot(format='opencv')
  10. cv2.imwrite('home.jpg', image)
  11. # get raw jpeg data
  12. imagebin = d.screenshot(format='raw')
  13. open("some.jpg", "wb").write(imagebin)
  1. #Dump UI hierarchy 转储 UI 层次结构
  2. # get the UI hierarchy dump content (unicoded).
  3. xml = d.dump_hierarchy()
  1. #下拉菜单
  2. d.open_notification() #只下拉出第一行
  3. d.open_quick_settings() #下拉出第一页
Selector 

选择器是一种方便的机制,用于识别当前窗口中的特定 UI 对象

  1. #Select the object with text 'Clock' and its className is 'android.widget.TextView'
  2. d(text='Clock', className='android.widget.TextView')
  3. '''
  4. Selector supports below parameters. Refer to [UiSelector Java doc](http://developer.android.com/tools/help/uiautomator/UiSelector.html) for detailed information.
  5. * `text`, `textContains`, `textMatches`, `textStartsWith`
  6. * `className`, `classNameMatches`
  7. * `description`, `descriptionContains`, `descriptionMatches`, `descriptionStartsWith`
  8. * `checkable`, `checked`, `clickable`, `longClickable`
  9. * `scrollable`, `enabled`,`focusable`, `focused`, `selected`
  10. * `packageName`, `packageNameMatches`
  11. * `resourceId`, `resourceIdMatches`
  12. * `index`, `instance`

 Children and siblings

  1. # get the children or grandchildren 后代元素
  2. d(className="android.widget.ListView").child(text="Bluetooth") #text一定要完全匹配
  3. d(className="android.view.ViewGroup").child(description="vip歌曲").click()
  4. # 多个参数 指定父元素和子元素
  5. d(className="android.widget.LinearLayout", resourceId="com.tencent.mm:id/kp3").child(text="朋友圈", className="android.widget.TextView").click()
  6. # 通过父 和 孙(后代),去找子
  7. d(父属性*n).child_by_text(孙(后代)的text, 子属性*n).click() #返回子
  8. d(className="android.widget.LinearLayout").child_by_text("我的快递", className="android.widget.RelativeLayout").click() #当页面有很多属性符合的父和子时,可能会找不到。 这时需要添加更多的属性
  9. d(className="android.widget.LinearLayout").child_by_text("我的快递", className="android.widget.RelativeLayout", resourceId="com.alipay.android.phone.openplatform:id/home_app_view").click() #给子添加resourceId属性,就能找到了
  10. d(className="android.widget.LinearLayout").child_by_text("消息盒子", className="android.widget.LinearLayout", resourceId="com.alipay.mobile.homefeeds:id/notification_msg_box").click() #同上
  11. #用setting和wechat的界面 测试child_by_text时,因为可用的 子属性少,总是返回页面上text对不上的元素,原因未查明?
  12. #网上别人uiautomator用法d().child_by_text().siblings()
  13. #我uiautomator2用会报错'str' object has no attribute 'clone'
  14. #有人提了issue,作者暂时没空修
  15. #还可以滚动屏幕来寻找 allow_scroll_search=True
  16. d(className="android.widget.ScrollView").child_by_text("工具箱", allow_scroll_search=True, className="android.widget.LinearLayout").click()
  17. #又出现了选中其他元素的情况。。。总感觉child_by_text不怎么好用
  18. # get siblings 同级元素
  19. d(text="Google").sibling(className="android.widget.ImageView")
  20. d(text="支付宝").sibling(description="时钟").click()
  21. d(description="K歌").sibling(description="下载").click()
  22. #relative positioning 用相对位置来找元素
  23. d(text="Wi‑Fi").right(className="android.widget.Switch").click() #left right up down
  24. #Multiple instances 当找到很多个元素时,指定要第几个
  25. d(text="Add new", instance=0) # 计数从0开始,要第1个
  26. d(className="android.widget.TextView")[2].click() #要第3个
  1. #元素相关的其他api
  2. # get the count of views with text "Add new" on current screen
  3. d(text="Add new").count
  4. len(d(text="Add new")) # same as count property ,不是列表,但 是可迭代对象
  5. # get the instance via index
  6. d(text="Add new")[0]
  7. d(text="Add new")[1]
  8. ...
  9. # iterator
  10. for view in d(text="Add new"):
  11. view.info # ...
 Get the selected ui object status and its information
  1. d(text="Settings").exists # True if exists, else False
  2. d.exists(text="Settings") # alias of above property.
  3. # advanced usage
  4. d(text="Settings").exists(timeout=3) # wait Settings appear in 3s, same as .wait(3)
  5. d(text="Settings").info #Retrieve the info of the specific UI object
  6. #Get/Set/Clear text of an editable field (e.g., EditText widgets)
  7. d(text="Settings").get_text() # get widget text 有text的元素都能获得
  8. d(className="android.widget.MultiAutoCompleteTextView").set_text("My text...") # set the text 这个不是app改名,在widget.Text...的地方才能输入
  9. d(className="android.widget.EditText")[0].clear_text() # 在widget.Text...的地方才能清除
  10. #对非widget.Text...的元素,在光标处输入
  11. d.set_fastinput_ime(True) # 切换成FastInputIME输入法
  12. d.send_keys("你好123abcEFG") # adb广播输入
  13. d.clear_text() # 清除输入框所有内容(Require android-uiautomator.apk version >= 1.0.7)
  14. d.set_fastinput_ime(False) # 切换成 系统自带的输入法(不是自己另外装的)
  15. d.send_action("search") # 该函数可以使用的参数有 go search send next done previous
  16. #什么时候该使用这个函数呢?
  17. #有些时候在EditText中输入完内容之后,调用press("search") or press("enter")发现并没有什么反应。
  18. #这个时候就需要send_action函数了,这里用到了只有输入法才能用的IME_ACTION_CODE。
  19. #send_action先broadcast命令发送给输入法操作IME_ACTION_CODE,由输入法完成后续跟EditText的通信。
  1. x, y = d(text="Settings").center() #Get Widget center point
  2. im = d(text="支付宝").screenshot() #将整个元素框截下来
  3. im.save("D:\你的名字\settings.jpg") #不知道为什么保存到桌面会报错,明明都是全英
 click action on the selected UI object
  1. d(text="Settings").click() # click on the center of the specific ui object
  2. #自定义点击元素的位置,从元素框的左上角开始,写比例会自动计算
  3. d(text="Settings").click(offset=(0.5, 0.5)) # Default center
  4. d(text="Settings").click(offset=(0, 0)) # click left-top
  5. d(text="Settings").click(offset=(1, 1)) # click right-bottom
  6. # wait element to appear for at most 10 seconds and then click
  7. d(text="Settings").click(timeout=10) #等10s超时后报错
  8. clicked = d(text='Skip').click_exists(timeout=10.0) #同上,区别在10s内有返回True,没有返回False,不报错
  9. # click until element gone, return bool 一直点,直到页面内没有这个元素,返回True,如果元素还在,返False
  10. is_gone = d(text="Skip").click_gone(maxretry=10, interval=1.0) # maxretry default 10, interval default 1.0
  11. #点10次,每次间隔1s
  12. # wait until the ui object appears
  13. d(text="Settings").wait(timeout=3.0) # return bool The default timeout is 20s.
  14. # wait until the ui object gone
  15. d(text="Settings").wait_gone(timeout=1.0)
Gesture actions for the specific UI object
  1. # notes : drag can not be used for Android<4.3.
  2. # drag the UI object to a screen point (x, y), in 0.5 second
  3. d(text="Settings").drag_to(x, y, duration=0.5)
  4. # drag the UI object to (the center position of) another UI object, in 0.25 second
  5. d(text="Settings").drag_to(text="Clock", duration=0.25)
  6. #从元素的中心滑动到其边缘
  7. d(text="Settings").swipe("up", steps=20) # 1 steps is about 5ms 当steps过短时,就和滑动屏幕一个效果
  8. #up right left down
  9. #Two-point gesture from one point to another 两点操作,比如放大图片
  10. d(text="Settings").gesture((sx1, sy1), (sx2, sy2), (ex1, ey1), (ex2, ey2))
  11. d(className="android.widget.FrameLayout").gesture((0.388, 0.591), (0.626, 0.624), (0.175, 0.533), (0.929, 0.745))
  12. #也有预设好的,
  13. # notes : pinch can not be set until Android 4.3.
  14. # from edge to center. here is "In" not "in"
  15. d(text="Settings").pinch_in(percent=100, steps=10) #缩小图片
  16. # from center to edge
  17. d(text="Settings").pinch_out() #放大图片
  1. #fling on the specific ui object(scrollable) 快速滑动fling
  2. # fling.forward(default)下.vertically(default)竖直方向
  3. d(scrollable=True).fling()
  4. d(scrollable=True).fling.horiz.forward() #水平方向若不能滑动,会触发点击
  5. d(scrollable=True).fling.vert.backward() #上
  6. d(scrollable=True).fling.horiz.toBeginning(max_swipes=1000)
  7. d(scrollable=True).fling.toEnd()
  8. # scroll.forward(default).vertically(default) 一页页滑动scroll
  9. d(scrollable=True).scroll(steps=10)
  10. d(scrollable=True).scroll.horiz.forward(steps=100)
  11. d(scrollable=True).scroll.vert.backward()
  12. d(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)
  13. d(scrollable=True).scroll.toEnd()
  14. # scroll forward vertically until specific ui object appears
  15. d(scrollable=True).scroll.to(text="Google") #这里是向下滑动,底部一出现这个元素就会停止滑动
XPath

更精准的定位。在weditor可以找到每个控件的xpath

  1. d.xpath('//*[@text="私人FM"]').get().info # 获取控件信息
  2. # wait exists 10s
  3. d.xpath("//android.widget.TextView").wait(10.0)
  4. # find and click
  5. d.xpath("//*[@content-desc='分享']").click()
  6. # check exists
  7. if d.xpath("//android.widget.TextView[contains(@text, 'Se')]").exists:
  8. print("exists")
  9. # get all text-view text, attrib and center point
  10. for elem in d.xpath("//android.widget.TextView").all():
  11. print("Text:", elem.text)
  12. # Dictionary eg:
  13. # {'index': '1', 'text': '999+', 'resource-id': 'com.netease.cloudmusic:id/qb', 'package': 'com.netease.cloudmusic', 'content-desc': '', 'checkable': 'false', 'checked': 'false', 'clickable': 'false', 'enabled': 'true', 'focusable': 'false', 'focused': 'false','scrollable': 'false', 'long-clickable': 'false', 'password': 'false', 'selected': 'false', 'visible-to-user': 'true', 'bounds': '[661,1444][718,1478]'}
  14. print("Attrib:", elem.attrib)
  15. # Coordinate eg: (100, 200)
  16. print("Position:", elem.center())
  17. print(el.elem) # 输出lxml解析出来的Node
  18. print("bounds:", el.bounds) # output tuple: (left, top, right, bottom)

   其他XPath常见用法

Session

Session represent an app lifecycle. Can be used to start app, detect app crash 当操作时app闪退,会抛出SessionBrokenError.

  1. sess = d.session("com.example.app") # 启动应用并获取session (应用此前未启动)
  2. sess.close() # 停止应用
  3. sess.restart() # 冷启动应用(重新分配新进程)
  4. sess.running() # True or False # check if session is ok.
  5. '''
  6. 冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
  7. 热启动:当启动应用时,后台已有该应用的进程(例:按home键回到桌面,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。
  8. 来源 https://www.zhihu.com/question/39146027/answer/337659054
  9. '''
  10. # When app is still running
  11. sess(text="Music").click() # operation goes normal 正常运行 就啥也不返回
  12. # If app crash or quit
  13. sess(text="Music").click() # raise SessionBrokenError 闪退或者崩溃则报错
  14. #和with一起用
  15. with d.session("com.netease.cloudmusic") as sess:
  16. sess(text="Play").click()
  17. #Attach to the running app 附加session到已启动的应用
  18. # launch app if not running, skip launch if already running
  19. sess = d.session("com.netease.cloudmusic", attach=True)
  20. # raise SessionBrokenError if not running
  21. sess = d.session("com.netease.cloudmusic", attach=True, strict=True)
Watch 注册监控

目前采用了后台运行了一个线程的方法(依赖threading库),然后每隔一段时间dump一次hierarchy,匹配到元素之后执行相应的操作。

  1. d.watcher.reset() # 停止并移除所有的监控,常用于初始化
  2. # 常用写法
  3. d.watcher.when("取消").click() #注册监控
  4. d.watcher.start() # 开始后台监控
  5. d.watcher.start(2.0) # 默认监控间隔2.0s
  6. d.watcher.stop() # 停止监控
  7. d.watcher.run() # 强制运行所有监控
  8. d.watcher.remove() # 移除所有的监控
  9. # 注册名为ANR的监控,当出现ANR和Force Close时,点击Force Close
  10. d.watcher("ANR").when(xpath="ANR").when("Force Close").click()
  11. d.watcher.remove("ANR")# 移除名为ANR的监控
  12. # 其他回调例子
  13. d.watcher.when("抢红包").press("back")
  14. d.watcher.when("//*[@text = 'Out of memory']").call(lambda d: d.shell('am force-stop com.im.qq'))
  15. d.xpath("继续").click() # 使用d.xpath检查元素的时候,会触发watcher(目前最多触发5次)
  16. # 在回调中调用不会再次触发watcher
  17. def click_callback(d: u2.Device):
  18. d.xpath("确定").click() # 在回调中调用不会再次触发watcher
WatchContext
  1. ctx = d.watch_context() #执行以后立刻开始监听
  2. ctx.when("取消").click() #注册
  3. ctx.wait_stable() # 开启弹窗监控,并等待界面稳定(两个弹窗检查周期内没有弹窗代表稳定)
  4. ctx.stop() #要用stop才能停止监听
  5. #原作者推荐下面用法,不需要自己start和stop,见仁见智吧
  6. #目前的这个watch_context是用threading启动的,每2s检查一次 目前还只有click这一种触发操作
  7. with d.watch_context() as ctx:
  8. ctx.when("取消").click() #如果只有这两行,则会立即执行'取消'(如果当前页面有'取消'),没有监听的效果
  9. ctx.wait_stable()
  10. ctx.when("取消").call(lambda d: d.press("back")) #加上下面两行就会进行3个watch check等待,这期间切换到'取消'界面,才体现监听效果
  11. #作者内置 注册了一些监听
  12. with d.watch_context(builtin=True) as ctx:
  13. # 在已有的基础上增加
  14. ctx.when("@tb:id/jview_view").when('//*[@content-desc="图片"]').click()
  15. '''
  16. self.when("继续使用").click()
  17. self.when("移入管控").when("取消").click()
  18. self.when("^立即(下载|更新)").when("取消").click()
  19. self.when("同意").click()
  20. self.when("^(好的|确定)").click()
  21. self.when("继续安装").click()
  22. self.when("安装").click()
  23. self.when("Agree").click()
  24. self.when("ALLOW").click()
  25. '''
Global settings 
  1. d.HTTP_TIMEOUT = 60 # 默认值60s, http默认请求超时时间
  2. # 当设备掉线时,等待设备在线时长,仅当TMQ=true时有效,支持通过环境变量 WAIT_FOR_DEVICE_TIMEOUT 设置
  3. d.WAIT_FOR_DEVICE_TIMEOUT = 70
  4. #其他的配置
  5. print(d.settings)
  6. d.settings['operation_delay'] = (.5, 1) # 配置点击前延时0.5s,点击后延时1s
  7. # 修改 要延迟生效的操作
  8. # 其中 double_click, long_click 都对应click
  9. d.settings['operation_delay_methods'] = ['click', 'swipe', 'drag', 'press']
  10. d.settings['xpath_debug'] = True # 开启xpath插件的调试日志
  11. d.settings['wait_timeout'] = 20.0 # 默认控件等待时间(原生操作,xpath插件的等待时间)
Screenrecord 视频录制

这里没有使用手机中自带的screenrecord命令,是通过获取手机图片合成视频的方法,所以需要安装一些其他的依赖,如imageio, imageio-ffmpeg, numpy等 因为有些依赖比较大,推荐使用镜像安装。下面的命令可安装上述依赖。

pip3 install -U "uiautomator2[image]" -i https://pypi.doubanio.com/simple

有的依赖好像已经停止维护了,

ImportError: cannot import name 'create_connection' from 'websocket'
Image match 图像匹配

同上,就到这里吧

Shell commands shell命令?留坑

更多的api见

github.com/openatx/uiautomator2/README.md?plain=1#L850

Python+uiautomator2手机UI自动化测试实战 -- 2. 用法介绍_Ricky_Frog


十分钟弄懂最快的APP自动化工具uiautomator2(入门到精通) - 知乎

d.implicitly_wait(等待时长)         #单位是s

sx, ex和sy,ey分别表示起点和终点的坐标        d.swipe(sx, sy, ex, ey, 0.5)

获取屏幕尺寸:d.window_size()

滑动距离尽量大点,比如x轴起点终点可以设置系数分别为0.9, 0.1,如果你设置为0.9,0.5,否则可能会出现滑动距离太小,导致没有滑过去的情况

停止app并清理环境

一个完整的用例就写完了,当然为了演示起见,我省略了很多,比如PO模式、pytest、日志、报告等等


Performance 性能采集

原文见:uiautomator2/uiautomator2/ext /perf/

自动记录测试过程中的CPU,PSS, NET

  1. import uiautomator2 as u2
  2. import uiautomator2.ext.perf as perf
  3. package_name = "com.netease.cloudmusic"
  4. u2.plugin_register('perf', perf.Perf)
  5. def main():
  6. d = u2.connect()
  7. d.ext_perf.package_name = package_name
  8. d.ext_perf.csv_output = "perf.csv" # 保存数据到perf.csv
  9. # d.debug = True # 采集到数据就输出,默认关闭。如果后面接d.info之类的命令,会给出更多的数据
  10. # d.interval = 1.0 # 数据采集间隔,默认1.0s,尽量不要小于0.5s,因为采集内存比较费时间
  11. d.ext_perf.start()
  12. # run ... tests code here ...
  13. d.ext_perf.stop() # 最好结束的时候调用下,虽然不调用也没多大关系
  14. # generate images from csv
  15. # 需要安装 matplotlib, pandas, numpy, humanize
  16. d.ext_perf.csv2images()
  17. if __name__ == '__main__':
  18. main()
  19. '''
  20. csv2images函数更多的用法
  21. d.ext_perf.csv2images("perf.csv", target_dir="./")
  22. - PSS直接通过`dumpsys meminfo <package-name>`获取
  23. - CPU直接读取的`/proc/`下的文件计算出来的,多核的情况,数据是有可能超过100%的
  24. - rxBytes, txBytes 目前只有wlan的流量,tcp和udp的流量总和
  25. - fps 通过解析`dumpsys SurfaceFlinger --list` 和 `dumpsys SurfaceFlinger --latency <VIEW>` 计算出来

PO模式:自动化测试(UI)----PO设计模式_ui自动化po模型_疯狂的大饼的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/556667
推荐阅读
相关标签
  

闽ICP备14008679号