4.1 查看工具
Web 自动化开发的时候,要找到元素,我们是通过浏览器的开发者工具栏来查看元素的特性,根据这些特性,比如:id、classname、tagname,或者 css 和 xpath 选择到元素。那么安卓的移动 app 我们怎么查看元素呢?安卓查看页面元素,最常用的就是 Android SDK 中的 uiautomateviewer,我的目录在 .\tools\tools\bin 下面。
image.png双击打开,然后手机打开要查看app的界面,然后点击
image.png它就会到当前连接手机的设备上去获取,当前界面的信息。
image.png
然后我们就可以去查看了。uiautomatorviewer 查看的是手机当前界面的元素,我们可以点击手机的界面,进入到不同的界面,查看我们要查看界面的元素。进入到一个新界面的时候,需要再次点击按钮。和 web 不同,它不能自动刷新,需要我们手动点击重新获取当前界面。我们要查找某个元素,可以把鼠标移动到某个元素上,某个元素就有虚线红框子,同时右边会高亮当前的元素,如果确定了要查看的元素,就点击一下该元素,虚线变成实线,就是确定了查看这个元素的信息。同时,右边下面就显示出该元素的具体的属性信息,这时候再异动鼠标就不会根据经过的元素而变化了。它认为已经确定了要看的元素了。如果我们想再次查看别的元素,就需要随便点击一下界面某个地方,实线又变成虚线。就可以再次移动选择别的元素,确定下来就点击它。
可能还有人看到了整个按钮。
image.png
它也可以获取当前界面的信息,它获取的相对来说是一个精简的目录数,它把中间一些没有具体信息的层次会去掉。比如:
image.png
它就比刚才精简了好多,去掉一些没有包含实际信息的层次的信息,通常还是用前面方法的多。
下面介绍一些元素的属性:
- index: 就是该元素是其父元素的第几个子节点(从0开始)。
- text: 就是如果该元素对应的界面上有文本内容,就是其文本内容的字符串值。显然这是我们将来非常关心的属性,因为可能要分析内容,判断是否与预期相等。
- resource-id: 是该元素的资源 id,一般来说唯一的决定了一个元素,有点像 web 的 id。
- class: 就是该元素的类型,类似 html 的 tag 名
- package: 就是所在 app 的包名。
- content-desc: 这个属性用来描述该元素的用途,特性等。
- bounds: 就是这个元素在界面上的位置,左下角和右小角的坐标。这个属性也很重要,以后如果我们黔驴技穷,是在选择不到元素的时候,就根据这个属性直接去根据坐标操作元素了。
对于 bounds 看下面截图,一目了然
注意:是像素
4.2 选择元素的代码
选择元素的代码也和 Selenium 本相同,可以通过:
- find_element_by_XXX 符合条件的第一个元素,找不到这样的元素,会抛出异常。
- find_elements_by_XXX 符合条件的所有元素的列表,找不到会返回空列表。
- 通过 WebDriver 对象调用这样的方法。查找范围是整个界面树形结构。
- 通过 WebElement 对象调用这样的方法,查找范围是该元素的子节点。
4.3 选择元素的方式
4.3.1 ID
我们在学习 Web 自动化的时候就说过,能根据 id 最好根据 id,因为是唯一的,效率高、表达式又简单,在安卓应用自动化的时候,同样可以根据 id 查找,但是这个 id 其实安卓应用元素的 resource id 属性,比如上个章节代码中的:
# 根据id找到元素,并点击
driver.find_element_by_id("io.manong.developerdaily:id/tab_bar_plus").click()
image.png
注意这里io.manong.developerdaily:id/tab_bar_plus
这里的 id 可以写全,也可以 :id/ 后面的内容就是 driver.find_element_by_id("tab_bar_plus").click() 。光有 id 去选择元素是远远不够的,有很多元素没有 id,也有很多 id 根据规范是唯一的,但是它不唯一,比如我们看一下开发者头条消息的 id 。
我们再看发现的 id 。
image.png是不是也叫 tv_tab_title ,点击我的它的 id 也是一样的。所以下面四个id 都是一样的。由于开发人员的疏忽。如果有这种错误的话,我们就不能通过 id 定位了。就要通过一些其它的方式。有的人会问我们怎么知道 id 是不是唯一的,以前做 web 自动化的时候我们可以按 F12 然后 Ctrl + F 一系列操作可以验证。我们在这里面要怎么验证呢?很遗憾它这里面就没有查询框了。一种方式就是根据名字来判断,通常 id 跟它的名字如果形成了一种直接的对应关系,通常就是唯一的,可以根据一种直觉,比如这个 id 叫 tv_tab_title 。tab 的 titile 名,但是你想想下面几个元素全是 tab 。这个时候就要小心了。这个只是一种猜测法还有一种唯一确认的方法,就是把整个界面对应的 xml 文档全部导出来查看一下,怎么导出呢,看截图
选择保存路径。
image.png
然后去路径里查看,会发现有两个文件。
image.png
第一个 dump 是它所对应的图片,整个不关心,我们关心的是第二个我们可以用 Notepad++ 打开。之后把要查找的 id 复制按 Ctrl + F 点击计数。
这里显示4次匹配,怀疑它不是唯一的。可以通过这种方式来确定是不是唯一,有些麻烦。
4.3.2 Class Name
class 属性决定了界面元素的类型。 通常 class 就是类似 web 里面 tagname, 所以通常不是唯一的。所以大部分情况,我们根据 classname 是要选择多个而不是一个。如果你确定,我们要查找的是某种类型的界面元素,而且这种类型的界面元素在当前界面中只有一个,就可以根据它来查找写法是:
driver.find_element_by_class_name('XXX')
大家看一个例子:开发者头条里面的 底部的大加号 android.widget.ImageButton ,这个在当前界面上就是一个唯一的,我们可以根据它查找。
代码是:
driver.find_element_by_class_name("android.widget.ImageButton").click()
有的时候,我们需要根据 classname 查找所有匹配的元素,这时候可以用:
driver.find_elements_by_class_name("XXX")
比如我们要获取界面上所有的 TextView 里面的文本内容,就可以写出如下代码:
tvs = driver.find_elements_by_class_name("android.widget.TextView")
for tv in tvs:
print(tv.text)
4.3.3 content-desc
content-desc 属性时用来描述该元素的作用的。 要查询的界面元素的 content-desc 属性在当前界面中唯一我们可以通过它来定位。
image.png它的 content-desc 有个 unique name 的,唯一的姓名,如果它有值,并且这个值它在当前这个 content-desc 属性里面它是唯一的,就可以根据这个值来选择,根据 content-desc 来选择的写法如下。
driver.find_element_by_accessibility_id('xxx')
不幸的是,基本上开发人员都不喜欢填写它。
1. 到如下网址下载 多多计算器
http://android.myapp.com/myapp/detail.htm?apkName=com.ibox.calculators&ADTAG=mobile
2. 用 aapt.exe 命令查看 apk 包的 appPackage 信息和主 Activity 信息
3. 用 UIAutomator Viewer 查看应用界面元素信息
4. 编写 python 程序,完成一个 计算 3+9 ,结果 再乘以 5 的自动化功能. 最后判断计算结果是否为 60,如果是,测试通过;否则测试不通过
示例代码:
from appium import webdriver
import time,traceback
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '7'
desired_caps['deviceName'] = 'test'
desired_caps['app'] = r'E:\tools\com.ibox.calculators_3.0.5_1305.apk'
desired_caps['appPackage'] = 'com.ibox.calculators'
desired_caps['appActivity'] = 'com.ibox.calculators.SplashActivity'
#desired_caps['unicodeKeyboard'] = True
desired_caps['noReset'] = True
desired_caps['newCommandTimeout'] = 6000
#启动Remote RPC
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
driver.implicitly_wait(10)
try:
# -----------------
num3 = driver.find_element_by_id("com.ibox.calculators:id/digit3")
num5 = driver.find_element_by_id("com.ibox.calculators:id/digit5")
num9 = driver.find_element_by_id("com.ibox.calculators:id/digit9")
plus = driver.find_element_by_id("com.ibox.calculators:id/plus")
mul = driver.find_element_by_id("com.ibox.calculators:id/mul")
equal = driver.find_element_by_id("com.ibox.calculators:id/equal")
num3.click()
plus.click()
num9.click()
equal.click()
mul.click()
num5.click()
equal.click()
# 检查结果,需要我们去找结果对应的界面元素,发现是下面这个TextView
# 研究发现没有特点,大家想想我们该怎么办
# 可以看看父节点有没有唯一标识,发现 父节点是有id的,
# 就可以怎么样?
# 先查找父节点,
# 再根据父节点元素 调用 find element 就是在父节点的范围内 查找
retLayout = driver.find_element_by_id('com.ibox.calculators:id/cv')
retTvs = retLayout.find_elements_by_class_name('android.widget.TextView')
retStr = retTvs[1].text
print(retStr)
if retStr == '60':
print('pass')
else:
print('fail')
# -----------------
except:
print(traceback.format_exc())
input('**** Press to quit..')
driver.quit()
网友评论