初探UiAutomator2.0中使用Xpath定位元素

題外話

最近更新有點(diǎn)延遲哈,那是因?yàn)榻恿艘粋€(gè)外包項(xiàng)目的活(就是移動(dòng)端自動(dòng)化相關(guān)的),忙的“外黑里焦”的,好在應(yīng)該2個(gè)星期的努力已經(jīng)進(jìn)入尾期,項(xiàng)目整體功能都已經(jīng)實(shí)現(xiàn),后面有空給大家分享,今天的主題是講一下在使用過(guò)程中遇到的一個(gè)問(wèn)題,如何在UiAutomator2.0中使用Xpath定位元素?

背景

現(xiàn)在的app在打包成apk的時(shí)候都是有加固處理的,各種混淆加固,所以已經(jīng)破壞了或擾亂了原本的代碼變量命名形式,這就給我們要基于界面來(lái)做自動(dòng)化測(cè)試帶來(lái)了災(zāi)難性的阻礙,因?yàn)槟切┗煜^(guò)的id是不固定的,下一次再出個(gè)新版本,這一切都變了,所以這就沒(méi)辦法用id來(lái)定位混淆過(guò)的app元素,那還有什么好的方法嗎?還記得Web自動(dòng)化測(cè)試中神乎其技的xpath嗎?不管什么元素都可以用它定位出來(lái),所以我就想在UiAutomator2.0中也使用它來(lái)定位混淆的app元素,這要如何操作?UiAutomator2.0的API中并沒(méi)有給出xpath這種方式,那我們只能自己去寫(xiě)一個(gè)了。

思路

參考UI Automator Viewer中抓取到的結(jié)構(gòu)層次,不能用resource-id,又要體現(xiàn)出層次關(guān)系,那就只能是class屬性了,這里的class可以對(duì)應(yīng)web xpath中的標(biāo)簽,使用業(yè)界統(tǒng)一的斜杠/來(lái)保持層次,那么最原始狀態(tài)下的xpath大概就是這個(gè)樣子了:

android.view.ViewGroup/android.widget.ImageView
再加上下標(biāo)
android.view.ViewGroup[2]/android.widget.ImageView[0]


xpath的格式定義出來(lái)了之后,我們就開(kāi)始一層一層去遍歷,很簡(jiǎn)單通過(guò)斜杠/來(lái)分隔出一個(gè)class數(shù)組,然后依次去查找這些class對(duì)應(yīng)的元素,通過(guò)父子關(guān)系拼接起來(lái),直到最后一個(gè)class,存在就返回對(duì)應(yīng)的對(duì)象,不存在就返回null。
由于時(shí)間關(guān)系,這一次就是初探,只實(shí)現(xiàn)了這種絕對(duì)路徑(/)下的定位,其實(shí)要想完整完成這個(gè)功能,還需要支持相對(duì)路徑(//)的定位,以及各種屬性的組合定位,其實(shí)基于這個(gè)版本上面改改也不遠(yuǎn)了,這就留給有興趣的童鞋去完成吧。

實(shí)現(xiàn)

1、首先要實(shí)現(xiàn)根據(jù)class或其他屬性去找到某個(gè)元素的子元素,我這里實(shí)現(xiàn)了支持傳入各種參數(shù),代碼如下:

public static UiObject2 getChild(Object root, Map<String,String> params) {
        if (params == null || !params.containsKey("class")) {
            log.e("[Error]參數(shù)錯(cuò)誤: 為空或未包含[class]key");
            return null;
        }
        String clazz = params.get("class");
        String className = clazz;
        int index = 0;
        if (clazz.endsWith("]") && clazz.contains("[")) { //有下標(biāo)
            className = clazz.substring(0, clazz.lastIndexOf("["));
            String num = clazz.substring(clazz.lastIndexOf("[") + 1, clazz.lastIndexOf("]"));
            index = num != null && !"".equals(num) ? Integer.parseInt(num) : index;
        }
        List<UiObject2> childList = null;
        if (root instanceof UiObject2) {
            childList = ((UiObject2) root).getChildren();
        } else {
            childList = hasObjects(By.clazz(className)) ? mDevice.findObjects(By.clazz(className)) : null;
        }
        List<UiObject2> tempList = new ArrayList<UiObject2>();
        if (childList != null && !childList.isEmpty()) {
            for (UiObject2 child : childList) {
                boolean isMatch = child.getClassName().equals(className);
                if (params.containsKey("pkg")) {
                    isMatch = isMatch && child.getApplicationPackage().equals(params.get("pkg"));
                }

                if (params.containsKey("text")) {
                    isMatch = isMatch && child.getText().equals(params.get("text"));
                }

                if (params.containsKey("desc")) {
                    isMatch = isMatch && child.getContentDescription().equals(params.get("desc"));
                }

                if (isMatch) {
                    tempList.add(child);
                }
            }
        }

        if(tempList.isEmpty()) {
            return null;
        }

        if (index >= tempList.size()) {
            log.e(String.format("[Error]查找class[%s] 下標(biāo)[%d]越界[%d]", clazz, index, tempList.size()));
            return null;
        }
        return tempList.get(index);
    }

2、再寫(xiě)一個(gè)通過(guò)class獲取子元素的簡(jiǎn)單實(shí)現(xiàn),因?yàn)檫@種方式用的多:

  public static UiObject2 getChild(Object root, String clazz) {
        Map<String,String> params = new HashMap<String,String>();
        params.put("class", clazz);
        return getChild(root, params);
    }

3、加入解析xpath表達(dá)式的部分,將解析和查找整個(gè)過(guò)程連起來(lái):

public static UiObject2 findObjectByXpath(UiObject2 root, String xpath) {
        if (xpath == null && "".equals(xpath)) {
            log.e("[Error]xpath expression[" + xpath + "] is invalid");
            return null;
        }
        String[] xpaths = null;
        if (xpath.contains("/")) {
            xpaths = xpath.split("/");
        } else {
            xpaths = new String[]{xpath};
        }
        UiObject2 preNode = root;
        for (String path : xpaths) {
            preNode = getChild(preNode, path);
            if (preNode == null) {
                //log.e(String.format("按xpath[%s]查找元素失敗, 未找到class[%s]對(duì)應(yīng)的節(jié)點(diǎn)", xpath, path));
                break;
            }
        }

        return preNode;
    }

4、使用演示:

String commentXpath = "android.widget.LinearLayout/android.widget.LinearLayout/android.widget.TextView[0]";
UiObject2 commentView = findObjectByXpath(root, commentXpath);

總結(jié)

既然是初探就先寫(xiě)這么多吧,給個(gè)實(shí)現(xiàn)思路,如果把整個(gè)功能都完成,可以考慮開(kāi)源到github上方便千千萬(wàn)萬(wàn)其他U2自動(dòng)化的童鞋,后面有時(shí)間可以考慮一下,我更希望有童鞋主動(dòng)來(lái)實(shí)現(xiàn)(哈哈,不做測(cè)試了,沒(méi)以前那么大的熱情和精力來(lái)搞這個(gè)了)。

原文來(lái)自下方公眾號(hào),轉(zhuǎn)載請(qǐng)聯(lián)系作者,并務(wù)必保留出處。
想第一時(shí)間看到更多原創(chuàng)技術(shù)好文和資料,請(qǐng)關(guān)注公眾號(hào):測(cè)試開(kāi)發(fā)棧

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,694評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評(píng)論 25 708
  • 劉蘭蘭 18852657993 年少時(shí),盼著時(shí)光走的快些,那樣就可以省去許多青澀的片斷,如今則希望時(shí)光止步,...
    高小美_6a97閱讀 249評(píng)論 0 0
  • 不忘初心,方得始終。我是一個(gè)軟件技術(shù)從業(yè)者,加入過(guò)不少技術(shù)社群。加入寫(xiě)作社群,這是第一次,值得記上一筆。 加入00...
    Ashton閱讀 226評(píng)論 1 2
  • 1 2016年7月24日 第一次見(jiàn)面 我去見(jiàn)的他(捂臉)我真的厚顏無(wú)恥 哎呀 那會(huì)應(yīng)該特別期待 不然頂著大太陽(yáng) ...
    銀針一朵閱讀 392評(píng)論 0 0

友情鏈接更多精彩內(nèi)容