连接设备
有多种方式可以连接
- wifi 首先要保证你的手机(10.0.0.1)和电脑在同一个局域网内
import uiautomator2 as u2d = u2.connect('10.0.0.1') # alias for u2.connect_wifi('10.0.0.1')print(d.info)
- usb
usb通过设备号连接,可以通过adb devices查看
import uiautomator2 as u2d = u2.connect('123456f') # alias for u2.connect_usb('123456f')print(d.info)
- adb wifi
import uiautomator2 as u2d = u2.connect_adb_wifi("10.0.0.1:5555")# Equals to # + Shell: adb connect 10.0.0.1:5555# + Python: u2.connect_usb("10.0.0.1:5555")
全局设置
里面包含一些u2全局设置的属性
- Http debug
>>> d.debug = True>>> d.info12:32:47.182 $ curl -X POST -d '{"jsonrpc": "2.0", "id": "b80d3a488580be1f3e9cb3e926175310", "method": "deviceInfo", "params": {}}' 'http://127.0.0.1:54179/jsonrpc/0'12:32:47.225 Response >>>{"jsonrpc":"2.0","id":"b80d3a488580be1f3e9cb3e926175310","result":{"currentPackageName":"com.android.mms","displayHeight":1920,"displayRotation":0,"displaySizeDpX":360,"displaySizeDpY":640,"displayWidth":1080,"productName":"odin","screenOn":true,"sdkInt":25,"naturalOrientation":true}}<<< END
- 默认等待超时时间
d.implicitly_wait(10.0)d(text="Settings").click() # if Settings button not show in 10s, UiObjectNotFoundError will raisedprint("wait timeout", d.implicitly_wait()) # get default implicit wait
将会作用在这些方法中 click, long_click, drag_to, get_text, set_text, clear_text,等.
app管理
- 安装
d.app_install('http://some-domain.com/some.apk')
- 唤起app
# 默认的这种方法是先通过atx-agent解析apk包的mainActivity,然后调用am start -n $package/$activity启动d.app_start("com.example.hello_world")# 使用 monkey -p com.example.hello_world -c android.intent.category.LAUNCHER 1 启动# 这种方法有个附带的问题,它自动会将手机的旋转锁定给关掉d.app_start("com.example.hello_world", use_monkey=True) # start with package name# 通过指定main activity的方式启动应用,等价于调用am start -n com.example.hello_world/.MainActivityd.app_start("com.example.hello_world", ".MainActivity")
- 停止app
# equivalent to `am force-stop`, thus you could lose datad.app_stop("com.example.hello_world") # equivalent to `pm clear`d.app_clear('com.example.hello_world')
- 停止所有运行的app
# stop alld.app_stop_all()# stop all app except for com.examples.demod.app_stop_all(excludes=['com.examples.demo'])
- 获取app信息
d.app_info("com.examples.demo")# expect output#{# "mainActivity": "com.github.uiautomator.MainActivity",# "label": "ATX",# "versionName": "1.1.7",# "versionCode": 1001007,# "size":1760809#}# save app iconimg = d.app_icon("com.examples.demo")img.save("icon.png")
- 获取所有正在运行的app
d.app_list_running()# expect output# ["com.xxxx.xxxx", "com.github.uiautomator", "xxxx"]
- 等待app运行
pid = d.app_wait("com.example.android") # 等待应用运行, return pid(int)if not pid: print("com.example.android is not running")else: print("com.example.android pid is %d" % pid)d.app_wait("com.example.android", front=True) # 等待应用前台运行d.app_wait("com.example.android", timeout=20.0) # 最长等待时间20s(默认)
- 传文件到设备
# push to a folderd.push("foo.txt", "/sdcard/")# push and renamed.push("foo.txt", "/sdcard/bar.txt")# push fileobjwith open("foo.txt", 'rb') as f: d.push(f, "/sdcard/")# push and change file access moded.push("foo.sh", "/data/local/tmp/", mode=0o755)
- 从设备拉取一个文件到电脑
d.pull("/sdcard/tmp.txt", "tmp.txt")# FileNotFoundError will raise if the file is not found on the deviced.pull("/sdcard/some-file-not-exists.txt", "tmp.txt")
- 设备健康检查
d.healthcheck()
常见api使用
介绍如何操作设备
shell命令
- 具有超时保护的shell 命令(默认60s)
output, exit_code = d.shell("pwd", timeout=60) # timeout 60s (Default)# output: "/\n", exit_code: 0# Similar to command: adb shell pwd# Since `shell` function return type is `namedtuple("ShellResponse", ("output", "exit_code"))`# so we can do some tricksoutput = d.shell("pwd").outputexit_code = d.shell("pwd").exit_code# The first argument can be list. for exampleoutput, exit_code = d.shell(["ls", "-l"])# output: "/....", exit_code: 0
该 adb shell会阻塞,直到有结果活超时,不适合长时间使用
- 长期运行的shell
r = d.shell("logcat", stream=True)# r: requests.models.Responsedeadline = time.time() + 10 # run maxium 10stry: for line in r.iter_lines(): # r.iter_lines(chunk_size=512, decode_unicode=None, delimiter=None) if time.time() > deadline: break print("Read:", line.decode('utf-8'))finally: r.close() # this method must be called
在r.close() 时,命令将会被终止
会话
会话代表应用程序生命周期。 可用于启动应用程序,检测应用程序崩溃。
-
唤起和关闭app
sess = d.session("com.netease.cloudmusic") # start 网易云音乐sess.close() # 停止网易云音乐sess.restart() # 冷启动网易云音乐 -
使用python
with
with d.session("com.netease.cloudmusic") as sess: sess(text="Play").click()
- 连接运行的app
# launch app if not running, skip launch if already running#如果app没有运行将会唤起,运行则会连接sess = d.session("com.netease.cloudmusic", attach=True)# raise SessionBrokenError if not running# 异常在app不在运行的时候sess = d.session("com.netease.cloudmusic", attach=True, strict=True)
- 检测app崩溃
# When app is still runningsess(text="Music").click() # operation goes normal# If app crash or quit#异常在app不在运行的时候sess(text="Music").click() # raise SessionBrokenError# other function calls under session will raise SessionBrokenError too# check if session is ok.# Warning: function name may change in the futuresess.running() # True or False
获取设备信息
- 基本信息
d.info
{ u'displayRotation': 0, u'displaySizeDpY': 640, u'displaySizeDpX': 360, u'currentPackageName': u'com.android.launcher', u'productName': u'takju', u'displayWidth': 720, u'sdkInt': 18, u'displayHeight': 1184, u'naturalOrientation': True}
- 设备的详细信息
d.device_info
{'udid': '3578298f-b4:0b:44:e6:1f:90-OD103', 'version': '7.1.1', 'serial': '3578298f', 'brand': 'SMARTISAN', 'model': 'OD103', 'hwaddr': 'b4:0b:44:e6:1f:90', 'port': 7912, 'sdk': 25, 'agentVersion': 'dev', 'display': {'width': 1080, 'height': 1920}, 'battery': {'acPowered': False, 'usbPowered': False, 'wirelessPowered': False, 'status': 3, 'health': 0, 'present': True, 'level': 99, 'scale': 100, 'voltage': 4316, 'temperature': 272, 'technology': 'Li-ion'}, 'memory': {'total': 3690280, 'around': '4 GB'}, 'cpu': {'cores': 8, 'hardware': 'Qualcomm Technologies, Inc MSM8953Pro'}, 'presenceChangedAt': '0001-01-01T00:00:00Z', 'usingBeganAt': '0001-01-01T00:00:00Z'}
-
屏幕大小
print(d.window_size())# device upright output example: (1080, 1920)# device horizontal output example: (1920, 1080)
-
运行的app
print(d.app_current())# Output example 1: {'activity': '.Client', 'package': 'com.netease.example', 'pid': 23710}# Output example 2: {'activity': '.Client', 'package': 'com.netease.example'}# Output example 3: {'activity': None, 'package': None}
- 等待
activity
d.wait_activity(".ApiDemos", timeout=10) # default timeout 10.0 seconds# Output: true of false
- 获取设备号
print(d.serial)# output example: 74aAEDR428Z9
- 局域网ip
print(d.wlan_ip)# output example: 10.0.0.1
键盘事件
- 打开/关闭屏幕
d.screen_on() # turn on the screend.screen_off() # turn off the screen
- 获取屏幕状态
d.info.get('screenOn') # require Android >= 4.4
- 点击按键
d.press("home") # press the home key, with key name d.press("back") # press the back key, with key name d.press(0x07, 0x02) # press keycode 0x07('0') with META ALT(0x02) - 当前支持以下键名:
- home
- back
- left
- right
- up
- down
- center
- menu
- search
- enter
- delete ( or del)
- recent (recent apps)
- volume_up
- volume_down
- volume_mute
- camera
- power
更多key code定义参考Android KeyEvent
- 解锁
python d.unlock() # This is equivalent to # 1. launch activity: com.github.uiautomator.ACTION_IDENTIFY # 2. press the "home" key
与设备的手势交互
-
单击
d.click(x, y)
-
双击
d.double_click(x, y)d.double_click(x, y, 0.1) # default duration between two click is 0.1s
-
长按
d.long_click(x, y)d.long_click(x, y, 0.5) # long click 0.5s (default)
-
滑动
d.swipe(sx, sy, ex, ey)d.swipe(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)
-
滑动扩展功能
d.swipe_ext("right") # 屏幕右滑,4选1 "left", "right", "up", "bottom"d.swipe_ext("right", scale=0.9) # 默认0.9, 滑动距离为屏幕宽度的90%d.swipe_ext("right", box=(0, 0, 100, 100)) # 在 (0,0) -> (100, 100) 这个区域做滑动
* 拖拽```pythond.drag(sx, sy, ex, ey)d.drag(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)
-
滑动解锁
# swipe from point(x0, y0) to point(x1, y1) then to point(x2, y2)# time will speed 0.2s bwtween two pointsd.swipe_points([(x0, y0), (x1, y1), (x2, y2)], 0.2))
多用于九宫格解锁,提前获取到每个点的相对坐标(这里支持百分比), 更详细的使用参考这个帖子 使用u2实现九宫图案解锁
-
按着拖动
这个接口属于比较底层的原始接口,感觉并不完善,不过凑合能用。注:这个地方并不支持百分比
d.touch.down(10, 10) # 模拟按下time.sleep(.01) # down 和 move 之间的延迟,自己控制d.touch.move(15, 15) # 模拟移动d.touch.up() # 模拟抬起
注: click, swipe, drag 这几个操作支持百分比
屏幕相关
- 设置屏幕方向
- natural or n
- left or l
- right or r
- upsidedown or u (can not be set)
# retrieve orientation. the output could be "natural" or "left" or "right" or "upsidedown"orientation = d.orientation# WARNING: not pass testing in my TT-M1# set orientation and freeze rotation.# notes: setting "upsidedown" requires Android>=4.3.d.set_orientation('l') # or "left"d.set_orientation("l") # or "left"d.set_orientation("r") # or "right"d.set_orientation("n") # or "natural"
- 屏幕旋转
# freeze rotationd.freeze_rotation()# un-freeze rotationd.freeze_rotation(False)
- 截屏
# take screenshot and save to a file on the computer, require Android>=4.2.d.screenshot("home.jpg")# get PIL.Image formatted images. Naturally, you need pillow installed firstimage = d.screenshot() # default format="pillow"image.save("home.jpg") # or home.png. Currently, only png and jpg are supported# get opencv formatted images. Naturally, you need numpy and cv2 installed firstimport cv2image = d.screenshot(format='opencv')cv2.imwrite('home.jpg', image)# get raw jpeg dataimagebin = d.screenshot(format='raw')open("some.jpg", "wb").write(imagebin)
- dump 视图结构
# get the UI hierarchy dump content (unicoded).xml = d.dump_hierarchy()
- 打开通知和设置
d.open_notification()d.open_quick_settings()
选择器
选择器是一种灵活的机制用户标识一个特殊的ui对象
# Select the object with text 'Clock' and its className is 'android.widget.TextView'd(text='Clock', className='android.widget.TextView')
注: 可以使用d.info输出定位的控件信息,来确认是否正确
选择器支持以下参数,更多参考UiSelector java文档
text,textContains,textMatches,textStartsWithclassName,classNameMatchesdescription,descriptionContains,descriptionMatches,descriptionStartsWithcheckable,checked,clickable,longClickablescrollable,enabled,focusable,focused,selectedpackageName,packageNameMatchesresourceId,resourceIdMatchesindex,instance
子和兄弟控件(Children and siblings)
- children
# get the children or grandchildrend(className="android.widget.ListView").child(text="Bluetooth")
- siblings (兄弟)
# get siblingsd(text="Google").sibling(className="android.widget.ImageView")
textdescriptioninstance定位子view
# get the child matching the condition className="android.widget.LinearLayout"# and also its children or grandchildren with text "Bluetooth"d(className="android.widget.ListView", resourceId="android:id/list") \ .child_by_text("Bluetooth", className="android.widget.LinearLayout")# get children by allowing scroll searchd(className="android.widget.ListView", resourceId="android:id/list") \ .child_by_text( "Bluetooth", allow_scroll_search=True, className="android.widget.LinearLayout" )
child_by_description child_by_instance 和 child_by_instance差不多,只是条件换了,但是都是查找屏幕内的view
例子dom信息如下:
<node index="0" text="" resource-id="android:id/list" class="android.widget.ListView" ...> <node index="0" text="WIRELESS & NETWORKS" resource-id="" class="android.widget.TextView" .../> <node index="1" text="" resource-id="" class="android.widget.LinearLayout" ...> <node index="1" text="" resource-id="" class="android.widget.RelativeLayout" ...> <node index="0" text="Wi‑Fi" resource-id="android:id/title" class="android.widget.TextView" .../> </node> <node index="2" text="ON" resource-id="com.android.settings:id/switchWidget" class="android.widget.Switch" .../> </node> ...</node>
这里有一个例子来说明如何使用
注: 不同版本的settings可能实现不一样,例如6.0的就不是ListView
d(className="android.widget.ListView", resourceId="android:id/list") \ .child_by_text("Wi‑Fi", className="android.widget.LinearLayout") \ .child(className="android.widget.Switch") \ .click()
-
相对位置
d(A).left(B), selects B on the left side of A.d(A).right(B), selects B on the right side of A.d(A).up(B), selects B above A.d(A).down(B), selects B under A.
## select "switch" on the right side of "Wi‑Fi"d(text="Wi‑Fi").right(className="android.widget.Switch").click()
- 多个实例
有时候界面上会有多个view具有相同的属性,例如text,这时候你可以使用instance来选择你需要的实例
d(text="Add new", instance=0) # which means the first instance with text "Add new"
更多api信息 list-like API (similar to jQuery):
# get the count of views with text "Add new" on current screend(text="Add new").count# same as count propertylen(d(text="Add new"))# get the instance via indexd(text="Add new")[0]d(text="Add new")[1]...# iteratorfor view in d(text="Add new"): view.info # ...
注:在遍历list的时候要保证界面是不变化的,不然发生错误
ui对象状态和信息
- 检查ui是否存在
d(text="Settings").exists # True if exists, else Falsed.exists(text="Settings") # alias of above property.# advanced usaged(text="Settings").exists(timeout=3) # wait Settings appear in 3s, same as .wait(3)
- ui对象信息
d(text="Settings").info# 输出{ u'contentDescription': u'',u'checked': False,u'scrollable': False,u'text': u'Settings',u'packageName': u'com.android.launcher',u'selected': False,u'enabled': True,u'bounds': {u'top': 385, u'right': 360, u'bottom': 585, u'left': 200},u'className': u'android.widget.TextView',u'focused': False,u'focusable': True,u'clickable': True,u'chileCount': 0,u'longClickable': True,u'visibleBounds': {u'top': 385, u'right': 360, u'bottom': 585, u'left': 200},u'checkable': False}
- 获取/设置/清空 输入框
d(text="Settings").get_text() # get widget textd(text="Settings").set_text("My text...") # set the textd(text="Settings").clear_text() # clear the text
- 获取控件中心点
x, y = d(text="Settings").center()# x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y
点击选中的ui对象
- 单击
# click on the center of the specific ui objectd(text="Settings").click()# wait element to appear for at most 10 seconds and then clickd(text="Settings").click(timeout=10)# click with offset(x_offset, y_offset)# click_x = x_offset * width + x_left_top# click_y = y_offset * height + y_left_topd(text="Settings").click(offset=(0.5, 0.5)) # Default centerd(text="Settings").click(offset=(0, 0)) # click left-topd(text="Settings").click(offset=(1, 1)) # click right-bottom# click when exists in 10s, default timeout 0sclicked = d(text='Skip').click_exists(timeout=10.0)# click until element gone, return boolis_gone = d(text="Skip").click_gone(maxretry=10, interval=1.0) # maxretry default 10, interval default 1.0
- 长按
# long click on the center of the specific UI objectd(text="Settings").long_click()
手势操作
- 拖动
# notes : drag can not be used for Android<4.3.# drag the UI object to a screen point (x, y), in 0.5 secondd(text="Settings").drag_to(x, y, duration=0.5)# drag the UI object to (the center position of) another UI object, in 0.25 secondd(text="Settings").drag_to(text="Clock", duration=0.25)
-
滑动
支持四个方向- left
- right
- top
- bottom
d(text="Settings").swipe("right")d(text="Settings").swipe("left", steps=10)d(text="Settings").swipe("up", steps=20) # 1 steps is about 5ms, so 20 steps is about 0.1sd(text="Settings").swipe("down", steps=20) -
双指滑动
d(text="Settings").gesture((sx1, sy1), (sx2, sy2), (ex1, ey1), (ex2, ey2))
- Two-point gesture on the specific UI object 不晓得怎么表达
Supports two gestures:
In, from edge to centerOut, from center to edge
# notes : pinch can not be set until Android 4.3.# from edge to center. here is "In" not "in"d(text="Settings").pinch_in(percent=100, steps=10)# from center to edged(text="Settings").pinch_out()
-
等待ui出现活消失
# wait until the ui object appearsd(text="Settings").wait(timeout=3.0) # return bool# wait until the ui object goned(text="Settings").wait_gone(timeout=1.0)
-
fling指定的ui(scrollable)
horizorvert(横竖)forwardorbackwardortoBeginningortoEnd(前后)
# fling forward(default) vertically(default) d(scrollable=True).fling()# fling forward horizontallyd(scrollable=True).fling.horiz.forward()# fling backward verticallyd(scrollable=True).fling.vert.backward()# fling to beginning horizontallyd(scrollable=True).fling.horiz.toBeginning(max_swipes=1000)# fling to end verticallyd(scrollable=True).fling.toEnd()
- 滑动指定的ui对象(scrollable)
Possible properties:
horizorvertforwardorbackwardortoBeginningortoEnd, orto
# scroll forward(default) vertically(default)d(scrollable=True).scroll(steps=10)# scroll forward horizontallyd(scrollable=True).scroll.horiz.forward(steps=100)# scroll backward verticallyd(scrollable=True).scroll.vert.backward()# scroll to beginning horizontallyd(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)# scroll to end verticallyd(scrollable=True).scroll.toEnd()# scroll forward vertically until specific ui object appearsd(scrollable=True).scroll.to(text="Security")
观察者
当选择器找不到指定对象的时候,你可以注册观察者来执行一些操作
- 注册
d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \ .click(text="Force Close")# d.watcher(name) ## creates a new named watcher.# .when(condition) ## the UiSelector condition of the watcher.# .click(target) ## perform click action on the target UiSelector.#d.watcher("ALERT").when(text="OK").click()# Same asd.watcher("ALERT").when(text="OK").click(text="OK")# 多条件d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \ .press("back", "home")# d.watcher(name) ## creates a new named watcher.# .when(condition) ## the UiSelector condition of the watcher.# .press(<keyname>, ..., <keyname>.() ## press keys one by one in sequence.
- 检查是否触发
d.watcher("watcher_name").triggered# true in case of the specified watcher triggered, else false
- 移除
# remove the watcherd.watcher("watcher_name").remove()
- 列出所有观察者
d.watchers# a list of all registered watchers
- 检查是否触发,任意一个
d.watchers.triggered# true in case of any watcher triggered
- 重置
# reset all triggered watchers, after that, d.watchers.triggered will be false.d.watchers.reset()
- 移除
# remove all registered watchersd.watchers.remove()# remove the named watcher, same as d.watcher("watcher_name").remove()d.watchers.remove("watcher_name")
- 强制运行所有
# force to run all registered watchersd.watchers.run()
另外文档还是有很多没有写,推荐直接去看源码init.py
现在推荐使用这种观察者来处理弹窗等
d.xpath.when(“服务条款%”).when(“同意”).click() d.xpath.when(“立即体验”).click() d.xpath.when(“跳过”).click()d.xpath.run_watchers() # 运行一次弹窗检查 d.xpath.watch_background(2.0)
全局设置
# set delay 1.5s after each UI click and click, 点击延迟d.click_post_delay = 1.5 # default no delay# set default element wait timeout (seconds) 默认超时时间d.wait_timeout = 30.0 # default 20.0
UiAutomator中的超时设置(隐藏方法)
>> d.jsonrpc.getConfigurator() {'actionAcknowledgmentTimeout': 500, 'keyInjectionDelay': 0, 'scrollAcknowledgmentTimeout': 200, 'waitForIdleTimeout': 0, 'waitForSelectorTimeout': 0}>> d.jsonrpc.setConfigurator({"waitForIdleTimeout": 100}){'actionAcknowledgmentTimeout': 500, 'keyInjectionDelay': 0, 'scrollAcknowledgmentTimeout': 200, 'waitForIdleTimeout': 100, 'waitForSelectorTimeout': 0}
Input method
这种方法通常用于不知道控件的情况下的输入。第一步需要切换输入法,然后发送adb广播命令,具体使用方法如下
d.set_fastinput_ime(True) # 切换成FastInputIME输入法d.send_keys("你好123abcEFG") # adb广播输入d.clear_text() # 清除输入框所有内容(Require android-uiautomator.apk version >= 1.0.7)d.set_fastinput_ime(False) # 切换成正常的输入法d.send_action("search") # 模拟输入法的搜索
send_action 说明
该函数可以使用的参数有 go search send next done previous
什么时候该使用这个函数呢?
有些时候在EditText中输入完内容之后,调用press("search") or press("enter")发现并没有什么反应。
这个时候就需要send_action函数了,这里用到了只有输入法才能用的IME_ACTION_CODE。send_action先broadcast命令发送给输入法操作IME_ACTION_CODE,由输入法完成后续跟EditText的通信。(原理我不太清楚,有了解的,提issue告诉我)
Toast
Show Toast
d.toast.show("Hello world")d.toast.show("Hello world", 1.0) # show for 1.0s, default 1.0s
Get Toast
# [Args]# 5.0: max wait timeout. Default 10.0# 10.0: cache time. return cache toast if already toast already show up in recent 10 seconds. Default 10.0 (Maybe change in the furture)# "default message": return if no toast finally get. Default Noned.toast.get_message(5.0, 10.0, "default message")# common usageassert "Short message" in d.toast.get_message(5.0, default="")# clear cached toastd.toast.reset()# Now d.toast.get_message(0) is None
XPath
For example: 其中一个节点的内容
<android.widget.TextView index="2" text="05:19" resource-id="com.netease.cloudmusic:id/qf" 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="[957,1602][1020,1636]" />
xpath定位和使用方法
有些属性的名字有修改需要注意
description -> content-descresourceId -> resource-id
常见用法
# wait exists 10sd.xpath("//android.widget.TextView").wait(10.0)# find and clickd.xpath("//*[@content-desc='分享']").click()# check existsif d.xpath("//android.widget.TextView[contains(@text, 'Se')]").exists: print("exists")# get all text-view text, attrib and center pointfor elem in d.xpath("//android.widget.TextView").all(): print("Text:", elem.text) # Dictionary eg: # {'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]'} print("Attrib:", elem.attrib) # Coordinate eg: (100, 200) print("Position:", elem.center())
其他XPath常见用法
See also: https://github.com/openatx/uiautomator2/blob/master/uiautomator2/ext/xpath/README.md
uiautomator2 是一个超级好的项目,希望大家一起参与多提pr
