事由

最近在做 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


↙↙↙阅读原文可查看相关链接,并与作者交流