https://github.com/xiaocong/uiautomator

This module is a Python wrapper of Android uiautomator testing framework. It works on Android 4.1+ simply with Android device attached via adb, no need to install anything on Android device.

from uiautomator import device as d

d.screen.on()
d(text="Clock").click()

Installation

$ pip install uiautomator

Pre-requirements

  • Install Android SDK, and set ANDROID_HOME environment to the correct path.
  • Enable ADB setting on device and connect your android device using usb with your PC.
  • Allow apps to install from unknown sources on device settings.

import uiautomator

  • If ANDROID_SERIAL is defined in environment, or there is only one device connected:

    from uiautomator import device as d
    
  • Speficy the serial number when retrieving the device object

    from uiautomator import Device
    
    d = Device('014E05DE0F02000E')
    
  • Speficy the adb server host and port running on other computer

    Although adb supports -a option since SDK 4.3, but now it has a bug on it. The only way to start adb server listenning on all interfaces instead of localhost, is adb -a -P 5037 fork-server server &

    from uiautomator import Device
    
    d = Device('014E05DE0F02000E', adb_server_host='192.168.1.68', adb_server_port=5037)
    

Notes: In below examples, we use d represent the android device object.

Table of Contents

Basic API Usages

Watcher introduction

Handler introduction

Selector introduction

Basic API Usages

This part show the normal actions of the device through some simple examples.

  • Retrieve the device info

    d.info
    

    Below is a possible result:

    { 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
    }
    
    

Key Event Actions of the device

  • Turn on/off screen

    # Turn on screen
    d.screen.on()
    # Turn off screen
    d.screen.off()
    

    Alternative method is:

    # wakeup the device
    d.wakeup()
    # sleep the device, same as turning off the screen.
    d.sleep()
    
  • Check if the screen is on or off

    if d.screen == "on":  # of d.screen != "off"
        # do something in case of screen on
        pass
    if d.screen == "off":  # of d.screen != "on"
        # do something in case of screen off
        pass
    
  • Press hard/soft key

    # press home key
    d.press.home()
    # press back key
    d.press.back()
    # the normal way to press back key
    d.press("back")
    # press keycode 0x07('0') with META ALT(0x02) on
    d.press(0x07, 0x02)
    
  • Next keys are currently supported:

    • home
    • back
    • left
    • right
    • up
    • down
    • center
    • menu
    • search
    • enter
    • delete(or del)
    • recent(recent apps)
    • volume_up
    • volume_down
    • volume_mute
    • camera
    • power

    You can find all key code definitions at Android KeyEvent.

Gesture interaction of the device

  • Click the screen

    # click (x, y) on screen
    d.click(x, y)
    
  • Long click the screen

    # long click (x, y) on screen
    d.long_click(x, y)
    
  • Swipe

    # swipe from (sx, sy) to (ex, ey)
    d.swipe(sx, sy, ex, ey)
    # swipe from (sx, sy) to (ex, ey) with 10 steps
    d.swipe(sx, sy, ex, ey, steps=10)
    
  • Drag

    # drag from (sx, sy) to (ex, ey)
    d.drag(sx, sy, ex, ey)
    # drag from (sx, sy) to (ex, ey) with 10 steps
    d.drag(sx, sy, ex, ey, steps=10)
    

Screen Actions of the device

  • Retrieve/Set Orientation

    The possible orientation is:

    • natural or n
    • left or l
    • right or r
    • upsidedown or u (can not be set)
    # retrieve orientation, it may be "natural" or "left" or "right" or "upsidedown"
    orientation = d.orientation
    # set orientation and freeze rotation.
    # notes: "upsidedown" can not be set until Android 4.3.
    d.orientation = "l" # or "left"
    d.orientation = "r" # or "right"
    d.orientation = "n" # or "natural"
    
  • Freeze/Un-Freeze rotation

    # freeze rotation
    d.freeze_rotation()
    # un-freeze rotation
    d.freeze_rotation(False)
    
  • Take screenshot

    # take screenshot and save to local file "home.png", can not work until Android 4.2.
    d.screenshot("home.png")
    
  • Dump Window Hierarchy

    # dump the widown hierarchy and save to local file "hierarchy.xml"
    d.dump("hierarchy.xml")
    # or get the dumped content(unicode) from return.
    xml = d.dump()
    
  • Open notification or quick settings

    # open notification, can not work until Android 4.3.
    d.open.notification()
    # open quick settings, can not work until Android 4.3.
    d.open.quick_settings()
    
  • Wait for idle or window update

    # wait for current window to idle
    d.wait.idle()
    # wait until window update event occurs
    d.wait.update()
    

Watcher

You can register watcher to perform some actions when a selector can not find a match.

  • Register Watcher

    When a selector can not find a match, uiautomator will run all registered watchers.

    • Click target when conditions match
    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.
    
    • Press key when conditions match
    d.watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
                                 .press.back.home()
    # Alternative way to define it as below
    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.
    #  Alternavie way defining key sequence is press(<keybname>, ..., <keyname>)
    
  • Check if the named watcher triggered

    A watcher is triggered, which means the watcher was run and all its conditions matched.

    d.watcher("watcher_name").triggered
    # true in case of the specified watcher triggered, else false
    
  • Remove named watcher

    # remove the watcher
    d.watcher("watcher_name").remove()
    
  • List all watchers

    d.watchers
    # a list of all registered wachers' names
    
  • Check if there is any watcher triggered

    d.watchers.triggered
    #  true in case of any watcher triggered
    
  • Reset all triggered watchers

    # reset all triggered watchers, after that, d.watchers.triggered will be false.
    d.watchers.reset()
    
  • Remvoe watchers

    # remove all registered watchers
    d.watchers.remove()
    # remove the named watcher, same as d.watcher("watcher_name").remove()
    d.watchers.remove("watcher_name")
    
  • Force to run all watchers

    # force to run all registered watchers
    d.watchers.run()
    

Handler

The functionality of handler is same as Watcher, except it is implemented ourside of Android uiautomator. The most different usage between handler and watcher is, handler can use customized callback function.

def fc_close(device):
  if device(text='Force Close').exists:
    device(text='Force Close').click()
  return True  # return True means to break the loop of handler callback functions.

# turn on the handler callback function
d.handlers.on(fc_close)

# turn off the handler callback function
d.handlers.off(fc_close)

Selector

Selector is to identify specific ui object in current window.

# To seleted the object ,text is 'Clock' and its className is 'android.widget.TextView'
d(text='Clock', className='android.widget.TextView')

Selector supports below parameters. Refer to UiSelector java doc for detailed information.

  • text, textContains, textMatches, textStartsWith
  • className, classNameMatches
  • description, descriptionContains, descriptionMatches, descriptionStartsWith
  • checkable, checked, clickable, longClickable
  • scrollable, enabled,focusable, focused, selected
  • packageName, packageNameMatches
  • resourceId, resourceIdMatches
  • index, instance

Child and sibling UI object

  • child

    # get the child or grandchild
    d(className="android.widget.ListView").child(text="Bluetooth")
    
  • sibling

    # get sibling or child of sibling
    d(text="Google").sibling(className="android.widget.ImageView")
    
  • child by text or description or instance

    # get the child match className="android.widget.LinearLayout"
    # and also it or its child or grandchild contains text "Bluetooth"
    d(className="android.widget.ListView", resourceId="android:id/list") \
     .child_by_text("Bluetooth", className="android.widget.LinearLayout")
    
    # allow scroll search to get the child
    d(className="android.widget.ListView", resourceId="android:id/list") \
     .child_by_text(
        "Bluetooth",
        allow_scroll_search=True,
        className="android.widget.LinearLayout"
      )
    
    • child_by_description is to find child which or which's grandchild contains the specified description, others are the same as child_by_text.

    • child_by_instance 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 scrolling.

    See below links for detailed information:

    • UiScrollable, getChildByDescription, getChildByText, getChildByInstance
    • UiCollection, getChildByDescription, getChildByText, getChildByInstance

    Above methods support chained invoking, e.g. for below hierarchy

    <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>
    

    [图片上传失败...(image-2c8410-1597048069587)]

    We want to click the switch at the right side of text 'Wi‑Fi' to turn on/of Wi‑Fi. As there are several switches with almost the same properties, so we can not use like d(className="android.widget.Switch") to select the ui object. Instead, we can use code below to select it.

    d(className="android.widget.ListView", resourceId="android:id/list") \
      .child_by_text("Wi‑Fi", className="android.widget.LinearLayout") \
      .child(className="android.widget.Switch") \
      .click()
    
  • relative position

    Also we can use the relative position methods to get the view: left, right, top, bottom.

    • d(A).left(B), means selecting B on the left side of A.
    • d(A).right(B), means selecting B on the right side of A.
    • d(A).up(B), means selecting B above A.
    • d(A).down(B), means selecting B under A.

    So for above case, we can write code alternatively:

    ## select "switch" on the right side of "Wi‑Fi"
    d(text="Wi‑Fi").right(className="android.widget.Switch").click()
    
  • Multiple instances

    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:

    d(text="Add new", instance=0)  # which means the first instance with text "Add new"
    

    However, uiautomator provides list like methods to use it.

    # get the count of views with text "Add new" on current screen
    d(text="Add new").count
    
    # same as count property
    len(d(text="Add new"))
    
    # get the instance via index
    d(text="Add new")[0]
    d(text="Add new")[1]
    ...
    
    # iterator
    for view in d(text="Add new"):
        view.info  # ...
    

    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.

Get the selected ui object status and its information

  • Check if the specific ui object exists

    d(text="Settings").exists # True if exists, else False
    d.exists(text="Settings") # alias of above property.
    
  • Retrieve the info of the specific ui object

    d(text="Settings").info
    

    Below is a possible result:

    { 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
    }
    
    
  • Set/Clear text of editable field

    d(text="Settings").clear_text()  # clear the text
    d(text="Settings").set_text("My text...")  # set the text
    

Perform the click action on the seleted ui object

  • Perform click on the specific ui object

    # click on the center of the specific ui object
    d(text="Settings").click()
    # click on the bottomright corner of the specific ui object
    d(text="Settings").click.bottomright()
    # click on the topleft corner of the specific ui object
    d(text="Settings").click.topleft()
    # click and wait until the new window update
    d(text="Settings").click.wait()
    
  • Perform long click on the specific ui object

    # long click on the center of the specific ui object
    d(text="Settings").long_click()
    # long click on the bottomright corner of the specific ui object
    d(text="Settings").long_click.bottomright()
    # long click on the topleft corner of the specific ui object
    d(text="Settings").long_click.topleft()
    

Gesture action for the specific ui object

  • Drag the ui object to another point or ui object

    # notes : drag can not be set until Android 4.3.
    # drag the ui object to point (x, y)
    d(text="Settings").drag.to(x, y, steps=100)
    # drag the ui object to another ui object(center)
    d(text="Settings").drag.to(text="Clock", steps=50)
    
  • Swipe from the center of the ui object to its edge

    Swipe supports 4 directions:

    • left
    • right
    • top
    • bottom
    d(text="Settings").swipe.right()
    d(text="Settings").swipe.left(steps=10)
    d(text="Settings").swipe.up(steps=10)
    d(text="Settings").swipe.down()
    
  • Two point gesture from one point to another

    d(text="Settings").gesture((sx1, sy1), (sx2, sy2)) \
                      .to((ex1, ey1), (ex2, ey2))
    
  • Two point gesture on the specific ui object

    Supports two gestures:

    • In, from edge to center
    • Out, 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 edge
    d(text="Settings").pinch.Out()
    
  • 3 point gesture

    d().gestureM((sx1, sy1), (sx2, sy2),(sx3, sy3)) \
                      .to((ex1, ey1), (ex2, ey2),(ex3,ey3))
    d().gestureM((100,200),(300,200),(600,200),(100,600),(300,600),(600,900))
    
  • Wait until the specific ui object appears or gone

    # wait until the ui object appears
    d(text="Settings").wait.exists(timeout=3000)
    # wait until the ui object gone
    d(text="Settings").wait.gone(timeout=1000)
    
  • Perform fling on the specific ui object(scrollable)

    Possible properties:

    • horiz or vert
    • forward or backward or toBeginning or toEnd
    # fling forward(default) vertically(default) 
    d(scrollable=True).fling()
    # fling forward horizentally
    d(scrollable=True).fling.horiz.forward()
    # fling backward vertically
    d(scrollable=True).fling.vert.backward()
    # fling to beginning horizentally
    d(scrollable=True).fling.horiz.toBeginning(max_swipes=1000)
    # fling to end vertically
    d(scrollable=True).fling.toEnd()
    
  • Perform scroll on the specific ui object(scrollable)

    Possible properties:

    • horiz or vert
    • forward or backward or toBeginning or toEnd, or to
    # scroll forward(default) vertically(default)
    d(scrollable=True).scroll(steps=10)
    # scroll forward horizentally
    d(scrollable=True).scroll.horiz.forward(steps=100)
    # scroll backward vertically
    d(scrollable=True).scroll.vert.backward()
    # scroll to beginning horizentally
    d(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)
    # scroll to end vertically
    d(scrollable=True).scroll.toEnd()
    # scroll forward vertically until specific ui object appears
    d(scrollable=True).scroll.to(text="Security")
    

Contribution

  • Fork the repo, and clone to your computer.
  • Checkout a new branch from develop branch
  • Install requirements: pip install -r requirements.txt
  • Make your changes, and update tests. Don't forget adding your name at the end of 'Contributors' section
  • Pass all tests and your code must be covered: tox.
  • Commit your changes and submit pull request to develop branch.

Contributors

Issues & Discussion

If you have any bug reports or annoyances please report them to our issue tracker at github issues.

Notes

  • Android uiautomator works on Android 4.1+, so before using it, make sure your device is Android4.1+.
  • Some methods are only working on Android 4.2/4.3, so you'd better read detailed java documentation of uiautomator before using it.
  • The module uses uiautomator-jsonrpc-server as its daemon to communicate with devices.
  • The module is only tested on python2.7/3.2/3.3/pypy.

FAQ

  • Could not start JSONRPC server: raise IOError("RPC server not started!")

    It may be caused by network, device, or environment. So when you meet the issue, please follow below steps and try to manually start the JSONRPC server.

    1. Follow steps at uiautomator-jsonrpc-server to start jsonrpc server.

    2. Check if jsonrpc server is ok:

       curl -d '{"jsonrpc":"2.0","method":"deviceInfo","id":1}' localhost:9008/jsonrpc/0
      
      

      If you see message like {"jsonrpc":"2.0","id":1,"result":{"currentPackageName":"android","displayHeight":1280,"displayRotation":0,"displaySizeDpX":0,"displaySizeDpY":0,"displayWidth":720,"productName":"falcon","sdkInt":17,"naturalOrientation":true}}, it means the server is up.

    3. check local python lib: python2.7/dist-packages/uiautomator/libs:

      device sdk_level < 18 should use jar. device sdk_level >= 18 use APK.
      
      

    If you can manually start the jsonrpc server, but your script always meets IOError("RPC server not started!"), please submit an issue at github issues.

  • Error httplib.BadStatusLine: ''

    JsonRPC server needs to access temp directory on device, but on some low tier devices, it may meet error during accessing temp files without SD-CARD attached. So if you met the error, please insert a SD-CARD and then try again.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351