題外話
最近更新有點(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ā)棧