UiAutomator uiautomatorviewer 自动生成 xpath (兼容 android 和 iOS)

bauul · 2017年02月12日 · 最后由 hello 回复于 2019年05月13日 · 3247 次阅读

事由

最近在做 appium 工具升级的事儿,但是新升级的 appium 未提供打包好的工具包,测试同学不能直接获取控件树信息,需要使用命令行起 webdriveragent 或 app-inspector(macaca) 工具来获取。
然后正好最近在论坛上看到别人做了使用 android 端获取控件树的 idea。心想这确实是个好主意,测试同学使用一个工具就好了,不必在两个工具之间来回切换了。

原理

  1. 获取控件树信息

a. 使用 appium 原生方法 getPageSource,结果格式如下:

<?xml version="1.0" encoding="UTF-8"?><AppiumAUT><XCUIElementTypeApplication type="XCUIElementTypeApplication" name="苏宁金融" label="苏宁金融" visible="true" enabled="true" x="0" y="0" width="375" height="667">
<XCUIElementTypeWindow type="XCUIElementTypeWindow" visible="true" enabled="true" x="0" y="0" width="375" height="667">
    <XCUIElementTypeOther type="XCUIElementTypeOther" visible="true" enabled="true" x="0" y="0" width="375" height="667">
    </XCUIElementTypeWindow>
    <XCUIElementTypeWindow type="XCUIElementTypeWindow" visible="false" enabled="true" x="0" y="0" width="375" height="667">
    <XCUIElementTypeWindow type="XCUIElementTypeWindow" visible="true" enabled="true" x="0" y="0" width="375" height="667">
</XCUIElementTypeApplication></AppiumAUT>
  1. 展示

a. 上面的结果需要替换一下才能正常的显示到 uautomatorviewer 中去,如果不替换,则需要修改原码的解析规则(本人选择的替换,这个容易些吧)
替换规则:

if (UiAutomatorViewer.testPlatform.equals(UiAutomatorViewer.AOS)) {
    xmlStr = xmlStr.replaceAll("<\\/android\\.[a-z]+\\.[A-za-z]+", "<\\/node");
    xmlStr = xmlStr.replaceAll("<android\\.[a-z]+\\.[A-za-z]+", "<node");
} else {
    xmlStr = xmlStr.replaceAll("AppiumAUT", "hierarchy");
    if (xmlStr.contains("/UIA")) {
        xmlStr = xmlStr.replaceAll("/UIA.[A-za-z]+", "/node");
        xmlStr = xmlStr.replaceAll("UIA", "node type=\"UIA");
        xmlStr = xmlStr.replaceAll(" name=", "\" name=");
    } else {
        xmlStr = xmlStr.replaceAll("XCUIElement.* type", "node type");
        xmlStr = xmlStr.replaceAll("/XCUIElementType.[A-za-z]+", "/node");
    }
}

b. 生成 xpath

public String getXpathWithRootNode(BasicTreeNode btn) {

    String xpath = "";

    while (btn != null && btn.getAttributesArray() != null) {
        String elementTyle = ((AttributePair)btn.getAttributesArray()[0]).value;
        xpath = elementTyle + "[" + getIndex(btn, btn.getParent()) + "]/" + xpath;
        btn = btn.getParent();
    }

    //System.out.println(xpath);
    return xpath.substring(0, xpath.length()-1);

}

public int getIndex(BasicTreeNode child, BasicTreeNode parent) {
    int index = 1;
    String dType = ((AttributePair)child.getAttributesArray()[0]).value;
    for (BasicTreeNode n : parent.getChildren()) {
        if (n.equals(child)) {
            break;
        }
        if (dType.equals(((AttributePair)n.getAttributesArray()[0]).value)) {
            index++;
        } 

    }

    return index;
}

控件坐标与截图对应(宽和高的比例:screenshot.width / driver.manage().window().getSize().width):

public void setSelectedNode(BasicTreeNode node) {
        this.mSelectedNode = node;
        if ((this.mSelectedNode instanceof UiNode)) {
            UiNode uiNode = (UiNode) this.mSelectedNode;

            if (UiAutomatorViewer.testPlatform.equals(UiAutomatorViewer.AOS)) {
                this.mCurrentDrawingRect = new Rectangle(uiNode.x, uiNode.y,
                        uiNode.width, uiNode.height);
            } else {
                /** 如果是iphone设备,需要根据图片与实际window的比例,计算出控件在截图中的位置*/

                String width = uiNode.getAttribute("width");
                String height = uiNode.getAttribute("height");
                if (uiNode.getAttribute("width").contains(".")) {
                    int indexW = width.indexOf(".");
                    width = width.substring(0, indexW+2);
                }
                if (uiNode.getAttribute("height").contains(".")) {
                    int indexH = height.indexOf(".");
                    height = height.substring(0, indexH+2);
                }

                String x = uiNode.getAttribute("x");
                String y = uiNode.getAttribute("y");
                if (x.contains(".")) {
                    int indexX = x.indexOf(".");
                    x = x.substring(0, indexX+2);
                }
                if (y.contains(".")) {
                    int indexY = y.indexOf(".");
                    y = y.substring(0, indexY+2);
                }
                this.mCurrentDrawingRect = new Rectangle((int)(Float.parseFloat(x) * UiAutomatorViewer.iosScreenZoomInPercentWidth), (int)(Float.parseFloat(y) * UiAutomatorViewer.iosScreenZoomInPercentHeight),
                        (int)(Float.parseFloat(width) * UiAutomatorViewer.iosScreenZoomInPercentWidth), (int)(Float.parseFloat(height) * UiAutomatorViewer.iosScreenZoomInPercentHeight));
            }
        } else {
            this.mCurrentDrawingRect = null;
        }
    }

结果展示:

欢迎指教,感谢 testerhome

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

更新一下,兼容 ios9.3 以下的版本

bauul #27 · 2017年02月21日 Author

@xdf
达峰哥,可以指点一下,这边坐标值和图片像素不匹配的问题吗?感谢

更新一下,坐标值和图片的对应方法,兼容部分 APP 的坐标值存在 13 位小数的问题

直接 windows 电脑连接 ios 设备就可以了吗?

这个当然是不行的了,是在 mac 电脑上跑 uiautomatorviewer 来显示 ios 设备的控件信息啊

不好意思,想问的是,最后是要修改 uiautomatorviewer 的源码还是怎样,没太懂😅

志阳、 回复

嗯嗯,是的,需要修改 uiautomatorviewer 的源码,源码在 google 上可以搜到的。

没有可执行文件或者工程么?

bauul #10 · 2017年03月27日 Author
蓝翔 回复

公司产品,主要这和另一个工具结合使用的,方法是拿到 appium driver 调用 getPageSource 方法

bauul #18 · 2017年12月14日 Author

@Lihuazhang
同一个人点了两个赞?

跟着大神的脚步,开始研究抓取 ios 的 xpath~

bauul #14 · 2018年06月19日 Author
hello 回复

我是弱鸡,😜

bauul 回复

别谦虚了,请收下我的双膝,刚开始玩这个, 有很多不懂的地方。不知是否方便加个 Q

bauul · #12 · 2018年06月19日 Author
仅楼主可见

仅楼主可见,看不到,512433465 方便的话,加一下我 Q

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08

IOS 端查看元素怎么用,楼主能说下嘛

bauul #23 · 2019年02月19日 Author

上面讲了啊,需要修改 uiautomatorviewer 的源码,把控件信息塞到树上,就可以了。至于获取控件信息的方法现在主要是调用 Facebook 的 WDA 的 API,你可以看一下 appium 的源码,里面有

bauul 回复

需要通启动 wda 服务是吗

是的

你好,能否将 uautomatorviewer 有关界面操作手机获取控件属性等信息集成在自己的 SSM 框架中的网页里?不用他自带的页面

bauul #27 · 2019年05月13日 Author
Altndong 回复

可以啊

Altndong 回复

ATX 就是这么干的吧

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册