QT4A文档

QT4A,即Quick Test for Android,基于QTA提供的面向Android应用的UI测试自动化解决方案。

使用文档

使用前准备

QT4A依赖QTAF模块,使用前请参考《使用前准备》一节。

准备ADB

你可以下载google官方提供的ADB安装即可。如果你电脑已经安装配置了Android Studio,那么ADB也已安装。在终端输入命令:

$ adb version

可以看到显示adb版本,则代表adb安装成功。

准备Android设备

QT4A使用需要至少有一台Android设备。

  1. 连接到PC
手机需要通过USB连接到对应的PC,并安装相关的驱动,如驱动没有自动安装,可以使用腾讯“电脑管家”进行驱动安装。
  1. 设置可以ADB调试

修改手机的配置,打开“USB调试”功能。 保证adb工具可以识别出来。 可以通过adb工具检查设备是否连接和安装成功:

$ adb devices
List of devices attached
3fr4343aa12   device
  1. 如果你的手机是root过的设备,那么该步骤可跳过。如果你的手机未root,请先在你的电脑上安装jdk 64位,并配置环境变量,同时把bin目录下的jarsigner.exe加入到环境变量中,为后面重打包apk准备环境。

注解

建议优先使用root过的设备,这样没有各类系统授权弹框的干扰,也无需重打包APK后再安装应用。

快速入门

测试项目

测试用例归属于一个测试项目,在设计测试用例之前,如果没有测试项目,请先参考《创建和修改测试项目》。按照该指引你可以创建一个例如名称为”demo”的项目。

测试环境

你可以选择你习惯的开发环境,如Eclipse、命令行执行、PyCharm等,推荐使用Eclipse开发环境。

安装QT4A

通过pip安装的方式安装QT4A:

pip install qt4a

安装QT4A测试桩

执行用例前,需先安装QT4A测试桩,可通过QT4A命令执行:

qt4a-manage install-driver

执行成功如下:

_images/install_driver_cmd.png

同时可以看到文件已拷贝到手机目录中:

_images/qt4a_driver_files.png

Demo工程

你可从github下载 Demo工程,其中包括demo测试项目源码和demo apk,本文的分析基于该demo工程进行。

安装被测App

手机已root

手机已root的情况下,下载提供的demo apk到本地(如保存为D:\demo.apk),本文档以测试该App的登录功能为例讲解如何编写一个基本的QT4A用例。可以通过在控制台窗口执行命令以安装该应用:

adb install D:\demo.apk

该被测App的功能是:只要你帐号密码不为空,就会跳转到登录成功界面,若帐号或密码为空,则跳转到登录失败界面。

手机未root

如果你的手机未root,请先将你的apk进行重打包(假设你已经按照《使用前准备》一节装好了jdk),再安装重打包后的apk,重打包命令如下:

qt4a-manage repack-apk -p D:\1.apk

耐心等待执行完成,会打印出一个重打包后的apk路径:

_images/repack_apk_cmd.png

到该路径下拷贝重打包后的apk,安装重打包后的apk即可。命令更多参数可以在命令行中加-h查看。

安装release包

上面都是安装的debug包,给出的demo apk也是debug版本的,未经过混淆。建议安装debug版本的App,通常debug版本的App没有做过混淆,QT4A可以顺利找到控件。如果你安装的是release版本的App,做过混淆,则需把对应的混淆还原文件push到手机tmp目录下,文件名为/data/local/tmp/{{package_name}}_map.txt,其中{{package_name}}是你的应用包名:

$ adb push D:\demo_map.txt /data/local/tmp/com.qta.qt4a.demo_map.txt

执行成功后可以看到文件已在目标目录:

_images/map.png

当然,提供的demo app是debug包,所以无需执行上述命令,只是举例说明release包的安装方法。

理解UI结构

Android的UI结构涉及三个概念,应用(App)、窗口(Window)、控件(Control):

  • App: 每一个Android应用是一个App,这是我们开发出来待测试的应用程序,安装在我们的手机上,App基类由 qt4a.androidapp.AndroidApp 实现。
  • Window: 一个App包含多个Window(Activity),Window表示一个被测的用户窗口界面,一般和Android应用中的Activity对应,我们需要对每个窗口进行定义,以测试目标窗口,窗口基类由 qt4a.andrcontrols.Window 实现 。
  • Control: 一个Window包含多个Control,即每一个窗口包含了多个控件元素,QT4A中用QPath来查找定位控件,以便对各个控件进行操作。Android中控件类型较多,相应地QT4A中实现了对各个类型的封装,如 qt4a.andrcontrols.TextView , qt4a.andrcontrols.Button 等,全部控件类型请阅读接口文档。

所以我们需要以QT4A中的实现为基础,针对三种UI元素分别进行封装使用。

第一个测试用例

开始写QT4A的第一个用例前,请先参考《Testbase设计测试用例》熟悉用例基本结构。由开头可知,我们已经创建了一个名为demo的项目,其中,

  • demotest:测试用例集合,这里存储所有测试用例的脚本。
  • demolib:测试业务库,这里存放所有测试业务lib层的代码,使得不同用例可以复用demolib的接口。
  • settings.py:项目配置文件,可以配置你所需要的项。

demotest目录下会自动生成hello.py文件,我们在该文件实现第一个简单的测试用例。

demotest/hello.py中实现 HelloTest类:

# -*- coding: utf-8 -*-

'''示例登录测试用例
'''

from demolib.demotestbase import DemoTestBase
from demolib.demoapp import DemoApp


    '''示例登录测试用例
    '''
    owner = "Administrator"
    timeout = 5
    priority = DemoTestBase.EnumPriority.High
    status = DemoTestBase.EnumStatus.Design

    def run_test(self):
        #--------------------------
        self.start_step('1、登录Android demo')
        #--------------------------
        acc = "admin"
        pwd = "admin"
        device = self.acquire_device()
        app = DemoApp(device)
        app.login(acc, pwd)
        self.waitForEqual('当前Activity为:com.qta.qt4a.demo.HomeActivity', app.device, 'current_activity', 'com.qta.qt4a.demo.HomeActivity')

if __name__ == '__main__':
    HelloTest().debug_run()

警告

在测试用例中强烈建议只调用lib封装的接口和断言操作,以保证App UI变化时用例的逻辑不需要改动,只需要统一修改lib层接口,这样也能保持用例的简洁易懂。

注解

一个基本的用例结构如上述代码所述,用例名命名为XXTest,每个用例必须实现run_test接口。

继承于测试基类

首先,用例继承于DemoTestBase测试基类,针对测试基类的封装可以参考《封装测试基类》一节。然后在run_test接口中编写你的测试用例。

获取设备

在用例中首先需要获取连接在你PC上的Android设备,利用以下代码:

device = self.acquire_device() #申请Android设备,返回设备对象device

即可返回设备对象,此处参数未指定特定设备,会自动选择插在你电脑上的其中一台设备。返回类型为 qt4a.device.Device ,所以你可以查找该类的接口并进行使用。

注解

如果在命令行中执行adb devices命令,返回结果没有device字样出现,证明手机连接不正常,则会抛出异常,需重新连接手机后再试。

实例化App类

申请到设备后,开始实例化你的应用App类,针对DemoApp的封装可以参考《封装App》一节:

app = DemoApp(device)
业务逻辑操作

在用例中只调用业务逻辑接口,而业务逻辑实现在demolib库下的各个文件中。本测试用例是为了验证登录功能是否如预期,故此处调用:

app.login(acc, pwd)

从实现:

def login(self, acc, pwd):
 '''登录demo
 '''
    from login import LoginPanel, HomePanel
    login_panel = LoginPanel(self)
    login_panel.login(acc, pwd)
    self.wait_for_activity(HomePanel.Activity, 10)

可以看出,该函数调用了LoginPanel的login函数,即输入帐号密码并点击登录的功能,预期是跳转到登录成功的页面。所以接下来验证测试功能点,是否是跳转到了目标页面:

self.waitForEqual('当前Activity为:com.qta.qt4a.demo.HomeActivity', app.device, 'current_activity', 'com.qta.qt4a.demo.HomeActivity')

当然,此处只是验证跳转到的目标Activity对不对,如果你要做更细致的判断,例如判断目标页面的提示语是否正确,可以继续封装。

本测试用例比较简单,所以只有一个步骤, 如果业务逻辑更加复杂,可以继续往下写self.start_step(‘2、验证xx功能’)等。 至此,一个基本的用例就完成了。此时app界面如下:

_images/login_add_acc.png _images/login_success.png

注解

此处正好将login接口封装在DemoApp类中,所以可以直接调用,一般把登录、登出函数放在应用App类中实现,其他功能逻辑不建议放入应用App中,以免App类显得冗余。其他功能逻辑可放各个面板类中,在用例中直接声明面板类,并调用其接口即可。

而login接口中实际是去调用封装好的窗口类LoginPanel的接口函数,针对登录窗口的封装可以参考《封装Activity》一节。

UI元素封装

Android窗口中的各个控件元素需要用QPath进行定位,同时控件类型根据是否是Android本身提供的还是自定义的,简称为原生控件和自绘控件来分别进行封装使用。

封装测试基类

测试基类概述

QTAF中实现的测试基类《TestCase》提供了很多功能接口,如环境准备和清理、断言、日志相关等功能,详细见测试基类的相关说明。QT4A中的测试基类AndroidTestBase重载了QTAF提供的测试基类,复用其功能,并扩展Android需要的特定功能,如配置Android设备HOST,保存logcat或qt4a日志等。

测试基类封装

目前qt4a的测试基类 qt4a.androidtestbase.AndroidTestBase 已经实现了Android需要的常用功能。你可以在demolib/demotestbase.py中封装你的测试基类DemoTestBase,并且该类继承于AndroidTestBase,即可使用AndroidTestBase中已有功能,同时可重载各个接口扩展针对你测试项目的自定义的功能。例如可如下使用:

# -*- coding: utf-8 -*-
'''示例测试基类
'''

from qt4a.device import Device
from qt4a.androidtestbase import AndroidTestBase

class DemoTestBase(AndroidTestBase):
    '''demo测试用例基类
    '''

    def post_test(self):
        '''清理测试用例
        '''
        from qt4a.androiddriver.util import logger
        logger.info('post_test run')
        super(DemoTestBase, self).post_test()
        Device.release_all_device()  # 释放所有设备
        logger.info('postTest complete')

    def acquire_device(self, type='Android', device_id='', **kwds):
        '''申请设备

        :param type: 申请的设备类型,目前尚未使用
        :type type:  string
        :param device_id: 申请的设备ID,默认不指定设备ID
        :type device_id:  string
        '''
        device = super(DemoTestBase, self).acquire_device(device_id, **kwds)
        device.adb.start_logcat([])
        return device

即可实现测试用例的环境准备或环境清理功能。除了以上封装的基本功能,你可能还需重载其他接口,如:

  • 自定义被测应用的crash提取规则,如果logcat日志中有匹配到这里定义的规则,相关的crash日志将会被单独提取出来,供分析。详细请参考 从logcat提取crash日志 一节:

    def extract_crash_by_patterns(self):
    
  • 每个步骤前自定义一些操作,例如每个步骤前都打印出时间戳,看出每个步骤耗时等,可以重载下面接口:

    def start_step(self, step):
    

等等,更多参考QTAF和QT4A接口文档。

警告

重载基类各个接口时,必须显式调用基类的函数,以免基类的逻辑无法被执行到。

测试基类使用

在用例中将该类作为测试用例的基类:

class HelloTest(DemoTestBase):

封装App

App类概述

在demolib/app.py中封装你的应用App类DemoApp,实现App类的基本功能 qt4a.androidapp.AndroidApp 类提供了常见功能,如检测安装包、等待窗口出现,关闭当前窗口等,用户需要声明自己的App类,继承于AndroidApp基类。

App类封装

我们仍以Demo App为例,完整代码见Demo工程。被测应用的基本App类继承于AndroidApp类,只需实现最基本的功能,如下:

# -*- coding: utf-8 -*-

from qt4a.androidapp import AndroidApp

class DemoApp(AndroidApp):
    '''安卓Demo App类
    '''
    # 包名,必须定义
    package_name = 'com.qta.qt4a.demo'

    def __init__(self, device=None, clear_state=True, kill_process=True, net_type='wifi', start_extra_params={}):
        '''
        :param device: 设备实例
        :type device:  Device
        '''
        super(DemoApp, self).__init__(self.package_name, device)  #第一个参数传入主进程名,在demo app中,主进程名和包名相同
        self._start(clear_state, kill_process, start_extra_params=start_extra_params)

    def _start(self, clear_state=True, kill_process=True, start_extra_params={}):
        '''启动Android demo apk
        '''
        if kill_process == True:
            self.device.kill_process(self.package_name)  # 杀死已有进程
        if clear_state == True:
            self.device.clear_data(self.package_name) #清除包数据
        self.device.adb.start_activity('%s/%s.MainActivity' % (self.package_name, self.package_name), extra=start_extra_params)

上述代码只实现了最基本的功能,你可以根据需要定义更多的接口。__init__函数中需要实现:

  • 声明package_name属性,传入包名,AndroidApp类会去检查被测应用是否已安装。
  • 调用基类的__init__函数,以便初始化操作得以执行,此处传入了参数主进程名和device对象。
  • 杀死应用进程(kill_process调用),清除应用数据(clear_data调用),以便每次都会从应用的初始状态开始进入,这样可以知道每一步都会到达什么窗口界面,从而定义使用预期窗口。
  • 启动应用(start_activity调用)。

启动应用时,需要传入要启动的Activity参数,如果你不清楚被测应用的启动Activity,你可以调用QT4A的接口:

from qt4a.androiddriver.util import AndroidPackage
package = AndroidPackage(r'D:\demo.apk')
print (package.start_activity)
print (package.package_name)

由上可知,应用的包名也可以同时读取出来。更多的包信息可以参考该类的接口文档获取。这样便实现了一个最简单的被测应用App类。

App类功能自定义实现

如果你在启动应用前需要自定义一些其他操作,可以再自行实现。例如你在启动应用前希望去连接WIFI,那么可以在调用_start接口前先进行连接WIFI操作:

self._connect_network('wifi')
self._start(clear_state, kill_process, start_extra_params=start_extra_params)

然后自行实现_connect_network接口,例如:

def _connect_network(self, net_type):
'''连接网络
'''
   pass

所以请根据实际功能进行扩展开发。

App类使用

在用例中申请完设备后,即可开始实例化被测App,如下:

app = DemoApp(device)

传入设备实例参数device,在__init__函数中会先对应用数据进行清除,杀死应用进程,再启动应用,保证应用都是从启动界面开始。实例化App后,App会启动,进入启动界面:

_images/login.png

封装Activity

Activity概述

每个Android应用都包含多个窗口(Activity),每个窗口可以封装成一个面板类,在lib库中实现,建议App中一个产品功能模块封装在一个py文件中,例如app的主面板相关作为一个py文件mainpanel.py,app的登录相关作为一个文件login.py,在文件中再具体实现多个相关类的UI封装。

Activity封装

封装的面板类需要直接继承于标准Window基类。声明的面板类一般如下:

  • 继承于 qt4a.andrcontrols.Window
  • 声明Activity属性:这可以用控件探测工具探测得到
  • 声明Process属性:如Process=’com.qta.qt4a.demo:tools’,如果进程名和包名相同则可以省略该属性,当前登录界面就在主进程内,故可不声明Process属性。
  • __init__函数需要传入app实例作为参数
  • 在__init__接口中调用self.updateLocator接口,来声明窗口内的各个控件元素,如登录按钮、帐号等,控件是使用QPath进行封装定位的,如何封装可参考《设计QPath》一节。
  • 面板类中可以封装该面板内的各功能函数,如登录功能等。

根据以上原则,demolib/login.py中实现的 LoginPanel类 如下:

# -*- coding: utf-8 -*-

from qt4a.andrcontrols import Window, Button, EditText, TextView
from qt4a.qpath import QPath

class LoginPanel(Window):
    '''登录界面
    '''
    Activity = 'com.qta.qt4a.demo.MainActivity'  # 登录界面

    def __init__(self, demoapp):
        super(LoginPanel, self).__init__(demoapp)
        self.updateLocator({'帐号': {'type': EditText, 'root': self, 'locator': QPath('/Id="editAcc"')},
                            '密码': {'type': EditText, 'root': self, 'locator': QPath('/Id="editPwd"')},
                            '登录': {'type': Button, 'root': self, 'locator': QPath('/Id="btnLogin"')},

                            })

    def login(self, acc, pwd):
        '''登录界面
        '''
        self.wait_for_exist()
        self.Controls["帐号"].text = acc
        self.Controls["密码"].text = pwd
        self.Controls["登录"].click()

一般来说,app随着网络环境、手机性能等不同因素的影响,打开不同页面耗时不同,所以必要时可以加上一些等待逻辑,如login函数中,首先调用self.wait_for_exist()等待登录窗口出现再进行操作。当然,qt4a底层本身也已针对这些情况做了适配,所以封装时未必一定需要该类等待,如果你的应用打开某个页面很慢,或等待某个控件加载很耗时,例如超过10s,才需要自己再加等待出现逻辑。

获取控件的方式是self.Controls[xx],该调用返回的对象是你在self.updateLocator接口中声明的各个类型的对象,如self.Controls[“帐号”]返回的是EditText类对象,再调用其对应接口即可。详细控件封装使用方式可参考《封装原生控件》一节和《封装自绘控件》一节。

Activity使用

定义好面板类后,在用例中可以实例化面板类,并调用对应的功能接口:

login_panel = LoginPanel(app)
login_panel.login('admin', 'admin')

设计QPath

使用场景

App中每个窗口包含多个控件元素,要对各个元素进行操作,需要首先使用QPath来标识各个元素。从demo实例中可以看出,最终是在面板类的__init__接口中调用:

self.updateLocator({'帐号': {'type': EditText, 'root': self, 'locator': QPath('/Id="editAcc"')},
                         })

进行QPath的封装的,QPath的学习请先参考《QPath语法和使用》。如上代码所示,在封装控件时还需要指定其type和root属性。

  • type: 指定控件的类型,和Android中定义的控件类型相对应,如 qt4a.andrcontrols.TextViewqt4a.andrcontrols.Buttonqt4a.andrcontrols.ListView 等。目前QT4A已封装的全部类型见接口文档。
  • root: 指定控件的父节点,指定父节点后,查找控件时,会先找到父节点,然后以父节点为根节点,从根节点开始查找目标控件,这对于你定义的QPath找到多个的情况非常有用,如果指定了父节点,就只会返回父节点下的节点,否则找到多个重复节点就会报错。 如果指定为self,则表示会从整颗控件树根节点开始查找目标控件。
  • locator: 指定QPath,QPath的封装详见下文。
QT4A支持的QPath属性

QT4A目前支持的QPath关键字有Id、Text、Instance、MaxDepth、Visible、Type。同时支持全匹配(=)和正则匹配(~=)。以下通过AndroidUiSpy控件树探测工具抓取窗口控件树来一一说明用法,可从github下载 AndroidUiSpy工具 ,并参考 AndroidUiSpy使用文档

Id属性

Id属性最常用,使用控件Id标识控件,首选。如果控件没定义(例如控件ID显示为None),才选择其他属性进行定义。如下的帐号控件:

_images/id.png

可用Id定义:

'acc': {'type': TextView, 'root': self, 'locator': QPath('/Id="account"')},

虽然type在探测工具中显示的是AppCompatTextView,不过由于其继承于TextView,可以直接声明为qt4a中 qt4a.andrcontrols.TextView 已经实现的控件类型TextView。

Text属性

有时控件没有定义Id,但其是一个文本控件,text属性不为空,此时可用Text属性辅助定位,如下的控件:

_images/text.png

可用Text定义:

'demo': {'type': TextView, 'root': self, 'locator': QPath('/Text="demo"')},
Instance属性

有时你想要定义一个控件下的第几个子控件,又没有直接的Id可以定位,可以借助Instance,如下,我们还是需要探测界面中的demo属性,假如不用Text定义,用Instance也可以定义:

_images/instance.png

可定义为:

'demo': {'type': TextView, 'root': self, 'locator': QPath('/Id="action_bar"/Instance=0')},

注解

/表示控件树的不同层,例如/Id=”action_bar”/Instance=0表示查找直接父节点Id为action_bar下的第一个直接子节点;而&&描述的是同一层的节点,如/Id=”action_bar”&&Instance=0”表示查找的是第一个Id为action_bar的节点。在本控件树中,只有一个action_bar,所以表示的是这个action_bar自身,此时可省略Instance字段的声明了。

MaxDepth属性

有时你需要定义跨层的控件,例如M层定义后,接下来要定义第N层(M-N>1)的控件,如果一层一层定义下来比较费事,可以借助MaxDepth,即会搜索M层下的N层内的所有控件,假设我们仍然要定义Id为account的这个控件:

_images/maxdepth.png

借助MaxDepth的话可定义为:

'acc': {'type': TextView, 'root': self, 'locator': QPath('/Id="LinearLayout02"/Id="account"&&MaxDepth="2"')},

加上MaxDepth,那么找到LinearLayout02节点后,除了查找它的直接子节点是否有account节点,还会再查找第二层的节点,最终在第二层找到目标节点。如果不加MaxDepth,那么只会查找LinearLayout02的直接子节点,找不到就报错了。

注解

这里MaxDepth=”2”的2是字符串写法,表示从上一个声明的节点下来,最多查找2层。

Visible属性

有时有多个控件,控件Id相同,Visible属性不同,可以借助Visible属性来指定目标控件,如下:

_images/visible.png

假如你要定义editPwd这个控件,而该控件树还有一个Id为editPwd,但Visible属性为False的控件,通过下面的定义:

'密码': {'type': EditText, 'root': self, 'locator': QPath('/Id="editPwd"&&Visible="True"')},

就可以找到你要的目标控件了。当然,本控件树中,正好没有Id为editPwd,Visible为False的控件,所以此处的Visible属性省略也可以。

Type属性

type属性一般不建议使用,只有在上述几种方式还无法定义目标控件才使用。密码输入框这个控件(看Visible属性一节的控件截图),假如借助Type来定义,可如下:

'密码': {'type': EditText, 'root': self, 'locator': QPath('/Type="AppCompatEditText"&&Instance=1')},

由于当前控件树总共有2个type为AppCompatEditText的控件,我们要找的是第二个,所以加上Instance关键字来辅助定位。

注解

在QPath中声明的Type字段需要写控件的真实类型AppCompatEditText,用于定位控件用,而不能简化为Python层定义的EditText。而上图声明的’type’字段声明的类型才是从包qt4a.andrcontrols中查找合适的类型EditText来用即可,以便后面调用该控件对应的方法和属性。

正则匹配

在上面的例子中,我们都是用全匹配的方式,即在QPath中都是用”=”,如果要定义的内容有部分是变化的,可以考虑用正则匹配的方式,例如Text属性中提到的demo控件,如果前后有可能有别的变化的内容,可如下定义:

'demo': {'type': TextView, 'root': self, 'locator': QPath('/Text~=".*demo.*"')},

注解

从上面可以看出,同一个控件是可以有多种写法的,所以定义时应该选择最简洁的写法,不要不必要地复杂化。当需要多个字段辅助定义才能定位一个控件时,才进行结合使用。

QPath的root声明

以上的控件定义,root都声明为了self,因为没有复杂的控件树结构。如果复杂情况下需要先声明父控件,再在父控件下声明子控件:

'LinearLayout_account': {'type': LinearLayout, 'root': self, 'locator': QPath('/Id="LinearLayout_account"')},
'acc': {'type': TextView, 'root': 'LinearLayout_account', 'locator': QPath('/Id="account"')},

如上,会先找到LinearLayout_account节点,再去查找其下的account节点。

封装原生控件

Android系统中提供了丰富的控件类型供使用,如TextView,ListView, ScrollView, Button等,我们简称为原生控件,QT4A在Python层也对这些控件的自动化做了封装,以供使用。当然,有时候这些类型还不足以满足用户的需求,用例可以再重载定义新的更为复杂功能更丰富的控件类型,我们简称为自绘控件,这类控件暂不在本篇文档讨论范围内。

控件基本用法

我们知道,一个简单的控件定义如下:

class LoginPanel(Window):
    '''登录界面
    '''
    Activity = 'com.qta.qt4a.demo.MainActivity'  # 登录界面

    def __init__(self, demoapp):
        super(LoginPanel, self).__init__(demoapp)
        self.updateLocator({'acc': {'type': TextView, 'root': self, 'locator': QPath('/Id="account"')}, })

定义完后,在LoginPanel的接口下调用方式如下:

self.Controls['acc']

调用返回的对象的类型是我们定义的type,此处为TextView,再查看TextView类实现的属性和方法,可以看出TextView类下有text属性,那么可以读取text属性:

print (self.Controls['acc'].text)

同时,TextView继承于View类,View实现了click方法,所以又可以调用方法:

self.Controls['acc'].click()

当然,从实现上,acc控件没有实现点击触发逻辑,所以可以根据实际实现调用需要的接口。

样例:EditText类型

我们以帐号控件为例,其类型是EditText:

self.updateLocator({'帐号': {'type': EditText, 'root': self, 'locator': QPath('/Id="editAcc"')}, })

EditText类型的控件通常是一个文本输入框,可以让用户输入文本,从QT4A的实现中,可以看出EditText继承于TextView:

class EditText(TextView):

而TextView中又实现了@text.setter装饰的接口text:

class TextView(View):

 @text.setter
 def text(self, value):

所以对于文本框可以直接赋值:

self.Controls['帐号'].text = "admin"

请注意,在赋值前,QT4A会先关闭软键盘以避免软键盘的干扰,调用了其disable_soft_input接口,所以你执行完用例后,如果发现软键盘无法调出,而你手动又需要使用的话,可以自行在手机设置中切换输入法,则会恢复输入法开启状态或重启手机。

其他类型

其他类型的使用也与上述的TextView、EditText用法类似,各个类型实现的属性和类型不同,只需根据实际调用不同的属性和接口即可。

封装自绘控件

日志图片操作

查看QT4A日志

在用例执行过程中,qt4a会生成详细的log并保存在本地,供用例失败时分析原因,其日志保存在:

  • Win32: 保存在%APPDATA%目录下的qt4a文件夹中
  • linux: 保存在HOME目录下的qt4a文件夹中

logcat日志操作

启动logcat

在用例开始时会去申请Android设备(调用项目测试基类的acquire_device接口),在其中调用start_logcat接口,则会启动logcat日志的生成,如下:

from qt4a.androidtestbase import AndroidTestBase
class DemoTestBase(AndroidTestBase):
    '''demo测试用例基类
    '''

    def acquire_device(self, type='Android', device_id='', **kwds):
        device = super(DemoTestBase, self).acquire_device(device_id, **kwds)
        device.adb.start_logcat([])
        return device

所以,在用例中调用self.acquire_device()就会开始logcat日志的监控了。

注解

下文对logcat的操作都是建立在你开启了logcat的前提上。

查看logcat日志

在AndroidTestBase类的post_test接口中,会把logcat日志保存到当前用例所在目录下。同时在控制台会打印出logcat日志路径,如下:

INFO: logcat日志
   设备:d012se304:D:\proj\AndroidDemoTest\demotest\LoginTest_d012304_1539931489.log

可以在用例执行结束后查看logcat日志,来分析用例失败原因等。

从logcat提取crash日志

如果你希望自动抓取logcat中的crash日志,以便分析应用crash原因,除了qt4a定义的一些系统crash类型外,你还可以自定义crash提取规则,实现如下:

class DemoTestBase(AndroidTestBase):
    '''demo测试用例基类
    '''
    def extract_crash_by_patterns(self):
        """
            #用户定义的crash规则
            @return: 返回规则列表
        """

        self._target_crash_proc_list = [r'com\.qta\.qt4a\.demo.*']  # 要提取crash,必须对该变量赋值你所关心的进程,用正则表达式,关心多个应用则写多个正则

        # pattern_list中的每一个元素是一个二元组(tag_regex, content_regex)。
        # qt4a的logcat日志格式基本是"[com.qta.qt4a.demo(21679)] [2017-01-04 15:44:30.516] E/crash(21679): com.qta.qt4a.demo in current activity is crashed."样式。
        # 其中tag="crash", content="com.qta.qt4a.demo in current activity is crashed.",这2个标签都支持正则。
        # 如果你希望logcat日志中存在该类日志,则判定为crash,那么此时可添加pattern_list.append((r".*", r'.*com\.qta\.qt4a\.demo.* is crashed\.'))。
        pattern_list = []
        pattern_list.append((r'.*', r'.*com\.qta\.qt4a\.demo.* is crashed\.'))  # crash规则一,只是举例的crash规则,实际根据App来定
        pattern_list.append((r'StatisticCollector', r'getCrashExtraMessage\s+isNativeCrashed.*')) #crash规则二,只是举例的crash规则,实际根据App来定
        #你还可以继续添加其他crash规则

        return pattern_list

通过在项目测试基类的extract_crash_by_patterns接口中自定义你的应用crash规则(规则支持正则匹配),用例执行期间如果logcat日志内容中匹配到你定义的规则,则会在用例执行完成时,在控制台窗口打印出保存的crash日志的路径。

读取logcat日志内容

在用例执行过程中,你也可能需要读取logcat日志内容,以判断某些逻辑是否正常,那么在你申请到设备后,可以调用设备类的read_logcat接口,如下:

def run_test(self):
    device = self.acquire_device()
    # UI operation
    err_msg = r'.*failCode:.* errorMsg:.* service:.*'
    log = self.device.read_logcat('crash', 'com.qta.qt4a.demo', err_msg)
    # other operation

如上,可以调用read_logcat接口读取出最近一条或所有满足规则的日志,read_logcat的第一个参数传入tag,可传入正则,第二个参数是行日志的进程名,第三个参数是行日志的content,可传入正则。

手机日志

在用例执行过程中,随着App的自动操作,在手机目录中也可能产生App生成的日志,假如你需要这些日志,可以从手机中pull出来到PC上,在每个用例的开始会通过接口申请设备:

device = self.acquire_device()

返回了一个device实例,然后可以调用devcie实例的接口把日志提取到PC上,如:

device.pull_file('/sdcard/demo_logs/log.txt', 'tmp_log.txt')

tmp_log.txt也可以传入保存的全路径,即可把手机日志提取到PC。其他类型文件的提取也可调用该接口。如果是需要拷贝文件夹,则可以调用pull_dir接口,详细见接口文档。

日志作为测试结果附件

我们本地会生成一系列日志,QT4A日志,logcat日志,或App本身产生的日志,这些日志如果需要作为测试结果附件,可以达到:

  • 本地调试:控制台显式地显示日志信息及路径
  • 远程执行任务: 日志会一并上传到报告平台展示出来

只需要在用例中或测试基类(如DemoTestBase)的接口(例如post_test接口)中调用:

phone_files = {'device_id': 'D:\testcase\phone_log.txt'}
self.test_result.info('手机日志', attachments=phone_files)

图片操作

有时测试用例需要准备一些手机环境,如需要使得手机系统图库中有照片,在App中实现相关图片功能时才能选择图片来使用测试。所以需要我们push图片到手机中去,可以如下:

device = self.acquire_device()
dst_path = '/sdcard/dcim/demo_pic_folder/1.png'
device.push_file(r'D:\1.png', dst_path)
device.refresh_media_store(dst_path) #刷新媒体库

push成功的话可以在手机路径下查看到图片:

_images/push_png.png

请注意,在push_file完成后,还需要调用接口refresh_media_store来刷新系统图库,使得我们push到手机的图片能及时在系统图库中显示。然后你再声明App,并进行你的业务逻辑操作,在业务逻辑中需要选择图库中的图片则不会出现没有任何图片可选的情况,保证了用例的稳定性。

常见设备操作

在Android自动化过程中,避免不了的是对设备的各种操作,如获取当前窗口、发送按键、滑动窗口等,现针对常见的设备操作进行解析,更多的功能请参考接口文档。

执行shell命令

当本地安装了adb之后,就可以打开shell命令窗口执行各类命令,如获取设备信息,执行各种设备操作、访问各个目录文件等,QT4A中提供了统一的访问接口run_shell_cmd:

device = self.acquire_device()
device.run_shell_cmd('ls -l /data/local/tmp') #查看tmp目录下的文件信息
device.run_shell_cmd('df')  #查看磁盘信息
device.run_shell_cmd('getprop | grep model')  #获取设备的model信息

QT4A中使用该接口封装了很多常用操作,你可以优先使用QT4A中已经封装好的接口。如果在QT4A中找不到你所需要的操作,你还可以直接调用该接口进行你的操作。下文中的device对象也如该代码片段中实例化,不再重复。

如果你需要root权限或应用权限才能执行命令成功,而你的手机又未root,执行Device类的run_shell_cmd遇到权限问题的话,可以改为如下调用:

app = DemoApp(device)
app.run_shell_cmd('ls -l /data/data/com.qta.qt4a.demo')

如上,调用App类的run_shell_cmd的话,会区分当前手机是否root,来对应调用。所以你可以根据实际自行决定调用Device类的还是AndroidApp类的run_shell_cmd接口。

点击屏幕

如果你不希望调用界面各个控件如TextView等封装的click接口去点击屏幕,你可以使用点击屏幕坐标的方式:

screen_width,screen_heght = device.screen_size  #获取屏幕宽度、高度
device.run_shell_cmd('input tap %d %d' % (screen_width/2, screen_heght/2))

这样你便实现了点击屏幕正中间的功能。

警告

通常情况下请优先使用QT4A各个控件类型提供的click接口去点击,只有在特殊情况下不方便使用该接口才改为点击屏幕固定坐标的方式。

发送虚拟按键

Android设备上有很多虚拟按键,如HOME键、BACK键等,QT4A封装了常见的按键,在用例中实例化App类后可以获得app对象:

device = self.acquire_device()
app = DemoApp(device)

然后可以模拟发送各类按键,如发送返回键:

app.send_back_key()

发送HOME键:

app.send_home_key()

发送ENTER键:

app.send_enter_key()

如有其他按键,你还可以调用send_key接口发送,如发送DEL键:

from qt4a.androiddriver.util import KeyCode
app.send_key(KeyCode.KEYCODE_DEL)

滑动屏幕

有时候你需要针对屏幕进行滑动,而屏幕又没有ListView、ScrollView、ViewPager等控件(该类控件QT4A已封装了滑动相关接口),你需要自行调用滑动屏幕的接口,例如若App类开头有一些广告页面,需要滑动才会消失,那么可以调用:

screen_width, screen_height = device.screen_size
x1 = screen_width / 4
x2 = screen_width*3 / 4
y1 = y2 = screen_height / 2
app.get_driver().drag(x1, y1, x2, y2)

这样便实现了水平滑动的操作,但不同界面滑动的坐标需要做不同的设置才能滑动成功,所以请根据实际产品功能设置坐标值。

注解

如果是滑动ListView、ScrollView、 ViewPager等,请优先使用QT4A已经封装的滑动接口,不要自行再去调用drag接口。

获取当前窗口Activity

在用例编写过程中,我们常常需要知道当前窗口是什么,有可能应用会根据场景弹出不同的窗口,这个时候可以先判断当前窗口,再去实例化对应的面板类(我们在lib层定义的面板,如LoginPanel等),就可以处理不确定窗口出现的场景:

current_activity = device.get_current_activity()

即可获取当前窗口Activity,同时,这在需要等待一些目标窗口出现时十分有用(有可能前序会有广告窗口的自动跳转,然后才出现目标窗口),假设你已经获得device类对象,如:

import time
current_activity = None
timeout = 5
time0 = time.time()
while time.time() - time0 < timeout:
   current_activity = device.get_current_activity()
   if current_activity != LoginPanel.Activity:
       time.sleep(0.5)
   else:
       break
else:
   raise RuntimeError('登录窗口未找到,当前窗口为%s' % current_activity)

屏幕截图

在执行用例过程中,有些场景需要截图下来帮助分析,可以调用接口:

device.take_screen_shot(pic_path) #pic_path传入保存到本地的路径

当然,QT4A在用例失败时也会截图保存App现场。如你还需其他截图,可自行调用。

Web自动化测试

Web自动化概述

QT4A使用了 QT4W 提供的Web自动化基础能力来支持Web自动化测试,以下两种模式均可支持:

  • 纯网页开发的应用(Web App)
  • 原生控件+网页混合开发的应用(Hybrid App)

开始本节学习前,请熟悉以下基础知识:

安装QT4W

在安装了QT4A后,如果需要进行Web自动化测试,还需要通过pip安装的方式安装QT4W:

pip install qt4w

内嵌WebView基本结构

本节以内嵌WebView为例,说明一个基本web用例该如何编写。Web页面涉及几个重要的概念,如WebView、WebPage、WebElement等:

  • WebView: 是Android平台上一个特殊的Native View, 可用来显示网页,由 qt4a.andrcontrols.WebView 实现。
  • WebPage: 一个WebPage对应一个H5页面,包含多个Web控件元素,该类在QT4W库中提供实现 。
  • WebControl: 是H5页面下的web控件元素,QT4W下实现了WebElement、FrameElement、InputElement、SelectElement等Web控件类型,大部分的Web控件都是WebElement类型,该类在QT4W库中提供实现 。

Web测试用例

你依然可从github下载 Demo工程。用例的基本结构、用例的native层封装,可见QT4A《快速入门》一节。本节主要介绍web页面的封装。

demotest/webview.py中实现 WebViewTest用例:

首先实例化包含native webview控件的面板DemoWebPanel,封装其webview控件并将其传给WebPage类(在该例中封装了DemoWebPage)作为参数:

demo_webpanel = DemoWebPanel(app)
demo_webpage = DemoWebPage(demo_webpanel.Controls["webview"])

webview控件是Android原生控件,像其他普通Android控件一样封装即可,如下:

class DemoWebPanel(Window):
    '''Home界面
    '''
    Activity = 'com.qta.qt4a.demo.WebViewActivity'
    Process = 'com.qta.qt4a.demo'  #此时Process不写也可以,因为跟主进程一致

    def __init__(self, demoapp):
        super(DemoWebPanel, self).__init__(demoapp)
        self.update_locator({'webview': {'type': WebView, 'root': self, 'locator': QPath('/Id="sampleWebView"')},
                            })

webview控件的Type声明为WebView。

接下来就可以使用DemoWebPage类封装的各个Web控件了,如:

self.assert_equal("标题应该是WebView Demo", demo_webpage.control("标题").inner_text , "WebView Demo")

中读取了Web页面的名为”标题”的控件的文本内容(inner_text属性即读取文本);而:

demo_webpage.control("qt4a_source_code").click()

则是点击了封装的”qt4a_source_code”Web控件。最后验证点击后页面是否发生跳转,可以通过读取Web页面的url属性来判断,如下:

self.wait_for_equal('页面发生跳转,url变为https://github.com/Tencent/QT4A', demo_webpage, 'url', 'https://github.com/Tencent/QT4A')

注解

从上面可看出,Android原生控件获取方式为Controls[“xx”],如demo_webpanel.Controls[“webview”],而Web控件的获取方式为control(“xx”),如 demo_webpage.control(“标题”),写时请注意区分control和Controls、小括号和中括号。

WebPage封装

用例中使用的DemoWebPage封装如下:

class DemoWebPage(WebPage):
    '''Demo Web页面
    '''
    ui_map = {'标题': XPath('//h2'),
              'qt4a_source_code':{'type': WebElement,'locator': XPath('//div[@id="qt4a_code"]/a'),}
            #'qt4a_source_code':XPath('//div[@id="qt4a_code"]/a'), #如果type为WebElement也可直接简化为该写法,所以此时qt4a_source_code这么定义也可以
              }

继承于WebPage基类,在ui_map字典中实例化该页面的Web控件。而Web控件的封装规则参考 Web控件的标识与使用 一节。那么在Android端,如何获取到Web控件的XPath进行封装呢?QT4A提供了AndroidUiSpy的控件探测工具进行获取。可从github下载 AndroidUiSpy工具,并参考 AndroidUiSpy使用文档

用例执行

用例执行请参考《调试执行》一节。用例执行后结果如下:

_images/login1.png _images/click_go_to_webview.png _images/click_web_btn.png _images/jump_to_new_page.png

高级特性

QT4A中有部分功能是不常需要用到的,不过特定场景下可以使用的,列举如下。

禁止窗口弹出

有的应用,会不定期弹出特定窗口(是应用定义的窗口,不是系统弹窗),这些时机无法把握,也就无法在用例中特定时机进行处理。这个时候,如果该窗口不是我们的测试点,可以在项目App类屏蔽该窗口弹出:

class DemoApp(AndroidApp):
    def __init__(self, device=None, clear_state=True, kill_process=True, net_type='wifi', start_extra_params={}):
        import time
        super(DemoApp, self).__init__(self.process_dict['Main'], device)
        #other operation
        self._start(clear_state, kill_process, start_extra_params=start_extra_params)

    def _start(self, clear_state=True, kill_process=True, start_extra_params={}):
        '''启动Android demo apk
        '''
        # 启动Activity的相关代码,此处省略,详细见Demo样例
        #接下来新增禁止窗口弹出逻辑
        self.set_demo_ads_activtiy_popup()

    def set_demo_ads_activtiy_popup(self, popup=False):
        '''设置广告页面是否可以弹出
        '''
        self.set_activity_popup('com.xx.xxActivity', popup)  #第一个参数是Activity名

假设在应用中,不定期会弹出广告窗口,可以按上面调用set_activity_popup接口禁止弹出,由于Demo App实际没有什么广告窗口,所以这里随意指定了个Activity名。

该接口并不是任何场景都能用的,

  • 如果你要禁止的Activity,有不同的窗口页面对应,那么你会把其他正常需要弹出的页面也屏蔽掉,所以需要这个Activity只对应了广告这个窗口,才可使用。
  • 这个接口不能作用于非App内页面,如一些系统弹框等,该接口不适用。
  • 并不是App类任意页面都可以随意禁用掉,如果页面间有关联,有可能影响其他功能逻辑,所以请根据实际,需要且可用时才使用该功能。

监控任务

如果在应用中,不定期会弹出一些窗口,且这类窗口你希望禁用掉,但由于该窗口与其他你不希望禁用掉的窗口有相同的Activity名,所以不能直接按上面禁止窗口弹出的方法处理,此时你可以建个监控任务,监控到需要点掉的窗口,再去处理:

class DemoApp(AndroidApp):
    def __init__(self, device=None, clear_state=True, kill_process=True, net_type='wifi', start_extra_params={}):
        import time
        super(DemoApp, self).__init__(self.process_dict['Main'], device)
        #other operation
        self._start(clear_state, kill_process, start_extra_params=start_extra_params)

    def _start(self, clear_state=True, kill_process=True, start_extra_params={}):
        '''启动Android demo apk
        '''
        # 启动Activity的相关代码,此处省略,详细见Demo样例
        #接下来新增应用启动后检测无用窗口
        self.add_monitor_task(self.detect_invalid_activity)

    def detect_invalid_activity(self):
        '''检测不想要弹出的窗口
        '''
        current_activity = self.device.get_current_activity()
        if current_activity == 'xx.xx.upgradeActivity':
            self.send_back_key()
        elif current_activity == 'xx.xx.adviseActivity':
            #定义该面板,并点击面板中的例如关闭按钮关闭页面。例如:
            # advise_panel = AdvisePanel(self)
            #if advise_panel.Controls['关闭推荐'].exist():
               #advise_panel.Controls['关闭推荐'].click()
            pass
        #……

如上,调用AndroidApp类提供的add_monitor_task接口,可以启动监控线程监控不期望弹出的窗口。当有监控线程监控的窗口弹出时,可以例如判断该窗口是否有特定按钮存在,如果有,就是需要关闭的窗口,可以点击关闭按钮关闭,如果没有,这个窗口就不是该监控线程需要处理的。当你需要停止监控时,可以调用AndroidApp类的stop_monitor接口停止监控。

注解

监控线程监控是有时延的,因为其是每间隔一段时间才去判断一次是否有窗口需要处理,所以只有在窗口不定期弹出需要处理掉才使用监控线程的方式。

系统授权弹窗

在Android6.0及以上系统,发现即使root过的机型,也还是无法关闭系统授权弹框,此时可以尝试调用如下接口grant_all_runtime_permissions:

class DemoApp(AndroidApp):
    def __init__(self, device=None, clear_state=True, kill_process=True, net_type='wifi', start_extra_params={}):
        import time
        super(DemoApp, self).__init__(self.process_dict['Main'], device)
        #other operation
        self._start(clear_state, kill_process, start_extra_params=start_extra_params)
        self.grant_all_runtime_permissions()

调用该接口且生效的话,用例执行过程中就不再会弹出授权提示框,使用例可以正常执行完成。当然,该接口不适用于非root机型。非root机型的系统弹框需要再自行关闭。

测试桩插件

什么是测试桩插件

QT4A通过将测试桩注入到被测应用进程中,获取进程中的相关数据,如控件树信息等。但是有些情况下,用户也需要注入自己的代码,以获取自己期望的数据。QT4A测试桩提供了一种将用户指定的dex文件注入到被测进程中的能力,同时,借助于QT4A的通信通道,用户可以传输自己的数据,而不需要自己创建信道。

如何实现测试桩插件

测试桩插件本质上还是一个dex,可以使用Java语言编写。但是与普通的dex相比,它还是存在着一些差异。最主要的,就是它需要实现一个入口类,用来作为整个插件的执行入口。

public class MyPlugin {
    private int handleRequest(String arg){
        // do your job
        return 123;
    }

    public static JSONObject main(JSONObject args) {
		JSONObject result = new JSONObject();
		try{
			String cmd = args.getString("SubCmd");
            View view = (View)args.get("Control"); //获取View实例
            String arg = args.getString("Arg"); // 获取参数
            if("Hello".equals(cmd)){
                MyPlugin plugin = new MyPlugin();
                try{
                    result.put("Result", plugin.handleRequest(arg));
                }catch(Exception e){
                    result.put("Error", e.toString());
                }
            }else{
                result.put("Result", -1);
            }
		}catch(JSONException e){
			e.printStackTrace();
		}
		return result;
	}
}

MyPlugin.main是整个dex的入口函数,会被QT4A测试桩调起,并传入客户端传入的参数。main里一般会解析这些参数,并调用相应的方法,返回结果可以通过Result字段返回,函数执行的报错信息,可以通过Error字段返回,也可以打印到logcat中。不过,不捕获异常也没有关系,因为QT4A测试桩在调用插件函数的时候也会主动捕获异常的。

如何编译测试桩插件

  1. 使用普通编译apk的方式编译,然后提取出apk中的classes.dex
  2. 使用命令行方式编译,具体命令如下:
javac -encoding utf-8 -target 1.7 -d bin src/com/test/plugin/MyPlugin.java -bootclasspath $SDK_ROOT/platforms/android-24/android.jar # $SDK_ROOT为Android SDK根路径

cd bin
jar cvf plugin.jar com

$SDK_ROOT/build-tools/25.0.3/dx --dex --output=plugin.jar ../plugin.jar # 25.0.3要改成实际安装的版本

这样会在根目录生成目标文件plugin.jar,虽然这个文件是.jar结尾,但本质上是一个zip格式的dex文件。

如何使用测试桩插件

先将编译出来的dex/jar文件push到设备某一路径下,如:/data/local/tmp/plugin.jar

然后使用以下代码来调用:


result = driver.call_external_method(jar_path, # plugin.jar在设备中的路径
    'com.test.plugin.MyPlugin', # 替换为真正的插件入口类路径
    Control=hashcode, # 如果需要操作控件可以在这里指定控件的hashcode
    SubCmd='Hello', # 子命令
    Arg='you param' # 子命令的参数
    )

driverAndroidDriver实例。建议用户对接口再做一层封装,这样更像是本地方法调用。

QT4A配置项

配置项可在项目根目录下的settings.py中进行配置。特别是对于一些QT4A库已经实现的功能,你直接在配置项中开启即可使用。

配置HOST

当用例需要在测试环境中才能顺利执行时,你可能需要对手机配置HOST,AndroidTestBase中已实现这样的功能。用例开始会调用到AndroidTestBase的acquire_device申请设备,在该接口中实现了HOST的配置。如果你想开启配置HOST的功能,只需要增加下面的配置项:

QT4A_DEVICE_HOSTS=''103.22.4.120 a.b.c.com\n103.12.4.120 c.d.com''

具体的HOST内容请修改为你测试的HOST,那么,在调用接口acquire_device申请到设备时,就会将你设置的HOST自动配置到手机环境中。同时,用例执行结束时,也会自动在测试基类的post_test中恢复Android设备的HOST环境。

配置WIFI

有时需要在特定WIFI下进行测试,这时可以配置需要连接的网络,如:

QT4A_WIFI_SSID="wifi_name"
QT4A_WIFI_PASSWORD="wifi_password"

那么在调用:

device.enable_wifi()

接口时会尝试去连接wifi。不过高系统版本的非root手机的系统权限变得更加严格了,可能会由于权限弹框等问题自动连接失败。所以通常可以优先选择手动把wifi连接好,再跑自动化用例,这时可以不配置这两个配置项。

Q&A

接口文档

接口文档

qt4a.andrcontrols Package

定义Android控件

class qt4a.andrcontrols.AbsListView(*args, **kwds)

基类:qt4a.andrcontrols.ScrollView

ListView和GridView基类

get_child(idx)

提供使用索引访问子元素的方法

reach_bottom

滑动区域达到底部

reach_top

滑动区域达到顶部

reached_bottom

滑动区域达到底部

reached_top

滑动区域达到顶部

scroll_down_one_page()

向下滑动一页

scroll_up_one_page()

向上滑动一页

wait_for_complete(timeout=2)

等待ListView控件变化,比如需要读取本地数据

class qt4a.andrcontrols.ActionMenuItemView(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.TextView

class qt4a.andrcontrols.AppCompatEditText(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.EditText

class qt4a.andrcontrols.AppCompatImageView(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.ImageView

class qt4a.andrcontrols.Button(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.TextView

按钮类

class qt4a.andrcontrols.CheckBox(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.CompoundButton

选择按钮

class qt4a.andrcontrols.CheckedTextView(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.TextView

可选文本框

checked

是否已选

class qt4a.andrcontrols.ChromiumWebView(url=None, title=None, *args, **kwargs)

基类:qt4a.andrcontrols.WebkitWebView

Chromium内核的WebView

convert_frame_tree(frame_tree, parent=None)

将frame tree转化为Frame对象

create_socket()

创建socket对象

eval_script(frame_xpaths, script)

在指定frame中执行JavaScript,并返回执行结果

参数:
  • frame_xpaths (list) – frame元素的XPATH路径,如果是顶层页面,怎传入“[]”
  • script (string) – 要执行的JavaScript语句
get_debugger(timeout=10)

获取当前页面的RemoteDebugger实例

get_page_url()

获取当前WebView页面的url

class qt4a.andrcontrols.CompoundButton(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.Button

可选按钮,一般用于实现Switch

class qt4a.andrcontrols.DatePicker(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.FrameLayout

时间选择器

class qt4a.andrcontrols.DropdownView(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.View

class qt4a.andrcontrols.EditText(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.TextView

输入文本框

click()

click后自动关闭输入法

send_text(text)

输入按键,此方法不能输入中文和大写字母

class qt4a.andrcontrols.FrameLayout(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.ViewGroup

class qt4a.andrcontrols.Gravity(grav)

基类:object

用于控制控件靠左、居中等位置的属性

class qt4a.andrcontrols.GridView(*args, **kwds)

基类:qt4a.andrcontrols.AbsListView

格子视图

class qt4a.andrcontrols.ImageButton(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.ImageView

class qt4a.andrcontrols.ImageView(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.View

显示图片控件

capture(save_path='')

保存图片到本地

save(save_path='')

保存图片到本地 to de deleted

class qt4a.andrcontrols.LinearLayout(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.ViewGroup

class qt4a.andrcontrols.ListItem(view, idx=None)

基类:qt4a.andrcontrols.View

为方便遍历AbsListView,表示AbsListView的直接子孩子

has(key)

是否存在该节点

参数:key (string) – 封装时定义的子节点名
class qt4a.andrcontrols.ListView(*args, **kwds)

基类:qt4a.andrcontrols.AbsListView

列表视图

class qt4a.andrcontrols.ProgressBar(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.View

进度条

progress

进度

class qt4a.andrcontrols.RadioButton(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.CompoundButton

单选按钮

class qt4a.andrcontrols.RadioGroup(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.LinearLayout

class qt4a.andrcontrols.RecyclerView(*args, **kwds)

基类:qt4a.andrcontrols.AbsListView

虽然不是继承自ListView,但是可以复用其部分代码,所以从ListView继承过来

get_child(idx)

提供使用索引访问子元素的方法

reached_left

滑动区域达到最左边

reached_right

滑动区域达到最右边

scroll_down_one_page()

向下滑动一页

scroll_left_one_page()

向左滑动一页

scroll_right_one_page()

向右滑动一页

scroll_up_one_page()

向上滑动一页

class qt4a.andrcontrols.RelativeLayout(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.ViewGroup

gravity

位置属性

class qt4a.andrcontrols.ScrollView(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.FrameLayout

滚动视图

pull_down_refresh()

下拉刷新

reach_bottom

滑动区域达到底部

reach_top

滑动区域达到顶部

scroll(x, y, count=5, interval=0.04)

横向或纵向滚动

参数:
  • x (int) – x > 0 时向左滑动,x = x1 - x2,滚动条向右
  • y (int) – y > 0时向上滑动,y = y1 - y2,滚动条向下
  • count (int) – 分为几次滑动
scroll_down_one_page()

向下滑动一页

scroll_to_bottom()

滑动到底部

scroll_to_top()

滑动到顶部

scroll_up_one_page()

向上滑动一页

class qt4a.andrcontrols.SeekBar(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.ProgressBar

可修改进度的进度条

progress

进度

class qt4a.andrcontrols.TabWidget(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.View

Tab控件

selected_index

当前所选项的索引

class qt4a.andrcontrols.TextView(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.View

包含Text的View

click_clickable_span()

点击TextView中的ClickableSpan区域

hint_text

空白提示文本

html_style_text

HTML格式文本

image_resource_name

图像资源名称

text_color

字体颜色

text_size

字体大小

class qt4a.andrcontrols.VerticalSwipe(view)

基类:object

纵向滑动包装类

rect

left, top, width, height

swipe_down()

向下滑动

swipe_up()

向上滑动

class qt4a.andrcontrols.View(activity, root, driver, locator=None, hashcode=0)

基类:object

控件基类

clickable

是否可点击 to be deleted

container

获取所在容器类

content_desc

控件描述

double_click(x_offset=0, y_offset=0)

双击

参数:
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
exist()

判断控件是否存在

get_metis_view()

返回MetisView

hashcode

控件唯一标识,只有真正访问控件信息时才会去获取该标识

multiple_click(count=3, x_offset=0, y_offset=0)

多次点击

参数:
  • count (int) – 点击多少次
  • x_offset (int或float) – 距离控件区域左上角的横向偏移。
  • y_offset (int或float) – 距离控件区域左上角的纵向偏移。
scroll_to_visible(root_rect=None, self_rect=None)

滚动到可视区域

swipe(direct)

滑动

参数:direct (string,只能是“up”、“down”、“left”、“right”中的一个值) – 方向
wait_for_exist(timeout=10, interval=0.1)

等待控件出现

wait_for_invisible(timeout=10, interval=0.2)

等待控件不可见

参数:
  • timeout (int/float) – 超时时间,单位:秒
  • interval (int/float) – 重试间隔时间,单位:秒
wait_for_value(prop_name, prop_value, timeout=10, interval=0.5, regularMatch=False)

等待控件属性值出现, 如果属性为字符串类型,则使用正则匹配

参数:
  • prop_name – 属性名字
  • prop_value – 等待出现的属性值
  • timeout – 超时秒数, 默认为10
  • interval – 等待间隔,默认为0.5
  • regularMatch – 参数 property_name和waited_value是否采用正则表达式的比较。默认为不采用(False)正则,而是采用恒等比较。
wait_for_visible(timeout=10, interval=0.2)

等待控件可见

参数:
  • timeout (int/float) – 超时时间,单位:秒
  • interval (int/float) – 重试间隔时间,单位:秒
class qt4a.andrcontrols.ViewGroup(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.View

class qt4a.andrcontrols.ViewPager(activity, root, driver, locator=None, hashcode=0)

基类:qt4a.andrcontrols.ViewGroup

横向滚动视图

current_item_index

当前显示项的索引

item_count

总项数

scroll(count=1, rect=None)

左右滚动

参数:count (int) – 滑动次数,大于0表示向右滑动,小于0表示向左滑动
class qt4a.andrcontrols.WebView(*args, **kwargs)

基类:qt4a.andrcontrols.View

Web页面容器

click(x_offset=None, y_offset=None)

点击WebView中的某个坐标

参数:
  • x_offset (int/float) – 与WebView左上角的横向偏移量
  • y_offset (int/float) – 与WebView左上角的纵向偏移量
disable_soft_input()

禁用软键盘

drag(x1, y1, x2, y2)

从(x1, y1)点滑动到(x2, y2)点

参数:
  • x1 (int/float) – 起点横坐标
  • y1 (int/float) – 起点纵坐标
  • x2 (int/float) – 终点横坐标
  • y2 (int/float) – 终点纵坐标
eval_script(frame_xpaths, script)

在指定frame中执行JavaScript,并返回执行结果(该实现需要处理js基础库未注入情况的处理)

参数:
  • frame_xpaths (list) – frame元素的XPATH路径,如果是顶层页面,怎传入“[]”
  • script (string) – 要执行的JavaScript语句
long_click(x_offset, y_offset, duration=1)

长按WebView中的某个坐标

参数:
  • x_offset (int/float) – 与WebView左上角的横向偏移量
  • y_offset (int/float) – 与WebView左上角的纵向偏移量
  • duration (int/float) – 按住的持续时间
screenshot()

当前WebView的截图 :return: PIL.Image

send_keys(keys)

向WebView中的输入框发送按键

参数:keys (string) – 要发送的按键
visible_rect

WebView控件可见区域的坐标信息

webdriver_class

WebView对应的WebDriver类

webview_impl
class qt4a.andrcontrols.WebkitWebView(*args, **kwargs)

基类:qt4a.andrcontrols.View

4.4以下版本中的WebView

eval_script(frame_xpaths, script)

执行JavaScript

class qt4a.andrcontrols.Window(app_or_driver, wait_activity=True, **kwds)

基类:object

控件容器基类

Controls

返回控件集合。使用如foo.Controls[‘最小化按钮’]的形式获取控件

get_metis_view()

返回MetisView

hasControlKey(control_key)

是否包含控件control_key

返回类型:boolean
post_init()

窗口类自定义的初始化逻辑

update_locator(locators)

更新控件定义

wait_for_exist(timeout=10, interval=0.5)

等待窗口出现

qt4a.andrcontrols.func_wrap(func)

用于方法包装,主要作用是发现控件失效时能够更新控件

qt4a.andrcontrols.lazy_init(func)

调用到此函数时进入延迟初始化逻辑

qt4a.androidapp Package

Android App基类

class qt4a.androidapp.AndroidApp(process_name, device, init_env=True)

基类:object

Android App基类

add_monitor_task(task, last_time=0, interval=1)

添加监控任务

close()

关闭所有打开的driver

close_activity(activity)

关闭Activity

close_driver(process_name)

关闭测试桩

device

返回所在的设备

drag(direction='right', count=1)

滑动屏幕,适合一大块区域(如引导屏)的滑动,无需关心具体的控件

参数:
  • direction (string) – 方向,right、left、top、bottom
  • count (int) – 次数
enable_media_permission()

允许截屏和录音 Capture the device’s display contents and/or audio

enable_system_alert_window()

允许弹出悬浮窗口

get_driver(process_name='')

根据进程名获取driver对象,默认为主程序driver

get_string_resource(string_id, lang='')

获取字符串资源

参数:
  • string_id (string) – 字符串ID
  • lang (string) – 字符串语言,默认为当前系统语言
grant_all_runtime_permissions()

授予所有运行时权限

is_debug()

是否是debug包

monitor_thread()

监控线程

on_crashed()

发生Crash之后的处理

process_name

应用所在的主进程名

remove_all_task()

移除所有任务

remove_monitor_task(task)

移除监控任务

run_shell_cmd(cmdline, **kwargs)

root下使用root权限执行命令,非root下使用应用权限执行命令

send_back_key()

发送返回按键

send_enter_key()

发送回车键

send_file(activity, file_path)

向Activity发送文件

参数:
  • activity (string) – 目标Activity名称
  • file_path (string) – 文件在PC上的路径
send_home_key()

发送Home键

send_image(activity, image_path)

向Activity发送图片,支持多图

参数:
  • activity (string) – 目标Activity名称
  • image_path (string | list) – 图片在PC上的路径或路径列表
send_key(key)

发送单个按键

参数:key (string) – 发送的按键字符串
send_menu_key()

发送菜单键

set_activity_popup(activity, popup=False, process_name='')

设置Activity是否可以弹窗

set_driver_thread_priority(process_name='', priority='THREAD_PRIORITY_FOREGROUND')

设置测试线程优先级

start_monitor()

启动监控

stop_monitor()

停止检测

wait_for_activity(activity, timeout=15, interval=0.5)

等待Activity打开

参数:
  • activity (string) – Activtiy名称
  • timeout (int/float) – 超时时间,单位:S
  • interval (int/float) – 检查间隔时间,单位:S

qt4a.androidtestbase Package

Android自动化测试基类

class qt4a.androidtestbase.AndroidTestBase(testdata=None, testdataname=None, attrs=None)

基类:testbase.testcase.TestCase

QT4A测试基类

acquire_device(device_id=None, **kwargs)

申请设备接口

参数:device_id (string) – 设备ID,用于本地调试
add_logcat_callback(device)

判断logcat日志中是否包含debug级别的日志,如果没有,很有可能该手机可以设置日志级别,且本身已设置了过滤debug级别的日志,可尝试操作手机设置

cleanTest()

清理测试环境。慎用此函数,尽量将清理放到postTest里。

clean_test()

清理测试环境。慎用此函数,尽量将清理放到postTest里。

extract_crash_from_logcat(log_list)

检测logcat日志中是否有crash发生并萃取出相关日志

get_extra_fail_record()

用例执行失败时,用于获取用例相关的错误记录和附件信息

initTest(testresult)

初始化测试环境。慎用此函数,尽量将初始化放到preTest里。

init_test(testresult)

初始化测试环境。慎用此函数,尽量将初始化放到preTest里。

postTest()

清理测试用例

post_test()

清理测试用例

take_screen_shot(app_or_device, info)

生成当前指定设备的屏幕截图

参数:
  • app (AndroidApp or AndroidDevice) – AndroidApp类或AndroidDevice实例
  • info (string) – 显示的提示信息
class qt4a.androidtestbase.EnumCrashType

基类:object

枚举crash类型

qt4a.androidtestbase.get_valid_file_name(file_name)

过滤掉文件名中的非法字符

qt4a.device Package

Android 设备模块

class qt4a.device.AndroidDeviceResourceHandler(resource_lock_type=<class 'testbase.resource.LocalResourceLock'>)

基类:testbase.resource.LocalResourceHandler

iter_resource(res_group=None, condition=None)

遍历全部资源(可以按照优先级顺序返回来影响申请资源的优先级)

参数:
  • res_group (str) – 资源分组
  • condition (dict) – 资源属性匹配
返回:

iterator of resource, dict type with key ‘id’

Rtypes:

iterator(dict)

class qt4a.device.Device(id_or_adb_backend=None)

基类:object

Android设备类

add_phone_contacts(name, phone)

添加手机联系人

check_netstat()

检查网络状态

clear_camera_default_app()

清除默认相机应用

clear_data(package_name)

清理应用数据

参数:package_name (string) – 包名
clear_http_proxy()

清除http代理

click(x, y)

单击屏幕坐标

参数:
  • x (int/float) – 横坐标
  • y (int/float) – 纵坐标
connect_vpn(vpn_type, server, username, password)

连接VPN

参数:
  • vpn_type (string) – VPN类型
  • server (string) – 服务器地址
  • username (string) – 用户名
  • password (string) – 密码
connect_wifi(wifi_name, wifi_pass='')

连接指定的Wifi

参数:wifi_name (string) – WiFi名称
copy_file(src_path, dst_path)

设备内复制文件

参数:
  • src_path (string) – 源路径
  • dst_path (string) – 目标路径
country

国家

cpu_type

cpu类型

current_activity

当前Activity

debuggable

是否是调试版系统

del_phone_contacts(name)

删除手机联系人

delete_file(file_path)

删除文件

参数:file_path (string) – 文件在设备中的路径
delete_folder(folder_path)

删除文件夹

参数:folder_path (string) – 手机中的文件夹路径
device_host

设备主机

device_id

设备ID

disable_data_connection()

禁用数据连接

disable_network()

禁用所有网络

disable_wifi()

禁用Wifi

disconnect_vpn()

断开VPN

drag(x1, y1, x2, y2, count=5, wait_time=40, send_down_event=True, send_up_event=True)

滑动

参数:
  • x1 (int) – 起始x坐标
  • y1 (int) – 起始y坐标
  • x2 (int) – 终止x坐标
  • y2 (int) – 终止y坐标
  • count (int) – 滑动的步数
  • wait_time (int,单位:ms) – 每步间等待的时长
  • send_down_event (bool) – 是否发送DOWN事件
  • send_up_event (bool) – 是否发送UP事件
enable_network()

启用任一网络,优先使用Wifi

enable_wifi()

启用Wifi

end_call()

挂断电话

static extract_record_frame(file_path, save_dir)

提取录屏文件中的帧

get_available_data_storage()

获取数据存储区可用空间

get_available_external_storage()

获取sdcard可用存储空间

get_clipboard_text()

获取剪切板内容

get_current_activity()

获取当前Activtiy

get_external_sdcard_path()

获取外置SD卡路径

get_imei()

获取设备imei号

get_phone_contacts()

获取手机联系人列表

get_sim_card_state()

获取sim卡状态

get_static_field_value(pkg_name, cls_name, field_name, field_type='')

获取类中静态变量的值

参数:
  • pkg_name (string) – 包名
  • cls_name (string) – 类名
  • field_name (string) – 字段名
get_string_resource(pkg_name, string_id, lang='')

获取字符串资源

get_string_resource_id(pkg_name, text)

获取字符串资源ID

get_system_timezone()

获取当前系统时区

grant_all_runtime_permissions(package_name)

给APP授予所有运行时权限

has_camera()

是否有摄像头

has_gps()

是否有GPS

has_sim_card()

是否有sim卡

imei

手机串号

install_package(pkg_path, pkg_name='', overwrite=False)

安装应用

参数:
  • pkg_path (string) – 安装包路径
  • pkg_name (string) – 应用包名
  • overwrite (bool) – 是否是覆盖安装
is_app_installed(app_name)

应用是否安装

is_debug_package(package_name)

是否是debug包

is_emulator_device()

是否是模拟器设备

is_file_exists(file_path)

判断文件或目录是否存在

参数:file_path (string) – 文件或目录在设备中的路径
is_rooted()

是否root

kill_process(package_name)

杀死进程

参数:package_name (string) – 应用包名
language

语言

list_dir(dir_path)

列取目录

lock_keyguard()

锁屏

long_click(x, y, duration=1)

长按屏幕坐标

参数:
  • x (int/float) – 横坐标
  • y (int/float) – 纵坐标
  • duration (int/float) – 按住时长,单位为秒
mkdir(dir_path)

创建目录

参数:dir_path (string) – 要创建的目录路径
model

设备型号

modify_hosts(new_hosts=[])

修改hosts

参数:new_hosts (list) – 要修改的host列表,如果为空,表示恢复为默认hosts
play_sound(file_path, volume=50)

播放语音

参数:file_path (string) – 音频文件路径
pull_dir(src_path, dst_path)

从手机中拷贝文件夹到PC

参数:
  • src_path (string) – 手机上的源目录路径
  • dst_path (string) – PC上的目的目录路径
pull_file(src_path, dst_path)

将手机中的文件拷贝到PC中

参数:
  • src_path (string) – 手机中的源路径
  • dst_path (string) – PC中的目的路径
push_dir(src_path, dst_path)

向手机中拷贝文件夹

参数:
  • src_path (string) – PC上的源目录路径
  • dst_path (string) – 手机上的目的目录路径
push_file(src_path, dst_path)

向手机中拷贝文件

参数:
  • src_path (string) – PC上的源路径
  • dst_path (string) – 手机上的目标路径
read_logcat(tag, process_name_pattern, pattern, num=1)

查找最近满足条件的一条log

参数:
  • tag (string) – 期望的Tag
  • process_name_pattern (string) – 期望的进程名,传入正则表达式
  • pattern (Pattern) – 期望匹配的格式
  • num (int) – 返回满足条件的日志条数
reboot(wait_cpu_low=True, usage=20, duration=10, timeout=120)

重启手机

参数:
  • wait_cpu_low (bool) – 是否等待CPU使用率降低
  • usage (int) – cpu使用率阈值
  • duration (int) – 持续时间(秒)
  • timeout (int) – 超时时间,超市时间到后,无论当前CPU使用率是多少,都会返回
record_screen(save_path, record_time, frame_rate=10, quality=20)

录屏

参数:
  • save_path (string) – 保存路径,如果为已存在的目录路径,则会将每一帧图片保存到该目录下
  • record_time – 录制时间,单位:秒
  • frame_rate (int) – 帧率,1-30
  • quality (int) – 压缩质量,10-100
refresh_media_store(file_path='')

刷新图库,显示新拷贝到手机的图片

参数:file_path (string) – 要刷新的图片路径,不指定则刷新整个sdcard
register_screenshot_callback(callback, frame_rate=15)

注册截图回调函数

参数:
  • callback (function) – 回调函数,回调参数为PIL的Image对象
  • frame_rate (int) – 期望的帧率
static release_all_device()

释放所有设备

resolve_domain(domain)

解析域名

run_as(package_name, cmd, **kwargs)

以package_name权限执行命令cmd

参数:
  • package_name (string) – 包名,必须是已经安装的debug包
  • cmd (string) – 命令行
run_shell_cmd(cmd, *args, **kwargs)

执行adb shell命令

static screen_frame_to_video(frame_list, frame_rate, save_path)

将录屏帧序列转换为视频文件

screen_scale

屏幕缩放比例

screen_size

屏幕大小

sdk_version

SDK版本

send_file_to_app(activity, file_path)

向app分享文件

send_image_to_app(activity, image_path)

向app分享图片

send_key(key)

发送按键

参数:key (string) – 按键
send_text(text)

通过输入法方式发送文本

参数:text (string) – 要发送的文本
send_text_to_app(activity, text)

向app分享文本

set_allow_unknown_app(allow=True)

设置是否允许安装未知来源的应用

参数:allow (boolean) – 是否允许
set_app_permission(package_name, perm_name, is_allowed=True)

设置应用权限

参数:
  • package_name (string) – 应用包名
  • perm_name (string) – 权限名称
  • is_allowed (bool) – 是否允许
set_auto_rotate_screen(rotate=False)

设置是否旋转屏幕

参数:rotate (boolean) – 是否旋转
set_camera_photo(pic_path)

设置相机图片,调用该接口后,调用相机接口会返回指定图片

参数:pic_path (String) – 图片在PC中的路径
set_clipboard_text(text)

设置剪贴板内容

set_default_app(action, type, new_app)

设置默认应用

参数:
  • action (String) – 应用针对的类型,如:android.media.action.IMAGE_CAPTURE
  • new_app (String) – 新的应用包名
set_default_input_method(input_method)

设置默认输入法

参数:input_method (string) – 要设置的输入法服务名(package_name/service_name)
set_default_language(lang)

设置默认语言

set_http_proxy(host, port)

设置http代理

参数:
  • host – 代理服务器地址
  • port – 代理服务器端口
set_radio_enabled(enable)

是否启用Radio

set_screen_off_time(timeout=600)

设置灭屏时间

参数:timeout (int,单位为秒) – 超时时间
set_system_time(new_time=None)

设置系统时间

参数:new_time (str) – 新时间,默认为PC上的时间,格式为: 20151001.170000
set_system_timezone(new_timezone='Asia/Shanghai')

修改系统时区

set_time_12_24(is_24=True)

设置12/24小时格式

参数:is_24 (boolean) – 是否是24小时
set_volume(volume)

设置音量

start_activity(activity_name, action='', type='', data_uri='', extra={}, wait=True)

启动activity

参数:
  • activity_name (string) – Activity名称,如:com.tencent.mobileqq/.activity.SplashActivity
  • action (string) – Action名称
  • type (string) – mime类型
  • data_uri (string) – data uri
  • extra (dict) – 额外参数
  • wait (boolean) – 是否等待启动完成
switch_to_data_connection()

关闭WIFI,启用数据连接

system_version

系统版本

take_screen_shot(save_path)

截屏

参数:save_path (string) – 截屏图片存放在PC上的路径
uninstall_package(pkg_name)

卸载应用

参数:pkg_name (string) – 包名
unlock_keyguard()

解锁屏幕

unregister_screenshot_callback(callback)

注销截图回调函数

参数:callback (function) – 回调函数
wake_screen(wake=True)

唤醒屏幕

class qt4a.device.DeviceProviderManager

基类:qt4a.androiddriver.util.Singleton

设备提供者管理

connect_device(dev_prop)

连接设备

参数:dev_prop (dict) – 设备属性
返回:Device实例
release_all()

释放所有设备

class qt4a.device.LocalDeviceProvider

基类:qt4a.device.IDeviceProvider

本地设备提供者

connect_device(dev_prop)

连接设备

参数:dev_prop (dict) – 设备属性
返回:Device实例
static list()

获取本地可用设备列表

qt4a.qpath Package

QPath中间层

class qt4a.qpath.QPath(qpath_str)

基类:object

parsed_qpath

解析后的内容

qt4a.systemui Package

封装系统窗口控件

class qt4a.systemui.AppNoResponseWindow(app)

基类:qt4a.andrcontrols.Window

应用不响应

class qt4a.systemui.AppResolverPanel(app)

基类:qt4a.andrcontrols.Window

选择响应某个Intent请求的应用

class qt4a.systemui.CrashWindow(app)

基类:qt4a.andrcontrols.Window

Crash窗口

close()

关闭

class qt4a.systemui.PastePopup(app)

基类:qt4a.andrcontrols.Window

弹出式粘贴按钮

class qt4a.systemui.Toast(driver)

基类:qt4a.andrcontrols.Window

封装Toast

message

显示的消息文本

classmethod wait_for_message(app, msg='', timeout=10, interval=0.2)

等待toast出现

classmethod wait_for_message_disappear(app, msg='', timeout=10)

等待Toast消失