1. DOM結構
先來看幾個名詞和解釋:
- dom: Document Object Model 文檔對象模型
- dom應用: 最早應用於html和js的交互。界面的結構化描述, 常見的格式為html、xml。核心元素為節點和屬性
- xpath: xml路徑語言,用於xml 中的節點定位,XPath 可在 xml 文檔中對元素和屬性進行遍歷
如下我們再來看一個App的dom:

控件的基礎知識和selenium一樣,appium為移動端抽象出了一個控件模型,稱為dom結構;會把所有的控件都理解為xml文件,在xml文件里,每個控件都有自己的類型和屬性;
既然有了類型和屬性,自然就可以根據這些來定位元素,又因為整個模型是xml,也就同樣可以通過xpath的方法來定位各個控件的信息了,是不是似曾相識?在Web端自動化時候也介紹過相關元素定位方式,具體可在文章末尾往期回顧第一條點擊查看。
2. 控件定位
UI自動化測試的步驟三要素是:
- 定位
- 交互
- 斷言
那么第一步便是要對元素進行定位,下面就來看看移動端如何進行元素定位
2.1 控件屬性
通過uiautomatorviewer對雪球App首頁的解析得到如下圖結果:

通過解析結果我們可以看到元素的屬性和類型有:
- node
- attribute
- clickable
- content-desc
- resource-id
- text
- bounds
IOS和Android在控件屬性和上稍微有些不同(這里先說個概括,后續單獨出IOS的文章加以說明,歡迎關注):
- dom屬性和節點結構類似
- 名字和屬性的命名不同
2.2 定位方式
Appium 支持 WebDriver 定位策略的子集:
2.21 通過 “class” 查找 (例如, UI 組件的類型)-一般不推薦
這種就是通過判斷控件類型來查找,例如TextView、ImageView等

在實際工作中,這種定位方式幾乎不用,因為一個頁面中可能會有很多的TextView、ImageView等;
appiumdriver.findElementByClassName("android.widget.TextView");
2.22 通過 “xpath” 查找 (例如, 一個元素的路徑以抽象的方式去表達,具有一定的約束)-重要
如上所述,xpath是不僅可以在移動端進行元素定位,並且是我們最常用的定位方式之一,在web端自動化我們會首推CSS定位,而在移動端定位我們會首推xpath定位,良好的xpath定位語法會給我們定位帶來准確度和便利度,對速度的影響也完全會在我們的接受范圍以內
如下dom結構中,一個界面上有多同類型控件,這些控件有相同的id或屬性,不具備唯一性,所以無法直接進行指定控件的定位操作,這個時候就該xpath大顯身手了

上圖可以看到,所有勾選控件的結構是一樣的,相對位置是固定的,而勾選控件相對它們的"哥哥"節點的TextView是不同的,這樣就可以先定位至"哥哥"節點,在根據相對位置,定位到指定的控件節點
在xpath中提供了多種軸方法,其中following-sibling可實現此功能

如我們要定位"畫好一個封閉的圓"后面跟着的第二個RelativeLayout,具體寫法如下:
//下面兩種寫法均可實現
By.xpath("((//*[@text='畫好一個封閉的圓'])[2]/following-sibling::android.widget.RelativeLayout)[2]")
By.xpath("((//*[@text='畫好一個封閉的圓'])[2]/following-sibling::*[@class='android.widget.RelativeLayout'])[2]")
很多控件都是有text屬性的,但是appium是不支持直接對text進行定位的,而在實際工作中,我們經常會拿text進行定位,這就要歸功於xpath了,通過對xpath語法的封裝,我們就可以自定義一個根據text定位元素的方法來:

public By ByText(String text){
return By.xpath("//*[@text='"+ text + "']");
}
appiumdriver.findElement(ByText("關注"));
另外,需要定位Toast彈框時,有且僅有通過xpath的方式來實現:
有時候我們進行某個操作后會彈出消息提示,例如點擊某個按鈕或下拉刷新后可能會出現類似"刷新成功"的提示語,然后幾秒后消失;

彈出的消息很可能是Android系統自帶的Toast,Toast在彈出的時候會在當前界面出現節點android.widget.Toast,隨着消息的消失而消失;這個時候我們如果需要定位這個彈出消息,對其進行測試的話,就可以使用定位xpath方式了。
System.out.println( appiumdriver.findElementByXPath("//*[@class='android.widget.Toast']").getText());
結果:

2.23 通過id定位(每個元素原則上都有自己的唯一id值)-重要
學過web自動化的同學知道,在HTML中元素是有自己的id的,在移動端,元素依然有自己的id值,只不過名字叫做resource-id,如下:

注: 我們看到id的值很長,其實實際使用只需要取斜杠/后面的部分就可以了,如下:
By.id("statusTitle")
2.24 通過accessibilityId定位(實則就是android的content-desc)-偶爾用到
在移動端自動化中有個特殊的定位方式就是根據accessibilityId定位,在dom中表現就是屬性content-desc的值,如果Android中的content-desc中寫入了值,便可以通過其進行定位:

這里比較尷尬。。。由於研發經常偷懶不寫,找了半天也沒能找到例子,大家知道用法就好~另外要注意的是如果要寫成"By.xxx"的形式,需要使用MobileBy
MobileBy.AccessibilityId("AccessibilityId");
appiumdriver.findElementByAccessibilityId("AccessibilityId");
2.25 通過android uiautomator定位(相當於使用 UiAutomator Api 去遞歸地搜索元素(Android 專屬))-高級用法
有時候我們需要對界面進行一定的操作方式后才能找到我們想要的元素,比如滑動列表進行查找等,這個時候就可以借助於android uiautomator了這里利用模擬器中的API Demo做演示,進入APIDemo中Views,然后滑屏尋找“Popup Menu”進行點擊操作

可以利用Android的UIAutomator進行滑屏操作,這時候需要使用AndroidDriver,另外定位元素可以使用UiScrollable:

在官網的uiautomator UiSelector中有用ruby寫的實例,不過定位方式是一致的,可以直接借鑒至java代碼中

這里大概定位的方法就是,先用new UiSelector().scrollable(true).instance(0)判斷是否可以滑動,找到ListView,然后用scrollIntoView(new UiSelector().text("WebView").instance(0)滑動找到對應定位屬性的元素。
driver.findElementByXPath("//*[@text='Views']").click();
((AndroidDriver<MobileElement>)driver).
findElementByAndroidUIAutomator
("new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text(\"Popup Menu\").instance(0))")
.click();
在實際運行中,AndroidUIAutomator偶爾有定位失敗的情況,可能在定位元素是位置會產生一點偏差,這里稍加改造避免這種偶發性失敗;
改造方法:滑屏尋找元素時會先滑屏至待查元素的附近,這時元素已處於頁面可見范圍內,對元素操作可以重新定位操作,例如點擊操作可以利用Xpath的方法重新定位后再click().
By departmentName = MobileBy.AndroidUIAutomator(
"new UiScrollable(new UiSelector().scrollable(true).instance(0))." +
"scrollIntoView(new UiSelector().text(\""+ departName +"\").instance(0))");
find(departmentName);
// click(departmentName); 原來直接操作滑動查找的元素結果
click(ByText(departName));//現在利用xpath重新定位確認后再操作,成功率大大提升
運行效果演示:
3. 定位邏輯的區別
在之前的一篇文章中我們介紹過appium底層的使用了各種引擎,可在文章末尾往期回顧第一條點擊查看。
先簡單看如下圖:

我們現在用的最新的版本優先支持的就是uiautomator2,如果你使用的是相對較前的版本,可能支持的是uiautomator,那么這兩個引擎對於以上介紹的定位有什么影響呢?來看源碼:
我們現在用的最新的版本優先支持的就是uiautomator2,如果你使用的是相對較前的版本,可能支持的是uiautomator,那么這兩個引擎對於以上介紹的定位有什么影響呢?來看源碼:
- Uiautomator源碼

以id定位為例,在Uiautomator的源碼可見其對id定位要更為寬泛,當我們使用By.id的時候,會同時去匹配resourceId、accessibility id、id
- Uiautomator2源碼

在Uiautomator2中,將id的定位進行了細分,對應不同的id進行判斷后再操作,因此在使用Uiautomator2的時候我們的寫法要更為嚴謹
(文章來源於霍格沃茲測試學院)