pyuiantomator实现安卓自动化测试

该工具以Python package的形式存在,可通过pip在测试机(PC)上进行安装。

1
pip install uiautomator

安装完毕后,无需在Android设备上安装任何东西,只要设备通过adb与主机相连,就可以在主机中通过Python操作Android设备的UI控件。

如下是简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from uiautomator import device as d
 
# Turn on screen
d.screen.on()
 
# press back key
d.press.back()
 
# click (x, y) on screen
d.click(x, y)
 
# check unchecked checkbox
checkbox = d(className= 'android.widget.CheckBox', checked= 'false')
checkbox.click()
 
# click button with text 'Next'
d(text= "Clock").click()
button = d(className= 'android.widget.Button', text= 'Next')
button.click()
 
# swipe from (sx, sy) to (ex, ey)
d.swipe(sx, sy, ex, ey)

更详细的使用方法可参考项目文档

通过这种方式,我们就可以极大地简化Python操作UI控件的方式,不用再对照着UI Automator API使用Java编写测试用例,也不用再反复编译jar文件,省去了同一个测试场景需要维护两套代码的麻烦,整个过程也更加Pythonic。

当然,pyuiautomator也并非完美,毕竟它不是Google官方维护的,从我实际使用的经历来看,有时候也会存在一些问题。这里先不展开,后续会单独写一篇博客。不过,总的来说,pyuiautomator还是非常值得使用的,它的确可以大大简化测试工作量。

现在继续本文的主题,我们怎么采用pyuiautomator来进行UI控件操作呢?

其实从上面的示例代码中就可以看到,UI控件的操作分为两步,首先是对目标控件进行定位,然后是对定位的控件执行动作。

定位控件

在UI中,每个控件都有许多属性,例如classtextindex等等。我们要想对某一个控件进行操作,必然需要先对目标控件进行选择。

在上面的pyuiautomator用法示例中,已经包含了控件选择的代码:

1
2
checkbox = d(className= 'android.widget.CheckBox', checked= 'false')
button = d(className= 'android.widget.Button', text= 'Next')

在这两行代码中,分别实现了对checkbox和button的选择。基本原则就是,通过指定的控件属性,可以唯一确定目标控件。

那么,我们怎么知道目标控件有哪些属性,以及对应的属性值是什么呢?

Google官方提供了两个工具,hierarchyvieweruiautomatorviewer,这两个工具都位于<android-sdk>/tools/目录下。关于这两个工具的区别及其各自的特点,本文不进行详细介绍,我们当前只需要知道,在查看控件属性方面,这两个工具实现的功能完全相同,界面也完全相同,我们任选其一即可。

{: .center}
uiautomatorviewer

通过这个工具,我们可以查看到当前设备屏幕中的UI元素信息:

  • 当前Android设备屏幕中显示的UI元素的详细属性信息
  • 当前Android设备屏幕中所有UI元素的层级关系结构

需要强调的是,工具每执行一次dump,获取到的UI信息仅限于当前屏幕中前端(foreground)显示的内容。

获得UI元素的信息后,由于UI控件是以树形结构进行存储,而且每个控件都存在index属性值,因此,理论上讲,通过层级结构和index属性就能唯一指定任意UI控件。

然而,这并不是最佳实践。因为通常情况下,UI布局的树形结构层级较多,通过层级关系进行指定时会造成书写极为复杂,而且从代码中很难一眼看出指定的是哪个控件。不信?看下这个例子就能体会了。如下代码对应的就是上图中红色方框的控件,可以看出,要是寻找每个控件都要从顶级节点开始,要将根节点到目标控件的路径找出来,这也是一个很大的工作量,而且很容易出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
d(className='android.widget.FrameLayout')
.child(className='android.widget.LinearLayout')
.child(className='android.widget.FrameLayout')
.child(className='android.widget.FrameLayout')
.child(className='android.widget.FrameLayout')
.child(className='android.widget.FrameLayout')
.child(className='android.widget.FrameLayout')
.child(className='android.view.View')
.child(className='android.widget.FrameLayout')
.child(className='android.widget.FrameLayout')
.child(className='android.view.View')
.child(className='android.widget.FrameLayout')
.child(className='android.widget.TextView')

在实际应用中,我们更多地是采用控件的属性信息来定位控件,一般情况下,采用属性值text就能唯一确定目标控件了。例如同样是对上图中的红色方框进行定位,如下代码就足够了。

1
d(text='UC头条有新消息,点击刷新')

不过,经常会出现目标控件的text属性值为空的情况,这个时候我们一般就会采用class属性和部分层级关系组合的方式。同样是上图中的红色方框,我们也可以使用如下方式进行定位。

1
d(className='android.widget.FrameLayout').child(className='android.widget.TextView')

可以看出,同一个控件,我们可以采用多种方式进行定位。具体选择何种定位方式,可以参考如下准则:

  • 定位方式应保证定位准确
  • 定位方式应尽可能简洁
  • 定位方式应尽可能稳定,即使屏幕界面出现变化,定位方式也不会失效

这里说到了定位方式的准确性,那要如何进行验证呢?技巧是,采用.count.info属性值即可。

1
2
3
4
>>> d(text= 'UC头条有新消息,点击刷新').count
1
>>> d(text= 'UC头条有新消息,点击刷新').info
{ u'contentDescription': None, u'checked': False, u'clickable': True, u'scrollable': False, u'text': u'UC\u5934\u6761\u6709\u65b0\u6d88\u606f\uff0c\u70b9\u51fb\u5237\u65b0', u'packageName': u'com.UCMobile.projectscn1098RHEAD', u'selected': False, u'enabled': True, u'bounds': { u'top': 1064, u'left': 42, u'right': 1038, u'bottom': 1136}, u'className': u'android.widget.TextView', u'focusable': False, u'focused': False, u'checkable': False, u'resourceName': None, u'longClickable': False, u'visibleBounds': { u'top': 1064, u'left': 42, u'right': 1038, u'bottom': 1136}, u'childCount': 0}

.count属性值为1,.info属性值的内容与目标控件的属性值一致时,就可以确定我们采用的定位方式是准确的。

控件操作

定位到具体的控件后,操作就比较容易了。

pyuiautomator中,对UI Automator的UI操作动作进行了封装,常用的操作动作有:

  • .click()
  • .long_click()
  • .swipe
  • .drag

更多的操作可根据我们测试场景的实际需求,查询pyuiautomator文档找到合适的方法。

参考文档

猜你喜欢

转载自angelguo.iteye.com/blog/2315083
今日推荐