laitimes

Software testing/test development丨uiautomator2 automated testing tools are used

author:Hogwarts Software Testing
Software testing/test development丨uiautomator2 automated testing tools are used

Hogwarts Testing Academy is a gold sponsor of Python-UiAutomator2, and follows open source project authors to learn test development in practice.

Public account search: TestingStudio Hogwarts testing and development of dry goods are very hardcore

1. Background introduction

Google officially provides an Android automated testing tool (Java library), based on the Accessibility service, very powerful, can test third-party apps, get any control properties of any app on the screen, and operate on it, but there are two disadvantages:

  1. Test scripts can only use the Java language;
  2. The test script must be packaged as a jar or apk package and uploaded to the device to run;

In practice, we want the test logic to be written in Python and to be able to control the phone while running on the computer. Therefore, for this purpose, the python-uiautomator2 automated testing open source tool was developed, which encapsulates Google's own uiautomator2 test framework, which can run on any system that supports Python, and the current version is V2.10.2.

GitHub open source address:

https://github.com/openatx/uiautomator2           

Second, the working principle

Software testing/test development丨uiautomator2 automated testing tools are used

As shown in the figure, python-uiautomator2 is mainly divided into two parts, python client and mobile device

  • python side: Run scripts and send HTTP requests to mobile devices;
  • Mobile devices: The mobile device runs an HTTP service wrapped with uiautomator2, parses the received request, and converts it into uiautomator2 code;

The whole process:

  1. Install the atx-agent (daemon) on the mobile device, and then the atx-agent starts the uiautomator2 service (default port 7912) to listen;
  2. Write a test script on a PC and execute it (equivalent to sending an HTTP request to the server side of the mobile device);
  3. The mobile device receives HTTP requests from the PC through WIFI or USB and performs the prescribed operation;

3. Installation and startup

3.1 Install uiautomator2

Install using pip

pip install -U uiautomator2           

After the installation is complete, use the following python code to view the environment is configured successfully

Note: All the code in the following article needs to be imported into the uiautomator2 library, to simplify my use of u2 instead, d stands for driver

import uiautomator2 as u2
# 连接并启动
d = u2.connect() 
print(d.info)           

If the device information can be printed correctly, the installation is successful

Software testing/test development丨uiautomator2 automated testing tools are used

Note: You need to install the adb tool and configure to the system environment variable to operate the phone.

If you have problems with installation, you can check the issue list:

https://github.com/openatx/uiautomator2/wiki/Common-issues           

3.2 Install weditor

weditor is a browser-based UI viewer that helps us see UI element positioning.

Because uiautomator is an exclusive resource, uiautomatorviewer cannot be used when ATX is running, and this tool is needed to reduce the frequent start and stop of ATX

Install using pip

pip install -U weditor           

Check to see if the installation was successful

weditor --help           

The following message indicates that the installation is successful

Software testing/test development丨uiautomator2 automated testing tools are used

Run weditor

python -m weditor
#或者直接在命令行运行
weditor           

Fourth, element positioning

4.1 Usage

d(定位方式 = 定位值)
#例:
element = d(text='Phone')
#这里返回的是一个列表,当没找到元素时,不会报错,只会返回一个长度为 0 的列表
#当找到多个元素时,会返回多个元素的列表,需要加下标再定位
element[0].click()
#获取元素个数
print(element.count)           

4.2 Supported Targeting Methods

ui2 supports all positioning methods in the UiSelector class in android, details can be viewed at this website developer.android.com/reference/a...

The overall content is as follows, and all properties can be viewed through the weditor

name description
text text is the element that specifies text
textContains text contains elements that contain the specified text
textMatches text conforms to the element specified by the regularity
textStartsWith text An element that begins with the specified text
className className is an element that specifies the class name
classNameMatches The className class name conforms to the specified regular element
description description is an element that specifies text
descriptionContains description contains an element that contains the specified text
descriptionMatches description conforms to the element that specifies the regularity
descriptionStartsWith description element that begins with the specified text
checkable Checkable element, with True, False
checked The selected element, typically used for check boxes, with parameters True, False
clickable Clickable element, the parameter is True, False
longClickable An element that can be long pressed, with parameters True, False
scrollable Scrollable element, with parameters True, False
enabled Activated element, the parameter is True, False
focusable Focusable element, with True, False
focused The element that gets the focus with the parameters True, False
selected The currently selected element, the parameter is True, False
packageName packageName is the element that specifies the package name
packageNameMatches packageName is a regular element
resourceId resourceId is an element that specifies the content
resourceIdMatches resourceId is an element that conforms to the specified regularity

4.3 Child element and sibling positioning

Child element positioning

child()

#查找类名为 android.widget.ListView 下的 Bluetooth 元素
d(className="android.widget.ListView").child(text="Bluetooth")
# 下面这两种方式定位有点不准确,不建议使用
d(className="android.widget.ListView")\
.child_by_text("Bluetooth",allow_scroll_search=True)
d(className="android.widget.ListView").child_by_description("Bluetooth")           

Sibling element positioning

sibling()

#查找与 google 同一级别,类名为 android.widget.ImageView 的元素
d(text="Google").sibling(className="android.widget.ImageView")           

Chained calls

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

4.4 Relative positioning

Relative positioning is supported on left, right, top, bottom, that is, left and right before and after an element

d(A).left(B),# 选择 A 左边的 B
d(A).right(B),# 选择 A 右边的 B
d(A).up(B), #选择 A 上边的 B
d(A).down(B),# 选择 A 下边的 B
#选择 WIFI 右边的开关按钮
d(text='Wi‑Fi').right(resourceId='android:id/widget_frame')           

4.5 Element Common APIs

The class property methods marked with @property decoration in the table are all examples below

d(test="Settings").exists           
method description Return value remark
exists() Determine whether an element exists True,Flase @property
info() Returns all information for an element dictionary @property
get_text() Returns the element text string
set_text(text) Sets the element text None
clear_text() Empty the element text None
center() Returns the center point position of the element (x,y) Points based on the entire screen

exists in other ways:

d.exists(text='Wi‑Fi',timeout=5)           

info() output information:

{
  "bounds": {
    "bottom": 407,
    "left": 216,
    "right": 323,
    "top": 342
  },
  "childCount": 0,
  "className": "android.widget.TextView",
  "contentDescription": null,
  "packageName": "com.android.settings",
  "resourceName": "android:id/title",
  "text": "Wi‑Fi",
  "visibleBounds": {
    "bottom": 407,
    "left": 216,
    "right": 323,
    "top": 342
  },
  "checkable": false,
  "checked": false,
  "clickable": false,
  "enabled": true,
  "focusable": false,
  "focused": false,
  "longClickable": false,
  "scrollable": false,
  "selected": false
}           

All the properties of the element can be obtained separately through the information above

4.6 XPATH positioning

Because xpath is not supported by default in Java uiautoamtor, this is an extension of ui2, and the speed will be slower than other positioning methods

In xpath positioning, the description positioning in ui2 needs to be replaced with content-desc, and resourceId needs to be replaced with resource-id

How to use

# 只会返回一个元素,如果找不到元素,则会报 XPathElementNotFoundError 错误
# 如果找到多个元素,默认会返回第 0 个
d.xpath('//*[@resource-id="com.android.launcher3:id/icon"]')

# 如果返回的元素有多个,需要使用 all() 方法返回列表
# 使用 all 方法,当未找到元素时,不会报错,会返回一个空列表
d.xpath('//*[@resource-id="com.android.launcher3:id/icon"]').all()           

5. Device interaction

5.1 Click

d(text='Settings').click()
#单击直到元素消失 , 超时时间 10,点击间隔 1
d(text='Settings').click_gone(maxretry=10, interval=1.0)           

5.2 Long press

d(text='Settings').long_click()           

5.3 Dragging

Android < 4.3 does not use dragging

# 在 0.25S 内将 Setting 拖动至 Clock 上,拖动元素的中心位置
# duration 默认为 0.5, 实际拖动的时间会比设置的要高
d(text="Settings").drag_to(text="Clock", duration=0.25)

# 拖动 settings 到屏幕的某个点上
d(text="Settings").drag_to(877,733, duration=0.25)

#两个点之间的拖动 , 从点 1 拖动至点 2
d.drag(x1,y1,x2,y2)           

5.4 Sliding

There are two kinds of sliding, one is operated on the driver and the other is operated on the element

element

Slide from the center of the element to the edge of the element

# 在 Setings 上向上滑动。steps 默认为 10
# 1 步约为 5 毫秒,因此 20 步约为 0.1 s
d(text="Settings").swipe("up", steps=20)            

Driver

That is, the operation on the entire screen

# 实现下滑操作
x,y = d.window_size()
x1 = x / 2
y1 = y * 0.1
y2 = y * 0.9
d.swipe(x1,y1,x1,y2)           

The extension method of driver sliding can directly implement sliding without the need to encapsulate the anchor itself

# 支持前后左右的滑动
# "left", "right", "up", "down"
# 下滑操作
d.swipe_ext("down")           

5.5 Two-finger operation

android>4.3

Manipulate elements

d(text='Settings').gesture(start1,start2,end1,end2,)
# 放大操作
d(text='Settings').gesture((525,960),(613,1121),(135,622),(882,1540))           

Encapsulated zoom in and out operation

# 缩小
d(text="Settings").pinch_in()
# 放大
d(text="Settings").pinch_out()           

5.6 Wait for elements to appear or disappear

# 等待元素出现
d(text="Settings").wait(timeout=3.0)
# 等待元素消失,返回 True False,timout 默认为全局设置的等待时间
d(text='Settings').wait_gone(timeout=20)           

5.7 Scrolling Interface

Set the scrollable property to True;

Scroll type: horiz is horizontal, vert is vertical;

Scroll direction:

  • Forward forward
  • backward backward
  • toBeginning scrolls to Start
  • toEnd scrolls to the end
  • to scroll directly to an element appears

All methods return a Bool value;

# 垂直滚动到页面顶部 / 横向滚动到最左侧
d(scrollable=True).scroll.toBeginning()
d(scrollable=True).scroll.horiz.toBeginning()
# 垂直滚动到页面最底部 / 横向滚动到最右侧
d(scrollable=True).scroll.toEnd()
d(scrollable=True).scroll.horiz.toEnd()
# 垂直向后滚动到指定位置 / 横向向右滚动到指定位置
d(scrollable=True).scroll.to(description=" 指定位置 ")
d(scrollable=True).scroll.horiz.to(description=" 指定位置 ")
# 垂直向前滚动(横向同理)
d(scrollable=True).scroll.forward()
# 垂直向前滚动到指定位置(横向同理)
d(scrollable=True).scroll.forward.to(description=" 指定位置 ")
# 滚动直到 System 元素出现
d(scrollable=True).scroll.to(text="System")

Take screenshot of widget

im = d(text="Settings").screenshot()
im.save("settings.jpg")           

5.8 Input

5.8.1 Enter custom text

# 使用 adb 广播的方式输入
d.send_keys('hello')
# 清空输入框
d.clear_text()           

5.8.2 Input buttons

Two methods

# 发送回车
d.press('enter')
# 第二种
d.keyevent('enter')           

The buttons currently supported by Press are as follows

"""
        press key via name or key code. Supported key name includes:
            home, back, left, right, up, down, center, menu, search, enter,
            delete(or del), recent(recent apps), volume_up, volume_down,
            volume_mute, camera, power.
        """           

KeyEvent is input through the "adb shell input keyevent" method, which supports more rich keystrokes

More detailed key information developer.android.com/reference/a...

5.8.3 Input Method Switching

# 切换成 ui2 的输入法,这里会隐藏掉系统原本的输入法 , 默认是使用系统输入法
# 当传入 False 时会使用系统默认输入法,默认为 Fasle
d.set_fastinput_ime(True)
# 查看当前输入法
d.current_ime()
#返回值
('com.github.uiautomator/.FastInputIME', True)           

5.8.4 Analog Input Method Function

Features that can be simulated are go, search, send, next, done, previous.

If the press input key does not work, you can try this method input

# 搜索功能
d.send_action("search")           

5.9 Toast operation

# 获取 toast, 当没有找到 toast 消息时,返回 default 内容
d.toast.get_message(timout=5,default='no toast')
# 清空 toast 缓存
d.toast.reset()           

5.10 Monitoring Interface

The monitoring of the interface using Wather can be used to skip the pop-up box during the test

When wather is started, a new thread is created for monitoring

You can add multiple watchers

usage

# 注册监控 , 当界面内出现有 allow 字样时,点击 allow
d.watcher.when('allow').click()

# 移除 allow 的监控
d.watcher.remove("allow")

# 移除所有的监控
d.watcher.remove()

# 开始后台监控
d.watcher.start()
d.watcher.start(2.0) # 默认监控间隔 2.0s

# 强制运行所有监控
d.watcher.run()

# 停止监控
d.watcher.stop()

# 停止并移除所有的监控,常用于初始化
d.watcher.reset()           

Version 2.11.0 has added a watch_context method, the writing method is more concise than watcher, the official recommendation to use this method to achieve monitoring, currently only support click() this method.

wct = d.watch_context()
# 监控 ALLOW
wct.when("ALLOW").click()
# 监控 OK
wct.when('OK').click()
 # 开启弹窗监控,并等待界面稳定(两个弹窗检查周期内没有弹窗代表稳定)
wct.wait_stable()

#其它实现代码
# 停止监控
wct.stop()           

5.11 Multi-point swipe

This can be used to unlock patterns

Use the touch class

# 模拟按下不放手
touch.down(x,y)
# 停住 3S
touch.sleep(x,y)
# 模拟移动
touch.move(x,y)
# 模拟放开
touch.up(x,y)
#实现长按 , 同一个点按下休眠 5S 后抬起
d.touch.down(252,1151).sleep(5).up(252,1151)
# 实现四点的图案解锁,目前只支持坐标点
d.touch.down(252,1151).move(559,1431).move(804,1674).move(558,1666).up(558,1666)           

6. Image operation

6.1 Screenshots

d.screenshot('test.png')           

6.2 Recording Videos

This feeling is a useful feature, you can record at the beginning of a test case, stop recording at the end, and then fail if the test fails. Then upload to the test report, perfectly restore the operation site, and the specific principle will be studied later.

First of all, you need to download the dependency, and the official recommendation is to use mirror download:

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

Perform recording:

# 启动录制,默认帧率为 20
d.screenrecord('test.mp4')
# 其它操作
time.sleep(10)
#停止录制,只有停止录制了才能看到视频 
d.screenrecord.stop()           

6.3 Image recognition clicks

Download the same set of dependencies as recording videos.

This function is to manually capture the picture that needs to click on the target first, and then ui2 matches this picture in the interface, at present I have tried the accurate test is not very high, the error rate is very high, not recommended.

# 点击 
d.image.click('test.png')
# 匹配图片,返回相似度和坐标
# {'similarity': 0.9314796328544617, 'point': [99, 630]}
d.image.match('test.png')           

7. Application management

7.1 Get the APP information of the current interface

d.app_current()
#返回当前界面的包名,activity 及 pid
{
    "package": "com.xueqiu.android",
    "activity": ".common.MainActivity",
    "pid": 23007
}           

7.2 Installing the App

You can download and install the APP from the local path and URL, this method has no return value, and when the installation fails, a RuntimeError exception will be thrown

# 本地路径安装 
d.app_install('test.apk')
# url 安装 
d.app_install('http://s.toutiao.com/UsMYE/')           

7.3 Run the app

By default, when the app executes start in the running state, the app is not closed, but the current interface continues.

If you need to eliminate the previous startup state, you need to add the stop=True parameter.

# 通过包名启动
d.app_start("com.xueqiu.android",stop=True)

#源码说明
    def app_start(self, package_name: str, 
                  activity: Optional[str]=None, 
                  wait: bool = False, 
                  stop: bool=False, 
                  use_monkey: bool=False):
        """ Launch application
        Args:
            package_name (str): package name
            activity (str): app activity
            stop (bool): Stop app before starting the activity. (require activity)
            use_monkey (bool): use monkey command to start app when activity is not given
            wait (bool): wait until app started. default False
        """           

7.4 Stop Applying

The difference between stop and clear is the command used to end the app

stop uses "am force-stop"

clear uses "pm clear"

# 通过包名结束单个应用
d.app_stop("com.xueqiu.android")
d.app_clear('com.xueqiu.android')

# 结束所有应用 , 除了 excludes 参数列表中的应用包名
# 如果不传参,则会只保留两个依赖服务应用
# 会返回一个结束应用的包名列表
d.app_stop_all(excludes=['com.xueqiu.android'])           

7.5 Obtaining Application Information

d.app_info('com.xueqiu.android')

#输出
{
    "packageName": "com.xueqiu.android",
    "mainActivity": "com.xueqiu.android.common.splash.SplashActivity",
    "label": " 雪球 ",
    "versionName": "12.6.1",
    "versionCode": 257,
    "size": 72597243
}           

7.6 Get app icons

img = d.app_icon('com.xueqiu.android')
img.save('icon.png')           

7.7 Wait for the app to launch

# 等待此应用变为当前应用,返回 pid,超时未启动成功则返回 0
# front 为 true 表示等待 app 成为当前 app,
# 默认为 false,表示只要后台有这个应用的进程就会返回 PID
d.app_wait('com.xueqiu.android',60,front=True)           

7.8 Uninstall the app

# 卸载成功返回 true, 没有此包或者卸载失败返回 False
d.app_uninstall('com.xueqiu.android')

# 卸载所有自己安装的第三方应用 , 返回卸载 app 的包名列表
# excludes 表示不卸载的列表
# verbose 为 true 则会打印卸载信息
d.app_uninstall_all(excludes=[],verbose=True)           

The list of package names returned by uninstalling all applications must be uninstalled successfully, it is best to use verbose=true to print the information, so that you can see whether the uninstallation is successful

uninstalling com.xueqiu.android  OK
uninstalling com.android.cts.verifier  FAIL           

Or you can modify the source code so that only the successful package name is output, the annotated code is the added code, and the uncommented is the source code

def app_uninstall_all(self, excludes=[], verbose=False):
        """ Uninstall all apps """
        our_apps = ['com.github.uiautomator', 'com.github.uiautomator.test']
        output, _ = self.shell(['pm', 'list', 'packages', '-3'])
        pkgs = re.findall(r'package:([^\s]+)', output)
        pkgs = set(pkgs).difference(our_apps + excludes)
        pkgs = list(pkgs)
        # 增加一个卸载成功的列表
        #sucess_list = []
        for pkg_name in pkgs:
            if verbose:
                print("uninstalling", pkg_name, " ", end="", flush=True)
            ok = self.app_uninstall(pkg_name)
            if verbose:
                print("OK" if ok else "FAIL")
                # 增加如下语句,当成功则将包名加入 list
                #if ok:
                 #   sucess_list.append(pkg_name)
     # 返回成功的列表
    #    return sucess_list
        return pkgs           

8. Other practical methods

8.1 Connecting Devices

#当 PC 只连接了一个设备时,可以使用此种方式
d = u2.connect()
#返回的是 Device 类 , 此类继承方式如下

class Device(_Device, _AppMixIn, _PluginMixIn, _InputMethodMixIn, _DeprecatedMixIn):
    """ Device object """


# for compatible with old code
Session = Device           

connect() can be connected in one of the following other ways

#当 PC 与设备在同一网段时,可以使用 IP 地址和端口号通过 WIFI 连接,无需连接 USB 线
connect("10.0.0.1:7912")
connect("10.0.0.1") # use default 7912 port
connect("http://10.0.0.1")
connect("http://10.0.0.1:7912")
#多个设备时,使用设备号指定哪一个设备
connect("cff1123ea")  # adb device serial number           

8.2 Obtaining Device and Driver Information

8.2.1 Obtaining driver information

d.info
#输出
{
    "currentPackageName": "com.android.systemui",
    "displayHeight": 2097,
    "displayRotation": 0,
    "displaySizeDpX": 360,
    "displaySizeDpY": 780,
    "displayWidth": 1080,
    "productName": "freedom_turbo_XL",
    "screenOn": true,
    "sdkInt": 29,
    "naturalOrientation": true
}           

8.2.2 Obtaining Device Information

All the information of the test equipment is output, including battery, CPU, memory, etc

d.device_info
#输出
{
    "udid": "61c90e6a-ba:1b:ba:46:91:0e-freedom_turbo_XL",
    "version": "10",
    "serial": "61c90e6a",
    "brand": "Schok",
    "model": "freedom turbo XL",
    "hwaddr": "ba:1b:ba:46:91:0e",
    "port": 7912,
    "sdk": 29,
    "agentVersion": "0.9.4",
    "display": {
        "width": 1080,
        "height": 2340
    },
    "battery": {
        "acPowered": false,
        "usbPowered": true,
        "wirelessPowered": false,
        "status": 2,
        "health": 2,
        "present": true,
        "level": 98,
        "scale": 100,
        "voltage": 4400,
        "temperature": 292,
        "technology": "Li-ion"
    },
    "memory": {
        "total": 5795832,
        "around": "6 GB"
    },
    "cpu": {
        "cores": 8,
        "hardware": "Qualcomm Technologies, Inc SDM665"
    },
    "arch": "",
    "owner": null,
    "presenceChangedAt": "0001-01-01T00:00:00Z",
    "usingBeganAt": "0001-01-01T00:00:00Z",
    "product": null,
    "provider": null
}           

8.2.3 Get the screen resolution

# 返回(宽,高)元组
d.window_size()
# 例 分辨率为 1080*1920
# 手机竖屏状态返回 (1080,1920)
# 横屏状态返回 (1920,1080)           

8.2.4 Obtaining an IP Address

# 返回 ip 地址字符串,如果没有则返回 None
d.wlan_ip           

8.3 Driver Global Settings

8.3.1 Using Settings Settings

Review the settings default settings

d.settings
#输出

{
    #点击后的延迟,(0,3)表示元素点击前等待 0 秒,点击后等待 3S 再执行后续操作
    'operation_delay': (0, 3),
    # opretion_delay 生效的方法,默认为 click 和 swipe
    # 可以增加 press,send_keys,long_click 等方式
    'operation_delay_methods': ['click', 'swipe'],
    # 默认等待时间,相当于 appium 的隐式等待
    'wait_timeout': 20.0,
    # xpath 日志
    'xpath_debug': False
}           

To modify the default settings, you only need to modify the settings dictionary

#修改延迟为操作前延迟 2S 操作后延迟 4.5S
d.settings['operation_delay'] = (2,4.5)
#修改延迟生效方法
d.settings['operation_delay_methods'] = {'click','press','send_keys'}
# 修改默认等待
d.settings['wait_timeout'] = 10           

8.3.2 Use methods or property settings

  • HTTP default request timeout
# 默认值 60s, 
d.HTTP_TIMEOUT = 60            
  • When the device drops, wait for the device to be online
# 仅当 TMQ=true 时有效,支持通过环境变量 WAIT_FOR_DEVICE_TIMEOUT 设置
d.WAIT_FOR_DEVICE_TIMEOUT = 70            
  • element finds the default wait time
# 打不到元素时,等待 10 后再报异常
d.implicitly_wait(10.0)           
  • Open HTTP debug information
d.debug = True
d.info
#输出
15:52:04.736 $ curl -X POST -d '{"jsonrpc": "2.0", "id": "0eed6e063989e5844feba578399e6ff8", "method": "deviceInfo", "params": {}}' 'http://localhost:51046/jsonrpc/0'
15:52:04.816 Response (79 ms) >>>
{"jsonrpc":"2.0","id":"0eed6e063989e5844feba578399e6ff8","result":{"currentPackageName":"com.android.systemui","displayHeight":2097,"displayRotation":0,"displaySizeDpX":360,"displaySizeDpY":780,"displayWidth":1080,"productName":"freedom_turbo_XL","screenOn":true,"sdkInt":29,"naturalOrientation":true}}
<<< END           
  • dormancy
# 相当于 time.sleep(10)
d.sleep(10)           

8.4 Turn the screen on and off

# 亮屏
d.screen_on()
# 灭屏
d.screen_off()           

8.5 Screen orientation

# 设置屏幕方向
d.set_orientation(value)
# 获取当前屏幕方向
d.orientation           

The value value is a reference, as long as it is any value in the tuple.

# 正常竖屏
(0, "natural", "n", 0), 
# 往左横屏,相当于手机屏幕顺时针旋转 90 度
# 现实中如果要达到此效果,需要将手机逆时针旋转 90 度
 (1, "left", "l", 90),
# 倒置,这个需要看手机系统是否支持 , 倒过来显示 
 (2, "upsidedown", "u", 180), 
# 往右横屏,调整与往左相反,屏幕顺时针旋转 270 度
 (3, "right", "r", 270))           

8.6 Open the notification bar with Quick Settings

Open the notification bar

d.open_notification()           

Open Quick Settings

d.open_quick_settings()           

8.7 File Import and Export

8.7.1 Importing Files

# 如果是目录,这里 "/sdcrad/" 最后一个斜杠一定要加,否则会报错
d.push("test.txt","/sdcrad/")
d.push("test.txt","/sdcrad/test.txt")           

8.7.2 Exporting Files

d.pull('/sdcard/test.txt','text.txt')           

8.8 Executing shell commands

Execute using shell methods

8.8.1 Executing Non-Blocking Commands

output returns an overall string, and if you need to extract the value, you need to parse and extract the output

# 返回输出和退出码,正常为 0,异常为 1
output,exit_code = d.shell(["ls","-l"],timeout=60)           

8.8.2 Executing Blocking Commands (Persistent Commands)

# 返回一个命令的数据流 output 为 requests.models.Response
output = d.shell('logcat',stream=True)
try:
    # 按行读取,iter_lines 为迭代响应数据,一次一行
    for line in output.iter_lines():
        print(line.decode('utf8'))
finally:
    output.close()           

Source code description

def shell(self, cmdargs: Union[str, List[str]], stream=False, timeout=60):
        """
        Run adb shell command with arguments and return its output. Require atx-agent >=0.3.3

        Args:
            cmdargs: str or list, example: "ls -l" or ["ls", "-l"]
            timeout: seconds of command run, works on when stream is False
            stream: bool used for long running process.

        Returns:
            (output, exit_code) when stream is False
            requests.Response when stream is True, you have to close it after using

        Raises:
            RuntimeError

        For atx-agent is not support return exit code now.
        When command got something wrong, exit_code is always 1, otherwise exit_code is always 0
        """           

8.9 Session (currently deprecated)

8.10 Stop UI2 Services

Because of the atx-agent, Uiautomator will be guarded at all times, and will be restarted if it exits. But Uiautomator is overbearing, once it is running, the accessibility functions on the phone, the uiautomatorviewer on the computer can not be used, unless the uiautomator of the framework itself is turned off

Stop with code

d.service("uiautomator").stop()           

Stop manually

Open the ATX APP directly (after the init is successful, it will be installed) and click to close UIAutomator

Above, welcome to exchange and discuss together.