在Android自动化测试过程中,使用monkeyrunner可以通过坐标点击某个元素,但缺点是受屏幕分辨率影响,需要随时更改坐标位置。最近有使用了下Appium这个工具,优点是可以通过元素名称、id、class等属性定位,但是需要搭建稍微有点复杂的环境,然后我就想能不能不依赖任何环境,使用纯python实现通过这几个属性定位,只需要直接执行python脚本就ok?接下来开始着手实现
首先需要Android版本高于4.0,使用这个命令“adb shell uiautomator dump”命令,执行“adb shell uiautomator dump /data/local/tmp/uidump.xml”,然后将该xml文件pull到本地,从里面可以看到手机上当前页面的布局,在note节点下可以找到这些属性:text,resource-id,class,bounds,知道这些内容后就可以使用python对该xml文件解析获取到对应的属性,取出bounds的值,计算出对应元素区域的中心坐标,接着使用adb shell input tap 命令就可以点击该坐标,如果有相同的属性值,那就需要得到一个坐标点的列表,以应用“1号店“为例,在桌面上通过应用名称“1号店”,点击进入应用,然后点击“手机充值”,截图如下:
用python简单实现:
#coding=utf-8 import tempfile import os import re import time import xml.etree.cElementTree as ET class Element(object): """ 通过元素定位,需要Android 4.0以上 """ def __init__(self): """ 初始化,获取系统临时文件存储目录,定义匹配数字模式 """ self.tempFile = tempfile.gettempdir() self.pattern = re.compile(r"\d+") def __uidump(self): """ 获取当前Activity控件树 """ os.popen("adb shell uiautomator dump /data/local/tmp/uidump.xml") os.popen("adb pull /data/local/tmp/uidump.xml " + self.tempFile) def __element(self, attrib, name): """ 同属性单个元素,返回单个坐标元组 """ self.__uidump() tree = ET.ElementTree(file=self.tempFile + "\\uidump.xml") treeIter = tree.iter(tag="node") for elem in treeIter: if elem.attrib[attrib] == name: bounds = elem.attrib["bounds"] coord = self.pattern.findall(bounds) Xpoint = (int(coord[2]) - int(coord[0])) / 2.0 + int(coord[0]) Ypoint = (int(coord[3]) - int(coord[1])) / 2.0 + int(coord[1]) return Xpoint, Ypoint def __elements(self, attrib, name): """ 同属性多个元素,返回坐标元组列表 """ list = [] self.__uidump() tree = ET.ElementTree(file=self.tempFile + "\\uidump.xml") treeIter = tree.iter(tag="node") for elem in treeIter: if elem.attrib[attrib] == name: bounds = elem.attrib["bounds"] coord = self.pattern.findall(bounds) Xpoint = (int(coord[2]) - int(coord[0])) / 2.0 + int(coord[0]) Ypoint = (int(coord[3]) - int(coord[1])) / 2.0 + int(coord[1]) list.append((Xpoint, Ypoint)) return list def findElementByName(self, name): """ 通过元素名称定位 usage: findElementByName(u"设置") """ return self.__element("text", name) def findElementsByName(self, name): return self.__elements("text", name) def findElementByClass(self, className): """ 通过元素类名定位 usage: findElementByClass("android.widget.TextView") """ return self.__element("class", className) def findElementsByClass(self, className): return self.__elements("class", className) def findElementById(self, id): """ 通过元素的resource-id定位 usage: findElementsById("com.android.deskclock:id/imageview") """ return self.__element("resource-id",id) def findElementsById(self, id): return self.__elements("resource-id",id) class Event(object): def __init__(self): os.popen("adb wait-for-device ") def touch(self, dx, dy): """ 触摸事件 usage: touch(500, 500) """ os.popen("adb shell input tap " + str(dx) + " " + str(dy)) time.sleep(0.5) def test(): element = Element() evevt = Event() e1 = element.findElementByName(u"1号店") evevt.touch(e1[0], e1[1]) time.sleep(1) e2 = element.findElementByName(u"手机充值") evevt.touch(e2[0], e2[1])