{"id":201047,"date":"2023-08-16T23:45:24","date_gmt":"2023-08-16T15:45:24","guid":{"rendered":"https:\/\/www.idc.net\/help\/201047\/"},"modified":"2023-08-16T23:45:24","modified_gmt":"2023-08-16T15:45:24","slug":"%e5%8f%af%e4%bb%a5%e5%ae%8c%e6%88%90-android-ui-%e8%87%aa%e5%8a%a8%e5%8c%96%e7%9a%84-python-%e5%ba%93","status":"publish","type":"post","link":"https:\/\/idc.net\/help\/201047\/","title":{"rendered":"\u53ef\u4ee5\u5b8c\u6210 Android UI \u81ea\u52a8\u5316\u7684 Python \u5e93"},"content":{"rendered":"<p style=\"text-align: center\">&nbsp;<span>[[217058]]<\/span><\/p>\n<h2>uiautomator2<\/h2>\n<p>Android Uiautomator2 Python Wrapper \u8fd9\u662f\u4e00\u4e2a\u53ef\u4ee5\u5b8c\u6210Android\u7684UI\u81ea\u52a8\u5316\u7684python\u5e93\u3002&nbsp;\u8be5\u9879\u76ee\u8fd8\u5728\u706b\u70ed\u7684\u5f00\u53d1\u4e2d<\/p>\n<p>google\u63d0\u4f9b\u7684uiautomator\u5e93\u529f\u80fd\u505a\u8d77\u5b89\u5353\u81ea\u52a8\u5316\u6765\u975e\u5e38\u5f3a\u5927\uff0c\u552f\u72ec\u6709\u4e24\u4e2a\u7f3a\u70b9\uff1a1. \u53ea\u80fd\u5728\u624b\u673a\u4e0a\u8fd0\u884c 2. \u53ea\u80fd\u4f7f\u7528java\u8bed\u8a00\u3002 \u6240\u4ee5\u4e3a\u4e86\u80fd\u66f4\u7b80\u5355\u5feb\u6377\u7684\u4f7f\u7528uiautomator\uff0c\u8fd9\u4e2a\u9879\u76ee\u901a\u8fc7\u5728\u624b\u673a\u4e0a\u8fd0\u884c\u4e86\u4e00\u4e2ahttp\u670d\u52a1\u7684\u65b9\u6cd5\uff0c\u5c06uiautomator\u4e2d\u7684\u51fd\u6570\u5f00\u653e\u4e86\u51fa\u6765\u3002\u7136\u540e\u518d\u5c06\u8fd9\u4e9bhttp\u63a5\u53e3\uff0c\u5c01\u88c5\u6210\u4e86python\u5e93\u3002\u8fd9\u91cc\u8981\u975e\u5e38\u611f\u8c22 Xiaocong He (&nbsp;@xiaocong&nbsp;)\uff0c\u4ed6\u5c06\u8fd9\u4e2a\u60f3\u6cd5\u5b9e\u73b0\u4e86\u51fa\u6765\uff0cuiautomator2\u8fd9\u4e2a\u9879\u76ee\u5219\u662f\u5bf9\u539f\u6709xiaocong\u7684\u9879\u76ee&nbsp;uiautomator&nbsp;\u8fdb\u884c\u4e86bug\u7684\u4fee\u6539\uff0c\u529f\u80fd\u8fdb\u884c\u4e86\u52a0\u5f3a\u3002\u5177\u4f53\u6709\u4ee5\u4e0b<\/p>\n<ul>\n<li>\u4fee\u590duiautomator\u7ecf\u5e38\u6027\u9000\u51fa\u7684\u95ee\u9898<\/li>\n<li>\u4ee3\u7801\u8fdb\u884c\u4e86\u91cd\u6784\u548c\u7cbe\u7b80\uff0c\u65b9\u4fbf\u7ef4\u62a4<\/li>\n<li>\u589e\u52a0\u4e86\u8131\u79bb\u6570\u636e\u7ebf\u8fd0\u884c\u6d4b\u8bd5\u7684\u529f\u80fd<\/li>\n<li>\u901a\u8fc7&nbsp;minicap&nbsp;\u52a0\u5feb\u622a\u56fe\u901f\u5ea6<\/li>\n<\/ul>\n<p>\u867d\u7136\u6211\u8bf4\u7684\u5f88\u7b80\u5355\uff0c\u4f46\u662f\u5b9e\u73b0\u8d77\u6765\u7528\u5230\u4e86\u5f88\u591a\u7684\u6280\u672f\u548c\u6280\u5de7\uff0c\u529f\u80fd\u975e\u5e38\u5f3a\uff0c\u552f\u72ec\u6587\u6863\u6709\u70b9\u5c11\u3002\u54c8\u54c8<\/p>\n<h2>Installation<\/h2>\n<ol>\n<li>\n<p>Install python library<\/p>\n<pre>\r\n# Since uiautomator2 is still developing, you have to add --pre to install development version\r\npip install --pre uiautomator2\r\n\r\n# Or you can install from source\r\ngit clone https:\/\/github.com\/openatx\/uiautomator2\r\npip install -e uiautomator2<\/pre>\n<p>Optional, used in screenshot()<\/p>\n<pre>\r\npip install pillow<\/pre>\n<\/li>\n<li>\n<p>Push and install (apk, atx-agent, minicap, minitouch) to device<\/p>\n<p>\u7535\u8111\u8fde\u63a5\u4e0a\u4e00\u4e2a\u624b\u673a\u6216\u591a\u4e2a\u624b\u673a, \u786e\u4fddadb\u5df2\u7ecf\u6dfb\u52a0\u5230\u73af\u5883\u53d8\u91cf\u4e2d\uff0c\u6267\u884c\u4e0b\u9762\u7684\u547d\u4ee4\u4f1a\u81ea\u52a8\u5b89\u88c5&nbsp;uiautomator-apk&nbsp;\u4ee5\u53ca&nbsp;atx-agent<\/p>\n<pre>\r\npython -m uiautomator2 init<\/pre>\n<p>\u5b89\u88c5\u63d0\u793a&nbsp;success&nbsp;\u5373\u53ef<\/p>\n<\/li>\n<\/ol>\n<h2>Usage \u4f7f\u7528\u6307\u5357<\/h2>\n<p>\u4e0b\u6587\u4e2d\u6211\u4eec\u7528&nbsp;device_ip&nbsp;\u8fd9\u4e2a\u53d8\u91cf\u6765\u5b9a\u4e49\u624b\u673a\u7684IP\uff0c\u901a\u5e38\u6765\u8bf4\u5b89\u88c5\u5b8c&nbsp;atx-agent&nbsp;\u7684\u65f6\u5019\u4f1a\u81ea\u52a8\u63d0\u793a\u4f60\u624b\u673a\u7684IP\u662f\u591a\u5c11\u3002<\/p>\n<p>\u5982\u679c\u624b\u673a\u7684WIFI\u8ddf\u7535\u8111\u4e0d\u662f\u4e00\u4e2a\u7f51\u6bb5\u7684\uff0c\u9700\u8981\u5148\u901a\u8fc7\u6570\u636e\u7ebf\u5c06\u624b\u673a\u8fde\u63a5\u5230\u7535\u8111\u4e0a\uff0c\u4f7f\u7528\u547d\u4ee4&nbsp;adb forward tcp:7912 tcp:7912&nbsp;\u5c06\u624b\u673a\u4e0a\u7684\u670d\u52a1\u7aef\u53e37912\u8f6c\u53d1\u5230PC\u4e0a\u3002\u8fd9\u4e2a\u65f6\u5019\u8fde\u63a5\u5730\u5740\u4f7f\u7528&nbsp;127.0.0.1&nbsp;\u5373\u53ef\u3002<\/p>\n<h2>\u547d\u4ee4\u884c\u4f7f\u7528<\/h2>\n<ul>\n<li>\n<p>init: \u521d\u59cb\u5316\u8bbe\u5907\u7684atx-agent\u7b49<\/p>\n<p>Installation\u90e8\u5206\u5df2\u7ecf\u4ecb\u7ecd\u8fc7\uff0c\u8fd9\u91cc\u5c31\u4e0d\u5199\u4e86<\/p>\n<\/li>\n<li>\n<p>install: \u901a\u8fc7URL\u5b89\u88c5\u5e94\u7528<\/p>\n<pre>\r\n$ python -m uiautomator2 install $device_ip https:\/\/example.org\/some.apk\r\nMainThread: 15:37:55,731 downloading 80.4 kB \/ 770.6 kB\r\nMainThread: 15:37:56,763 installing 770.6 kB \/ 770.6 kB\r\nMainThread: 15:37:58,780 success installed 770.6 kB \/ 770.6 kB<\/pre>\n<\/li>\n<li>\n<p>clear-cache: \u6e05\u7a7a\u7f13\u5b58<\/p>\n<pre>\r\n$ python -m uiautomator2 clear-cache<\/pre>\n<\/li>\n<li>\n<p>app-stop-all&nbsp;: \u505c\u6b62\u6240\u6709\u5e94\u7528<\/p>\n<pre>\r\n$ python -m uiautomator2 app-stop-all $device_ip<\/pre>\n<\/li>\n<\/ul>\n<h2>QUICK START<\/h2>\n<p>Open python, input with the following code<\/p>\n<p>There are two ways to connect to the device.<\/p>\n<ol>\n<li>Through WIFI (recommend) Suppose device IP is&nbsp;10.0.0.1&nbsp;and your PC is in the same network.<\/li>\n<\/ol>\n<pre>\r\nimport uiautomator2 as u2\r\n\r\nd = u2.connect('10.0.0.1') # same as call with u2.connect_wifi('10.0.0.1')\r\nprint(d.info)<\/pre>\n<ol>\n<li>Through USB Suppose device serial is&nbsp;123456f<\/li>\n<\/ol>\n<pre>\r\nimport uiautomator2 as u2\r\n\r\nd = u2.connect('123456f') # same as call with u2.connect_usb('123456f')\r\nprint(d.info)<\/pre>\n<p>If just call&nbsp;u2.connect()&nbsp;with no arguments, env-var&nbsp;ANDROID_DEVICE_IP&nbsp;will first check. if env-var is empty,&nbsp;connect_usb&nbsp;will be called. you need to make sure there is only one device connected with your computer.<\/p>\n<h2>\u4e00\u4e9b\u5e38\u7528\u4f46\u662f\u4e0d\u77e5\u9053\u5f52\u5230\u4ec0\u4e48\u7c7b\u91cc\u7684\u51fd\u6570<\/h2>\n<p>\u5148\u4e2d\u6587\u5199\u7740\u4e86\uff0c\u56fd\u5916\u5927\u4f6c\u4eec\u5148\u7528Google Translate\u9876\u7740<\/p>\n<h3>\u68c0\u67e5\u5e76\u7ef4\u6301uiautomator\u5904\u4e8e\u8fd0\u884c\u72b6\u6001<\/h3>\n<pre>\r\nd.healthcheck()<\/pre>\n<h3>\u8fde\u63a5\u672c\u5730\u7684\u8bbe\u5907<\/h3>\n<p>\u9700\u8981\u8bbe\u5907\u66fe\u7ecf\u4f7f\u7528&nbsp;python -muiautomator2 init&nbsp;\u521d\u59cb\u5316\u8fc7<\/p>\n<pre>\r\nd = u2.connect_usb(\"{Your-Device-Serial}\")<\/pre>\n<h3>\u4e00\u5b9a\u65f6\u95f4\u5185\uff0c\u51fa\u73b0\u5219\u70b9\u51fb<\/h3>\n<p>10s\u5185\u5982\u679c\u51fa\u73b0Skip\u5219\u70b9\u51fb<\/p>\n<pre>\r\nclicked = d(text='Skip').click_exists(timeout=10.0)<\/pre>\n<h3>\u6253\u5f00\u8c03\u8bd5\u5f00\u5173<\/h3>\n<p>\u7528\u4e8e\u5f00\u53d1\u8005\u6216\u6709\u7ecf\u9a8c\u7684\u4f7f\u7528\u8005\u5b9a\u4f4d\u95ee\u9898<\/p>\n<pre>\r\n&gt;&gt;&gt; d.debug = True\r\n&gt;&gt;&gt; d.info\r\n12:32:47.182 $ curl -X POST -d '{\"jsonrpc\": \"2.0\", \"id\": \"b80d3a488580be1f3e9cb3e926175310\", \"method\": \"deviceInfo\", \"params\": {}}' 'http:\/\/127.0.0.1:54179\/jsonrpc\/0'\r\n12:32:47.225 Response &gt;&gt;&gt;\r\n{\"jsonrpc\":\"2.0\",\"id\":\"b80d3a488580be1f3e9cb3e926175310\",\"result\":{\"currentPackageName\":\"com.android.mms\",\"displayHeight\":1920,\"displayRotation\":0,\"displaySizeDpX\":360,\"displaySizeDpY\":640,\"displayWidth\":1080,\"productName\"\r\n:\"odin\",\"screenOn\":true,\"sdkInt\":25,\"naturalOrientation\":true}}\r\n&lt;&lt;&lt; END<\/pre>\n<p>Notes:In below examples, we use&nbsp;d&nbsp;represent the uiautomator2 connect object<\/p>\n<h2>Table of Contents<\/h2>\n<ul>\n<li>Retrive the device info<\/li>\n<li>Key Event Actions of the device<\/li>\n<li>Gesture interaction of the device<\/li>\n<li>Screen Actions of the device<\/li>\n<\/ul>\n<ul>\n<li>Child and sibling UI object<\/li>\n<li>Get the selected ui object status and its information<\/li>\n<li>Perform the click action on the seleted ui object<\/li>\n<li>Gesture action for the specific ui object<\/li>\n<\/ul>\n<h4>TODO<\/h4>\n<h2>Basic API Usages<\/h2>\n<p>This part show the normal actions of the device through some simple examples<\/p>\n<h3>Retrive the device info<\/h3>\n<pre>\r\nd.info<\/pre>\n<p>Below is a possible result:<\/p>\n<pre>\r\n{ \r\n    u'displayRotation': 0,\r\n    u'displaySizeDpY': 640,\r\n    u'displaySizeDpX': 360,\r\n    u'currentPackageName': u'com.android.launcher',\r\n    u'productName': u'takju',\r\n    u'displayWidth': 720,\r\n    u'sdkInt': 18,\r\n    u'displayHeight': 1184,\r\n    u'naturalOrientation': True\r\n}<\/pre>\n<h3>Key Event Actions of the device<\/h3>\n<ul>\n<li>\n<p>Tun on\/off screen<\/p>\n<pre>\r\nd.screen_on() # turn on screen\r\nd.screen_off() # turn off screen<\/pre>\n<\/li>\n<li>\n<p>Get screen on\/off status<\/p>\n<pre>\r\nd.info.get('screenOn') # require android &gt;= 4.4<\/pre>\n<\/li>\n<li>\n<p>Press hard\/soft key<\/p>\n<pre>\r\nd.press(\"home\") # press home key\r\nd.press(\"back\") # the normal way to press back key\r\nd.press(0x07, 0x02) # press keycode 0x07('0') with META ALT(0x02)<\/pre>\n<\/li>\n<li>\n<p>Next keys are currently supported:<\/p>\n<ul>\n<li>home<\/li>\n<li>back<\/li>\n<li>left<\/li>\n<li>right<\/li>\n<li>up<\/li>\n<li>down<\/li>\n<li>center<\/li>\n<li>menu<\/li>\n<li>search<\/li>\n<li>enter<\/li>\n<li>delete ( or del)<\/li>\n<li>recent (recent apps)<\/li>\n<li>volume_up<\/li>\n<li>volume_down<\/li>\n<li>volume_mute<\/li>\n<li>camera<\/li>\n<li>power<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>You can find all key code definitions at&nbsp;Android KeyEvnet<\/p>\n<ul>\n<li>\n<p>Unlock screen<\/p>\n<pre>\r\nd.unlock()\r\n# 1. launch activity: com.github.uiautomator.ACTION_IDENTIFY\r\n# 2. press \"home\"<\/pre>\n<\/li>\n<\/ul>\n<h3>Gesture interaction of the device<\/h3>\n<ul>\n<li>\n<p>Click the screen<\/p>\n<pre>\r\nd.click(x, y)<\/pre>\n<\/li>\n<li>\n<p>Long click the screen<\/p>\n<pre>\r\nd.long_click(x, y)\r\nd.long_click(x, y, 0.5) # long click 0.5s (default)<\/pre>\n<\/li>\n<li>\n<p>Swipe<\/p>\n<pre>\r\nd.swipe(sx, sy, ex, ey)\r\nd.swipe(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)<\/pre>\n<\/li>\n<li>\n<p>Drag<\/p>\n<pre>\r\nd.drag(sx, sy, ex, ey)\r\nd.drag(sx, sy, ex, ey, 0.5) # swipe for 0.5s(default)<\/pre>\n<\/li>\n<\/ul>\n<p>Note: click, swipe, drag support percent position. Example:<\/p>\n<p>d.long_click(0.5, 0.5)&nbsp;means long click center of screen<\/p>\n<h3>Screen Actions of the device<\/h3>\n<ul>\n<li>\n<p>Retrieve\/Set Orientation<\/p>\n<p>The possible orientation is:<\/p>\n<ul>\n<li>natural&nbsp;or&nbsp;n<\/li>\n<li>left&nbsp;or&nbsp;l<\/li>\n<li>right&nbsp;or&nbsp;r<\/li>\n<li>upsidedown&nbsp;or&nbsp;u&nbsp;(can not be set)<\/li>\n<\/ul>\n<pre>\r\n# retrieve orientation, it may be \"natural\" or \"left\" or \"right\" or \"upsidedown\"\r\norientation = d.orientation\r\n\r\n# WARNING: not pass testing in my TT-M1\r\n# set orientation and freeze rotation.\r\n# notes: \"upsidedown\" can not be set until Android 4.3.\r\nd.set_orientation('l') # or \"left\"\r\nd.set_orientation(\"l\") # or \"left\"\r\nd.set_orientation(\"r\") # or \"right\"\r\nd.set_orientation(\"n\") # or \"natural\"<\/pre>\n<\/li>\n<li>\n<p>Freeze\/Un-Freeze rotation<\/p>\n<pre>\r\n# freeze rotation\r\nd.freeze_rotation()\r\n# un-freeze rotation\r\nd.freeze_rotation(False)<\/pre>\n<\/li>\n<li>\n<p>Take screenshot<\/p>\n<pre>\r\n# take screenshot and save to local file \"home.jpg\", can not work until Android 4.2.\r\nd.screenshot(\"home.jpg\")\r\n# get PIL.Image format, need install pillow first\r\nimage = d.screenshot()\r\nimage.save(\"home.jpg\") # or home.png\r\n\r\n# get opencv format, need install numpy and cv2\r\nimport cv2\r\nimage = d.screenshot(format='opencv')\r\ncv2.imwrite('home.jpg', image)<\/pre>\n<\/li>\n<li>\n<p>Dump Window Hierarchy<\/p>\n<pre>\r\n# or get the dumped content(unicode) from return.\r\nxml = d.dump_hierarchy()<\/pre>\n<\/li>\n<li>\n<p>Open notification or quick settings<\/p>\n<pre>\r\nd.open_notification()\r\nd.open_quick_settings()<\/pre>\n<\/li>\n<\/ul>\n<h3>Push and pull file<\/h3>\n<ul>\n<li>\n<p>push file into device<\/p>\n<pre>\r\n# push into a folder\r\nd.push(\"foo.txt\", \"\/sdcard\/\")\r\n# push and rename\r\nd.push(\"foo.txt\", \"\/sdcard\/bar.txt\")\r\n# push fileobj\r\nwith open(\"foo.txt\", 'rb') as f:\r\n    d.push(f, \"\/sdcard\/\")\r\n# push and change file mode\r\nd.push(\"foo.sh\", \"\/data\/local\/tmp\/\", mode=0o755)<\/pre>\n<\/li>\n<li>\n<p>pull file from device<\/p>\n<pre>\r\nd.pull(\"\/sdcard\/tmp.txt\", \"tmp.txt\")\r\n\r\n# FileNotFoundError will raise if file not found in device\r\nd.pull(\"\/sdcard\/some-file-not-exists.txt\", \"tmp.txt\")<\/pre>\n<\/li>\n<\/ul>\n<h3>App management<\/h3>\n<p>Include app install, launch and stop<\/p>\n<h4>App install<\/h4>\n<p>Only support install from url for now.<\/p>\n<pre>\r\nd.app_install('http:\/\/some-domain.com\/some.apk')<\/pre>\n<h4>App launch<\/h4>\n<pre>\r\nd.app_start(\"com.example.hello_world\") # start with package name<\/pre>\n<h4>App stop<\/h4>\n<pre>\r\n# perform am force-stop\r\nd.app_stop(\"com.example.hello_world\") \r\n# perform pm clear\r\nd.app_clear('com.example.hello_world')<\/pre>\n<h4>App stop all the runnings<\/h4>\n<pre>\r\n# stop all\r\nd.app_stop_all()\r\n# stop all app except com.examples.demo\r\nd.app_stop_all(excludes=['com.examples.demo'])<\/pre>\n<h3>Selector<\/h3>\n<p>Selector is to identify specific ui object in current window.<\/p>\n<pre>\r\n# To seleted the object ,text is 'Clock' and its className is 'android.widget.TextView'\r\nd(text='Clock', className='android.widget.TextView')<\/pre>\n<p>Selector supports below parameters. Refer to&nbsp;UiSelector java doc&nbsp;for detailed information.<\/p>\n<ul>\n<li>text&nbsp;,&nbsp;textContains&nbsp;,&nbsp;textMatches&nbsp;,&nbsp;textStartsWith<\/li>\n<li>className&nbsp;,&nbsp;classNameMatches<\/li>\n<li>description&nbsp;,&nbsp;descriptionContains&nbsp;,&nbsp;descriptionMatches&nbsp;,&nbsp;descriptionStartsWith<\/li>\n<li>checkable&nbsp;,&nbsp;checked&nbsp;,&nbsp;clickable&nbsp;,&nbsp;longClickable<\/li>\n<li>scrollable&nbsp;,&nbsp;enabled&nbsp;,&nbsp;focusable&nbsp;,&nbsp;focused&nbsp;,&nbsp;selected<\/li>\n<li>packageName&nbsp;,&nbsp;packageNameMatches<\/li>\n<li>resourceId&nbsp;,&nbsp;resourceIdMatches<\/li>\n<li>index&nbsp;,&nbsp;instance<\/li>\n<\/ul>\n<h4>Child and sibling UI object<\/h4>\n<ul>\n<li>\n<p>child<\/p>\n<pre>\r\n# get the child or grandchild\r\nd(className=\"android.widget.ListView\").child(text=\"Bluetooth\")<\/pre>\n<\/li>\n<li>\n<p>sibling<\/p>\n<pre>\r\n# get sibling or child of sibling\r\nd(text=\"Google\").sibling(className=\"android.widget.ImageView\")<\/pre>\n<\/li>\n<li>\n<p>child by text or description or instance<\/p>\n<pre>\r\n# get the child match className=\"android.widget.LinearLayout\"\r\n# and also it or its child or grandchild contains text \"Bluetooth\"\r\nd(className=\"android.widget.ListView\", resourceId=\"android:id\/list\") \\\r\n .child_by_text(\"Bluetooth\", className=\"android.widget.LinearLayout\")\r\n\r\n# allow scroll search to get the child\r\nd(className=\"android.widget.ListView\", resourceId=\"android:id\/list\") \\\r\n .child_by_text(\r\n    \"Bluetooth\",\r\n    allow_scroll_search=True,\r\n    className=\"android.widget.LinearLayout\"\r\n  )<\/pre>\n<ul>\n<li>\n<p>child_by_description&nbsp;is to find child which or which's grandchild contains the specified description, others are the same as&nbsp;child_by_text&nbsp;.<\/p>\n<\/li>\n<li>\n<p>child_by_instance&nbsp;is to find child which has a child UI element anywhere within its sub hierarchy that is at the instance specified. It is performed on visible views without&nbsp;scrolling.<\/p>\n<\/li>\n<\/ul>\n<p>See below links for detailed information:<\/p>\n<ul>\n<li>UiScrollable&nbsp;,&nbsp;getChildByDescription&nbsp;,&nbsp;getChildByText&nbsp;,&nbsp;getChildByInstance<\/li>\n<li>UiCollection&nbsp;,&nbsp;getChildByDescription&nbsp;,&nbsp;getChildByText&nbsp;,&nbsp;getChildByInstance<\/li>\n<\/ul>\n<p>Above methods support chained invoking, e.g. for below hierarchy<\/p>\n<pre>\r\n&lt;node index=\"0\" text=\"\" resource-id=\"android:id\/list\" class=\"android.widget.ListView\" ...&gt;\r\n  &lt;node index=\"0\" text=\"WIRELESS &amp; NETWORKS\" resource-id=\"\" class=\"android.widget.TextView\" ...\/&gt;\r\n  &lt;node index=\"1\" text=\"\" resource-id=\"\" class=\"android.widget.LinearLayout\" ...&gt;\r\n    &lt;node index=\"1\" text=\"\" resource-id=\"\" class=\"android.widget.RelativeLayout\" ...&gt;\r\n      &lt;node index=\"0\" text=\"Wi\u2011Fi\" resource-id=\"android:id\/title\" class=\"android.widget.TextView\" ...\/&gt;\r\n    &lt;\/node&gt;\r\n    &lt;node index=\"2\" text=\"ON\" resource-id=\"com.android.settings:id\/switchWidget\" class=\"android.widget.Switch\" ...\/&gt;\r\n  &lt;\/node&gt;\r\n  ...\r\n&lt;\/node&gt;<\/pre>\n<\/p>\n<p>We want to click the switch at the right side of text 'Wi\u2011Fi' to turn on\/of Wi\u2011Fi. As there are several switches with almost the same properties, so we can not use like&nbsp;d(className=\"android.widget.Switch\")&nbsp;to select the ui object. Instead, we can use code below to select it.<\/p>\n<pre>\r\nd(className=\"android.widget.ListView\", resourceId=\"android:id\/list\") \\\r\n  .child_by_text(\"Wi\u2011Fi\", className=\"android.widget.LinearLayout\") \\\r\n  .child(className=\"android.widget.Switch\") \\\r\n  .click()<\/pre>\n<\/li>\n<li>\n<p>relative position<\/p>\n<p>Also we can use the relative position methods to get the view:&nbsp;left&nbsp;,&nbsp;right&nbsp;,&nbsp;top&nbsp;,&nbsp;bottom.<\/p>\n<ul>\n<li>d(A).left(B)&nbsp;, means selecting B on the left side of A.<\/li>\n<li>d(A).right(B)&nbsp;, means selecting B on the right side of A.<\/li>\n<li>d(A).up(B)&nbsp;, means selecting B above A.<\/li>\n<li>d(A).down(B)&nbsp;, means selecting B under A.<\/li>\n<\/ul>\n<p>So for above case, we can write code alternatively:<\/p>\n<pre>\r\n## select \"switch\" on the right side of \"Wi\u2011Fi\"\r\nd(text=\"Wi\u2011Fi\").right(className=\"android.widget.Switch\").click()<\/pre>\n<\/li>\n<li>\n<p>Multiple instances<\/p>\n<p>Sometimes the screen may contain multiple views with the same e.g. text, then you will have to use \"instance\" properties in selector like below:<\/p>\n<pre>\r\nd(text=\"Add new\", instance=0)  # which means the first instance with text \"Add new\"<\/pre>\n<p>However, uiautomator provides list like methods to use it.<\/p>\n<pre>\r\n# get the count of views with text \"Add new\" on current screen\r\nd(text=\"Add new\").count\r\n\r\n# same as count property\r\nlen(d(text=\"Add new\"))\r\n\r\n# get the instance via index\r\nd(text=\"Add new\")[0]\r\nd(text=\"Add new\")[1]\r\n...\r\n\r\n# iterator\r\nfor view in d(text=\"Add new\"):\r\n    view.info  # ...<\/pre>\n<p>Notes: when you are using selector like a list, you must make sure the screen keep unchanged, else you may get ui not found error.<\/p>\n<\/li>\n<\/ul>\n<h4>Get the selected ui object status and its information<\/h4>\n<ul>\n<li>\n<p>Check if the specific ui object exists<\/p>\n<pre>\r\nd(text=\"Settings\").exists # True if exists, else False\r\nd.exists(text=\"Settings\") # alias of above property.<\/pre>\n<\/li>\n<li>\n<p>Retrieve the info of the specific ui object<\/p>\n<pre>\r\nd(text=\"Settings\").info<\/pre>\n<p>Below is a possible result:<\/p>\n<pre>\r\n{ u'contentDescription': u'',\r\nu'checked': False,\r\nu'scrollable': False,\r\nu'text': u'Settings',\r\nu'packageName': u'com.android.launcher',\r\nu'selected': False,\r\nu'enabled': True,\r\nu'bounds': {u'top': 385,\r\n            u'right': 360,\r\n            u'bottom': 585,\r\n            u'left': 200},\r\nu'className': u'android.widget.TextView',\r\nu'focused': False,\r\nu'focusable': True,\r\nu'clickable': True,\r\nu'chileCount': 0,\r\nu'longClickable': True,\r\nu'visibleBounds': {u'top': 385,\r\n                    u'right': 360,\r\n                    u'bottom': 585,\r\n                    u'left': 200},\r\nu'checkable': False\r\n}<\/pre>\n<\/li>\n<li>\n<p>Set\/Clear text of editable field<\/p>\n<pre>\r\nd(text=\"Settings\").clear_text()  # clear the text\r\nd(text=\"Settings\").set_text(\"My text...\")  # set the text<\/pre>\n<\/li>\n<\/ul>\n<h4>Perform the click action on the seleted ui object<\/h4>\n<ul>\n<li>\n<p>Perform click on the specific ui object<\/p>\n<pre>\r\n# click on the center of the specific ui object\r\nd(text=\"Settings\").click()\r\n# wait element show for 10 seconds(Default)\r\nd(text=\"Settings\").click(timeout=10)\r\n# alias of click\r\n# short name for quick type with keyboard\r\nd(text=\"Settings\").tap()\r\n# wait element show for 0 seconds\r\nd(text=\"Settings\").tap_nowait()<\/pre>\n<\/li>\n<li>\n<p>Perform long click on the specific ui object<\/p>\n<pre>\r\n# long click on the center of the specific ui object\r\nd(text=\"Settings\").long_click()<\/pre>\n<\/li>\n<\/ul>\n<h4>Gesture action for the specific ui object<\/h4>\n<ul>\n<li>\n<p>Drag the ui object to another point or ui object<\/p>\n<pre>\r\n# notes : drag can not be set until Android 4.3.\r\n# drag the ui object to point (x, y)\r\nd(text=\"Settings\").drag_to(x, y, duration=0.5)\r\n# drag the ui object to another ui object(center)\r\nd(text=\"Settings\").drag_to(text=\"Clock\", duration=0.25)<\/pre>\n<\/li>\n<li>\n<p>Two point gesture from one point to another<\/p>\n<pre>\r\nd(text=\"Settings\").gesture((sx1, sy1), (sx2, sy2), (ex1, ey1), (ex2, ey2))<\/pre>\n<\/li>\n<li>\n<p>Two point gesture on the specific ui object<\/p>\n<p>Supports two gestures:<\/p>\n<ul>\n<li>In&nbsp;, from edge to center<\/li>\n<li>Out&nbsp;, from center to edge<\/li>\n<\/ul>\n<pre>\r\n# notes : pinch can not be set until Android 4.3.\r\n# from edge to center. here is \"In\" not \"in\"\r\nd(text=\"Settings\").pinch_in(percent=100, steps=10)\r\n# from center to edge\r\nd(text=\"Settings\").pinch_out()<\/pre>\n<\/li>\n<li>\n<p>Wait until the specific ui appears or gone<\/p>\n<pre>\r\n# wait until the ui object appears\r\nd(text=\"Settings\").wait(timeout=3.0) # return bool\r\n# wait until the ui object gone\r\nd(text=\"Settings\").wait_gone(timeout=1.0)<\/pre>\n<p>Default timeout is 20s. see&nbsp;global settings&nbsp;for more details<\/p>\n<\/li>\n<li>\n<p>Perform fling on the specific ui object(scrollable)<\/p>\n<p>Possible properties:<\/p>\n<ul>\n<li>horiz&nbsp;or&nbsp;vert<\/li>\n<li>forward&nbsp;or&nbsp;backward&nbsp;or&nbsp;toBeginning&nbsp;or&nbsp;toEnd<\/li>\n<\/ul>\n<pre>\r\n# fling forward(default) vertically(default) \r\nd(scrollable=True).fling()\r\n# fling forward horizentally\r\nd(scrollable=True).fling.horiz.forward()\r\n# fling backward vertically\r\nd(scrollable=True).fling.vert.backward()\r\n# fling to beginning horizentally\r\nd(scrollable=True).fling.horiz.toBeginning(max_swipes=1000)\r\n# fling to end vertically\r\nd(scrollable=True).fling.toEnd()<\/pre>\n<\/li>\n<li>\n<p>Perform scroll on the specific ui object(scrollable)<\/p>\n<p>Possible properties:<\/p>\n<ul>\n<li>horiz&nbsp;or&nbsp;vert<\/li>\n<li>forward&nbsp;or&nbsp;backward&nbsp;or&nbsp;toBeginning&nbsp;or&nbsp;toEnd&nbsp;, or&nbsp;to<\/li>\n<\/ul>\n<pre>\r\n# scroll forward(default) vertically(default)\r\nd(scrollable=True).scroll(steps=10)\r\n# scroll forward horizentally\r\nd(scrollable=True).scroll.horiz.forward(steps=100)\r\n# scroll backward vertically\r\nd(scrollable=True).scroll.vert.backward()\r\n# scroll to beginning horizentally\r\nd(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)\r\n# scroll to end vertically\r\nd(scrollable=True).scroll.toEnd()\r\n# scroll forward vertically until specific ui object appears\r\nd(scrollable=True).scroll.to(text=\"Security\")<\/pre>\n<\/li>\n<\/ul>\n<h3>Watcher<\/h3>\n<p>You can register&nbsp;watcher&nbsp;to perform some actions when a selector can not find a match.<\/p>\n<ul>\n<li>\n<p>Register Watcher<\/p>\n<p>When a selector can not find a match, uiautomator will run all registered watchers.<\/p>\n<ul>\n<li>Click target when conditions match<\/li>\n<\/ul>\n<pre>\r\nd.watcher(\"AUTO_FC_WHEN_ANR\").when(text=\"ANR\").when(text=\"Wait\") \\\r\n                             .click(text=\"Force Close\")\r\n# d.watcher(name) ## creates a new named watcher.\r\n#  .when(condition)  ## the UiSelector condition of the watcher.\r\n#  .click(target)  ## perform click action on the target UiSelector.<\/pre>\n<ul>\n<li>Press key when conditions match<\/li>\n<\/ul>\n<pre>\r\nd.watcher(\"AUTO_FC_WHEN_ANR\").when(text=\"ANR\").when(text=\"Wait\") \\\r\n                             .press(\"back\", \"home\")\r\n# d.watcher(name) ## creates a new named watcher.\r\n#  .when(condition)  ## the UiSelector condition of the watcher.\r\n#  .press(&lt;keyname&gt;, ..., &lt;keyname&gt;.()  ## press keys one by one in sequence.<\/pre>\n<\/li>\n<li>\n<p>Check if the named watcher triggered<\/p>\n<p>A watcher is triggered, which means the watcher was run and all its conditions matched.<\/p>\n<pre>\r\nd.watcher(\"watcher_name\").triggered\r\n# true in case of the specified watcher triggered, else false<\/pre>\n<\/li>\n<li>\n<p>Remove named watcher<\/p>\n<pre>\r\n# remove the watcher\r\nd.watcher(\"watcher_name\").remove()<\/pre>\n<\/li>\n<li>\n<p>List all watchers<\/p>\n<pre>\r\nd.watchers\r\n# a list of all registered wachers' names<\/pre>\n<\/li>\n<li>\n<p>Check if there is any watcher triggered<\/p>\n<pre>\r\nd.watchers.triggered\r\n#  true in case of any watcher triggered<\/pre>\n<\/li>\n<li>\n<p>Reset all triggered watchers<\/p>\n<pre>\r\n# reset all triggered watchers, after that, d.watchers.triggered will be false.\r\nd.watchers.reset()<\/pre>\n<\/li>\n<li>\n<p>Remvoe watchers<\/p>\n<pre>\r\n# remove all registered watchers\r\nd.watchers.remove()\r\n# remove the named watcher, same as d.watcher(\"watcher_name\").remove()\r\nd.watchers.remove(\"watcher_name\")<\/pre>\n<\/li>\n<li>\n<p>Force to run all watchers<\/p>\n<pre>\r\n# force to run all registered watchers\r\nd.watchers.run()<\/pre>\n<\/li>\n<\/ul>\n<p>\u53e6\u5916\u6587\u6863\u8fd8\u662f\u6709\u5f88\u591a\u6ca1\u6709\u5199\uff0c\u63a8\u8350\u76f4\u63a5\u53bb\u770b\u6e90\u7801&nbsp;init&nbsp;.py<\/p>\n<h3>Global settings<\/h3>\n<pre>\r\n# set delay 1.5s after each UI click and click\r\nd.click_post_delay = 1.5 # default no delay\r\n\r\n# set default element wait timeout (seconds)\r\nd.wait_timeout = 30.0 # default 20.0<\/pre>\n<h3>\u4e2d\u6587\u5b57\u7b26\u7684\u8f93\u5165<\/h3>\n<p>\u8fd9\u79cd\u65b9\u6cd5\u901a\u5e38\u7528\u4e8e\u4e0d\u77e5\u9053\u63a7\u4ef6\u7684\u60c5\u51b5\u4e0b\u7684\u8f93\u5165\u3002\u7b2c\u4e00\u6b65\u9700\u8981\u5207\u6362\u8f93\u5165\u6cd5\uff0c\u7136\u540e\u53d1\u9001adb\u5e7f\u64ad\u547d\u4ee4\uff0c\u5177\u4f53\u4f7f\u7528\u65b9\u6cd5\u5982\u4e0b<\/p>\n<pre>\r\nd.set_fastinput_ime(True) # \u5207\u6362\u6210FastInputIME\u8f93\u5165\u6cd5\r\nd.send_keys(\"\u4f60\u597d123abcEFG\") # adb\u5e7f\u64ad\u8f93\u5165\r\nd.set_fastinput_ime(False) # \u5207\u6362\u6210\u6b63\u5e38\u7684\u8f93\u5165\u6cd5<\/pre>\n<h2>\u6d4b\u8bd5\u65b9\u6cd5<\/h2>\n<pre>\r\n$ adb forward tcp:9008 tcp:9008\r\n$ curl 127.0.0.1:9008\/ping\r\n# expect: pong\r\n\r\n$ curl -d '{\"jsonrpc\":\"2.0\",\"method\":\"deviceInfo\",\"id\":1}' 127.0.0.1:9008\/jsonrpc\/0\r\n# expect JSON output<\/pre>\n<h2>Uiautomator\u4e0eUiautomator2\u7684\u533a\u522b<\/h2>\n<ol>\n<li>api\u4e0d\u540c\u4f46\u4e5f\u5dee\u4e0d\u591a<\/li>\n<li>Uiautomator2\u662f\u5b89\u5353\u9879\u76ee\uff0c\u800cUiautomator\u662fjava\u9879\u76ee<\/li>\n<li>Uiautomator2\u53ef\u4ee5\u8f93\u5165\u4e2d\u6587\uff0c\u800cUiautomator\u7684java\u5de5\u7a0b\u9700\u501f\u52a9utf7\u8f93\u5165\u6cd5\u624d\u80fd\u8f93\u5165\u4e2d\u6587<\/li>\n<li>Uiautomator2\u5fc5\u987b\u660e\u786eEditText\u6846\u624d\u80fd\u5411\u91cc\u9762\u8f93\u5165\u6587\u5b57\uff0cUiautomator\u76f4\u63a5\u6307\u5b9a\u7236\u7c7b\u4e5f\u53ef\u4ee5\u5728\u5b50\u7c7b\u4e2d\u8f93\u5165\u6587\u5b57<\/li>\n<li>Uiautomator2\u83b7\u53d6\u63a7\u4ef6\u901f\u5ea6\u5feb\u5199\uff0c\u800cUiautomator\u83b7\u53d6\u901f\u5ea6\u6162\u4e00\u4e9b;<\/li>\n<\/ol>\n<h2>\u5e38\u89c1\u95ee\u9898<\/h2>\n<ol>\n<li>\n<p>\u63d0\u793a&nbsp;502&nbsp;\u9519\u8bef<\/p>\n<p>\u5c1d\u8bd5\u624b\u673a\u8fde\u63a5PC\uff0c\u7136\u540e\u8fd0\u884c\u4e0b\u9762\u7684\u547d\u4ee4<\/p>\n<pre>\r\nadb shell am instrument -w -r  -e debug false -e class com.github.uiautomator.stub.Stub \\\r\n\tcom.github.uiautomator.test\/android.support.test.runner.AndroidJUnitRunner<\/pre>\n<p>\u5982\u679c\u8fd0\u884c\u6b63\u5e38\uff0c\u542f\u52a8\u6d4b\u8bd5\u4e4b\u524d\u589e\u52a0\u4e00\u884c\u4ee3\u7801&nbsp;d.healthcheck()<\/p>\n<p>\u5982\u679c\u62a5\u9519\uff0c\u53ef\u80fd\u662f\u7f3a\u5c11\u67d0\u4e2aapk\u6ca1\u6709\u5b89\u88c5\uff0c\u4f7f\u7528\u4e0b\u9762\u7684\u547d\u4ee4\u91cd\u65b0\u521d\u59cb\u5316&nbsp;python -m uiautomator2 init --reinstall<\/p>\n<\/li>\n<\/ol>\n<h2>\u5c1d\u9c9c\u529f\u80fd<\/h2>\n<p>\u624b\u673a&nbsp;python -muiautomator2 init&nbsp;\u4e4b\u540e\uff0c\u6d4f\u89c8\u5668\u8f93\u5165 &lt;\u624b\u673aIP:7912&gt;\uff0c\u4f1a\u53d1\u73b0\u4e00\u4e2a\u8fdc\u7a0b\u63a7\u5236\u529f\u80fd\uff0c\u5ef6\u8fdf\u975e\u5e38\u4f4e\u5662\u3002^_^<\/p>\n<h2>ABOUT<\/h2>\n<p>\u9879\u76ee\u91cd\u6784\u81ea&nbsp;https:\/\/github.com\/openatx\/atx-uiautomator<\/p>\n<h2>CHANGELOG<\/h2>\n<p>Auto generated by pbr:&nbsp;CHANGELOG<\/p>\n<h2>\u4f9d\u8d56\u9879\u76ee<\/h2>\n<ul>\n<li>uiautomator\u5b88\u62a4\u7a0b\u5e8f&nbsp;https:\/\/github.com\/openatx\/atx-agent<\/li>\n<li>uiautomator jsonrpc server&nbsp;https:\/\/github.com\/openatx\/android-uiautomator-server\/<\/li>\n<\/ul>\n<h2>Contributors<\/h2>\n<ul>\n<li>codeskyblue (&nbsp;@codeskyblue&nbsp;)<\/li>\n<li>Xiaocong He (&nbsp;@xiaocong&nbsp;)<\/li>\n<li>Yuanyuan Zou (&nbsp;@yuanyuan&nbsp;)<\/li>\n<li>Qian Jin (&nbsp;@QianJin2013&nbsp;)<\/li>\n<li>Xu Jingjie (&nbsp;@xiscoxu&nbsp;)<\/li>\n<li>Xia Mingyuan (&nbsp;@mingyuan-xia&nbsp;)<\/li>\n<li>Artem Iglikov, Google Inc. (&nbsp;@artikz&nbsp;)<\/li>\n<\/ul>\n<p>Others&nbsp;contributors<\/p>\n<h2>LICENSE<\/h2>\n<p>Under&nbsp;MIT<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp;[[217058]] uiautomator2 Android Uiautomator2 Pyth [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":201048,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[195111],"tags":[],"class_list":["post-201047","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android"],"_links":{"self":[{"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/posts\/201047","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/comments?post=201047"}],"version-history":[{"count":0,"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/posts\/201047\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/media\/201048"}],"wp:attachment":[{"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/media?parent=201047"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/categories?post=201047"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/idc.net\/help\/wp-json\/wp\/v2\/tags?post=201047"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}