UIAutomator定位Android控件的方法


UIAutomator各種控件定位的方法。

1. 背景

使用SDK自帶的NotePad應用,嘗試去獲得在NotesList那個Activity里的Menu Options上面的那個Add note菜單選項。以下是UIAutomatorViewer界面的一個截圖.

但有一個例外的地方是下文的” 通過偽xpath方法定位控件 “章節實例需要使用到的是NoteEditor這個activity里面的Menu options,因為需要演示通過子控件獲得父控件然后得到兄弟控件的功能,UIAutomatorViewer截圖如下。

2. 通過文本信息定位

通過控件的text屬性定位控件應該是最常用的一種方法了,畢竟移動應用的屏幕大小有限,存在text重復的可能性並不大,就算真的有重復,可以添加其他定位方法來縮寫誤差。

2.1 UISelector.text方法

addNote = new UiObject(new UiSelector().text("Add note")); assertEquals(addNote.getText(),"Add note");
該方法通過直接查找當前界面上所有的控件來比較每個控件的text屬性是否如預期值來定位控件,挺好理解的,所以就沒有必要細說了。

2.2. UISelector.textContains方法

addNote = new UiObject(new UiSelector().textContains("Add")); assertEquals(addNote.getText(),"Add note");
此方法跟以上方法類似,但是不需要輸入控件的全部text信息。

2.3 UISelector.textStartsWith方法

addNote = new UiObject(new UiSelector().textStartsWith("Add")); assertEquals(addNote.getText(),"Add note");
顧名思義,通過判斷一個控件的text的開始是否和預期的字串相吻合來獲得控件,其實個人覺得這個方法存在的必要性不強,因為它的功能完全可以用上面的方法或者下面的正則表達式的方法取代。況且既然你提供了textStartsWith方法,為什么你不提供個textEndWith的方法呢!

2.4 UISelector.textMatches方法

addNote = new UiObject(new UiSelector().textMatches("^Add.*")); assertEquals(addNote.getText(),"Add note");
這個方法是通過正則表達式的方式來比較控件的text來定位控件,這里有意思的是用戶使用的正則表達式是有限制的,請看該方法的官方描述:”  Set the search criteria to match the visible text displayed for a widget (for example, the text label to launch an app). The text for the widget must match exactly with the string in your input argument “。第一句我們不用管它,關鍵是第二句,翻譯過來就是” 目標控件的text(的所有內容)必須和我們輸入的正則表達式完全匹配“。什么意思呢? 意思就是你不能像往常的正則表達式那樣通過比較text的部分吻合來獲得控件 。以下面代碼為例子:
addNote = new UiObject(new UiSelector().textMatches("^Add")); assertEquals(addNote.getText(),"Add note");
正常來說這個正則表達式是沒有問題的,它的意思就是想要“獲取以Add開頭的text的控件,至於Add字串口面是什么值,沒有必要去管它”。但是按照我們上面的官方描述,這樣子是不行的,你必須要把正則表達式補充完整以使得正而表達式和控件的text完全進行匹配,至於你用什么通配符或者字串就完全按照正則表達式的語法了。

注意這個限制在UISlector使用所有的正則表達式相關的方法中都有效哦。

3 通過控件的ClassName定位

通過這種方法定位控件存在的一個問題是很容易發生重復,所以一般都是先用這種方法去narrow down目標控件,然后再去添加其他如text判斷等條件進行控件定位。

3.1 UISelector.className方法

addNote = new UiObject(new UiSelector().className("android.widget.TextView").text("Add note")); assertEquals(addNote.getText(),"Add note");
實例中首先通過ClassName找到所有的TextView控件,然后再在這些TextView控件查找text是”Add note“的控件。

3.2 UISelector.classNameMatches方法

addNote = new UiObject(new UiSelector().classNameMatches(".*TextView$")); assertEquals(addNote.getText(),"Add note");
通過正則表達式判斷className是否和預期的一致,  注意正則表達式的限制和章節2.4描述的一致 。

4. 通過偽xpath方法定位

UISelector類提供了一些方法根據控件在界面的XML布局中的層級關系來進行定位,但是UIAutomator又沒有真正的提供類似Appium的findElementWithXpath相關的方法,所以這里我就稱之為偽xpath方法。

這個章節使用到的不再是NotesList那個activity里面的menu options,而是NoteEditor這個activity里面的Menu options,里面不止有一個Menu entry。

4.1 通過UiSelector.fromParent或UiObject.getFromParent方法

這種方法我覺得最有用的情況是測試代碼當前在操作的是同一層級的一組控件中的某一個控件,轉而又需要操作同一層級的另外一個控件的時候。下面的實例就是通過save控件的父控件找到其同一層級的兄弟控件delete。這里分別列出了通過UiObject.getFromParent方法和UiSelector.fromParent方法的實例,事實上他們的功能是一樣的。

UiObject.getFromPatrent方法:

save =  new UiObject(new UiSelector().text("Save")); assertEquals(save.getText(),"Save"); delete = save.getFromParent(new UiSelector().text("Delete")); assertEquals(delete.getText(),"Delete");
UiSelector.fromParent方法(這個例子有點迂回笨拙,但為了演示功能就將就着看吧):
delete = new UiObject(new UiSelector().text("Save").fromParent(new UiSelector().text("Delete"))); assertEquals(delete.getText(),"Delete");

4.2 通過UiSelector.childSelector或UiObject.getChild方法

這種方法是在已知父控件的時候如何快速的查找該父控件下面的子控件。

UiObject.getChild方法:

UiObject parentView = new UiObject(new UiSelector().className("android.view.View")); save = parentView.getChild(new UiSelector().text("Save")); assertEquals(save.getText(),"Save");
UiSelector.childSelector方法:
save = new UiObject(new UiSelector().className("android.view.View").childSelector(new UiSelector().text("Save"))); assertEquals(save.getText(),"Save");

5. 通過控件ID定位

在Android API Level18及其以上的版本增加了一個Android控件的屬性ResourceId, 所以要注意在使用這種方法之前先確保你的目標測試設備和你的UIAutomoator庫jar包使用的都是API Level 18以上的版本 。例如我自己使用的就是本地sdk中版本19的庫: D:\Develops\AndroidSDK\platforms\android-19\uiautomator.jar

5.1 UiSelector.resourceId方法

addNote = new UiObject(new UiSelector().resourceId("android:id/title")); assertEquals(addNote.getText(),"Add note");

5.2 UiSelector.resourceIdMatches方法

addNote = new UiObject(new UiSelector().resourceIdMatches(".+id/title")); assertEquals(addNote.getText(),"Add note");
注意正則表達式的限制和章節2.4描述的一致

6. 通過contentDescription定位

在UiAutomator框架和使用了Uiautomator框架的Appium中,控件的屬性contentDescription一直是強調開發人員需要添加進去的,因為

  • 有些控件使用其他辦法很難或者根本沒有辦法定位
  • 最重要的是給每個控件的contentDescription設計個唯一值讓我們可以非常快速的定位控件,讓我們足夠敏捷!

以下的實例並沒有真正跑過的,因為Notepad應用上面的控件是沒有contentDescription這個屬性的,但是如果我們假設Add note這個控件的contentDescription是“AddNoteMenuDesc”的話,代碼的相應寫法應該就如下了。

6.1 UiSelector.description方法

addNote = new UiObject(new UiSelector().description("AddNoteMenuDesc)); assertEquals(addNote.getText(),"Add note");
</pre><h2>6.2 UiSelector.descriptionContains方法</h2></div><div><pre name="code" class="java"> addNote = new UiObject(new UiSelector().descriptionContains("AddNote")); assertEquals(addNote.getText(),"Add note");

6.3 UiSelector.descriptionStartWith方法

addNote = new UiObject(new UiSelector().descriptionStartsWith("AddNote")); assertEquals(addNote.getText(),"Add note");

6.4 UiSelector.descriptionMatches方法

//addNote = new UiObject(new UiSelector().descriptionMatches("^AddNote.*$")); //assertEquals(addNote.getText(),"Add note");

7.通過其他方法定位

除了以上比較常用的方法外,UIAutomator還支持其他一些方法,比如根據控件屬性是否可點擊可聚焦可長按等來縮小要定位的控件的范圍,具體使用方法不一一列舉,可以查看以下測試驗證代碼。
package majcit.com.UIAutomatorDemo;

import com.android.uiautomator.core.UiDevice; import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; import com.android.uiautomator.testrunner.UiAutomatorTestCase; import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; public class UISelectorFindElementTest extends UiAutomatorTestCase { public void testDemo() throws UiObjectNotFoundException { UiDevice device = getUiDevice(); device.pressHome(); // Start Notepad UiObject appNotes = new UiObject(new UiSelector().text("Notes")); appNotes.click(); //Sleep 3 seconds till the app get ready try { Thread.sleep(3000); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } //Evoke the system menu option device.pressMenu(); UiObject addNote = new UiObject(new UiSelector().text("Add note")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject (new UiSelector().checked(false).clickable(true)); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().className("android.widget.TextView").text("Add note")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().classNameMatches(".*TextView$")); assertEquals(addNote.getText(),"Add note"); //addNote = new UiObject(new UiSelector().description("AddNoteMenuDesc)); //assertEquals(addNote.getText(),"Add note"); //addNote = new UiObject(new UiSelector().descriptionContains("AddNote")); //assertEquals(addNote.getText(),"Add note"); //addNote = new UiObject(new UiSelector().descriptionStartsWith("AddNote")); //assertEquals(addNote.getText(),"Add note"); //addNote = new UiObject(new UiSelector().descriptionMatches("^AddNote.*$")); //assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().focusable(true).text("Add note")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().focused(false).text("Add note")); assertEquals(addNote.getText(),"Add note"); //TBD //addNote = new UiObject(new UiSelector().fromParent(selector)) addNote = new UiObject(new UiSelector().index(0).text("Add note")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().className("android.widget.TextView").enabled(true).instance(0)); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().longClickable(false).text("Add note")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().text("Add note")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().textContains("Add")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().textStartsWith("Add")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().textMatches("Add.*")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().resourceId("android:id/title")); assertEquals(addNote.getText(),"Add note"); addNote = new UiObject(new UiSelector().resourceIdMatches(".+id/title")); assertEquals(addNote.getText(),"Add note"); //Go to the editor activity, need to cancel menu options first device.pressMenu(); //Find out the new added note entry UiScrollable noteList = new UiScrollable( new UiSelector().className("android.widget.ListView")); //UiScrollable noteList = new UiScrollable( new UiSelector().scrollable(true)); UiObject note = null; if(noteList.exists()) { note = noteList.getChildByText(new UiSelector().className("android.widget.TextView"), "Note1", true); //note = noteList.getChildByText(new UiSelector().text("Note1"), "Note1", true); } else { note = new UiObject(new UiSelector().text("Note1")); } assertNotNull(note); //Go to the NoteEditor activity note.click(); device.pressMenu(); UiObject save = null; UiObject delete = null; save = new UiObject(new UiSelector().text("Save")); assertEquals(save.getText(),"Save"); delete = save.getFromParent(new UiSelector().text("Delete")); assertEquals(delete.getText(),"Delete"); delete = new UiObject(new UiSelector().text("Save").fromParent(new UiSelector().text("Delete"))); assertEquals(delete.getText(),"Delete"); save = new UiObject(new UiSelector().className("android.view.View").childSelector(new UiSelector().text("Save"))); assertEquals(save.getText(),"Save"); UiObject parentView = new UiObject(new UiSelector().className("android.view.View")); save = parentView.getChild(new UiSelector().text("Save")); assertEquals(save.getText(),"Save"); } }


        長按二維碼識別關注,您的支持是我們最大的動力。        

        公眾號:測試夢工廠

        QQ一群:300897805

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM