测试基础 通过 python 调用 adb 命令实现用元素名称、id、class 定位元素

xuxu · 2014年06月23日 · 最后由 securitytest 回复于 2017年09月26日 · 4522 次阅读
本帖已被设为精华帖!

在 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(2)

    e2 = element.findElementByName(u"手机充值")
    evevt.touch(e2[0], e2[1])

这是一个简单的思路,使用的前提是需要开启 view server,否则无法获取到 uidump.xml 这个文件。另外程序运行的时间会有点慢,可以有优化的方法。
另外如果属性值相同,使用 elements,返回的是含坐标元组的列表,如何使用,这里就不讲了。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 13 条回复 时间 点赞

楼主还真有想法,而且敢做出来,赞三十二个。

这是直接上源码的节奏啊,自己定义自己用,赞

学习!!

╭( ̄▽ ̄)╯╧═╧刚刚没看清楚内容,以为是 appium 了。

@xuxu 这种分享的精神值得称赞!!!

好帖!不给精华呢?

赞!学习了

好帖,必须顶·

请问这个可以怎么优化呢?

xuxu #11 · 2015年04月02日 Author

@lihuazhang 好老的贴了。。

楼主在吗?使用这个方式出现了一个问题,App 里注册的一个辅助功能失效了,没法拦截系统通知和事件了,必须重启一下辅助功能,这个问题你知道怎么解决吗?

陈恒捷 hybrid app 测试遇到的问题解决过程小结 中提及了此贴 03月22日 09:15
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册