UI Automator 是 UI 測試架構,適用於系統和已安裝應用程式的跨應用程式功能 UI 測試。UI Automator API 可讓您與裝置上可見的元素互動,無論哪個 Activity
處於焦點,都能夠執行操作,例如在測試裝置上開啟「設定」選單或應用程式啟動器。測試可以使用方便的描述詞彙 (例如該元件中顯示的文字或其內容說明) 來查詢 UI 元件。
UI Automator 測試架構是一種以檢測為基礎的 API,可與 AndroidJUnitRunner
測試執行器搭配使用。這類測試程式碼不會依賴目標應用程式的內部實作細節,因此非常適合用於編寫不透明方塊式自動化測試。
UI Automator 測試架構的主要功能包括:
- 擷取狀態資訊並在目標裝置上執行作業的 API。詳情請參閱「存取裝置狀態」。
- 支援跨應用程式 UI 測試的 API。詳情請參閱「UI Automator API」。
存取裝置狀態
UI Automator 測試架構會提供 UiDevice
類別,用於存取目標應用程式執行裝置並執行相關作業。您可以呼叫其方法來存取裝置屬性,例如目前的方向或顯示大小。UiDevice
類別還可讓您執行下列動作:
- 變更裝置旋轉。
- 按下硬體鍵,例如「調高音量」。
- 按下「返回」、「主畫面」或「選單」按鈕。
- 開啟通知欄。
- 擷取目前視窗的螢幕截圖。
舉例來說,如要模擬按下主畫面按鈕,請呼叫 UiDevice.pressHome()
方法。
UI Automator API
您可以使用 UI Automator API 編寫可靠的測試,而無須瞭解指定應用程式的實作詳細資料。您可以使用下列 API 擷取及操作多個應用程式的 UI 元件:
UiObject2
:代表在裝置上顯示的 UI 元素。BySelector
:指定比對 UI 元素的條件。By
:以簡潔的方式建構BySelector
。Configurator
:可讓您設定執行 UI Automator 測試的關鍵參數。
舉例來說,以下程式碼顯示如何編寫測試指令碼,在裝置上開啟 Gmail 應用程式:
Kotlin
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.pressHome() val gmail: UiObject2 = device.findObject(By.text("Gmail")) // Perform a click and wait until the app is opened. val opened: Boolean = gmail.clickAndWait(Until.newWindow(), 3000) assertThat(opened).isTrue()
Java
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.pressHome(); UiObject2 gmail = device.findObject(By.text("Gmail")); // Perform a click and wait until the app is opened. Boolean opened = gmail.clickAndWait(Until.newWindow(), 3000); assertTrue(opened);
設定 UI Automator
使用 UI Automator 建構 UI 測試前,請務必依照「設定 AndroidX Test 的專案」一文的說明設定測試原始碼位置和專案依附元件。
在 Android 應用程式模組的 build.gradle
檔案中,您必須設定對 UI Automator 程式庫的依附元件參照:
Kotlin
dependencies {
...
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
}
Groovy
dependencies {
...
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0'
}
如要最佳化 UI Automator 測試,請先檢查目標應用程式的 UI 元件,並確保可存取這些元件。這些最佳化提示會在接下來兩個部分中說明。
檢查裝置上的使用者介面
設計測試前,請檢查裝置上顯示的 UI 元件。為確保 UI Automator 測試能存取這些元件,請檢查這些元件是否有可見的文字標籤或 android:contentDescription
值,或兩者皆有。
uiautomatorviewer
工具提供方便的視覺化介面,可用於檢查版面配置階層,以及查看裝置前景可見的 UI 元件屬性。這項資訊可讓您使用 UI Automator 建立更精細的測試。例如,您可以建立符合特定可見屬性的 UI 選取器。
如要啟動 uiautomatorviewer
工具,請按照下列步驟操作:
- 在實體裝置上啟動目標應用程式。
- 將裝置連接至開發機器。
- 開啟終端機視窗,然後前往
<android-sdk>/tools/
目錄。 - 請使用下列指令執行工具:
$ uiautomatorviewer
如要查看應用程式的 UI 屬性,請按照下列步驟操作:
- 在
uiautomatorviewer
介面中,按一下「Device Screenshot」按鈕。 - 將滑鼠游標懸停在左側面板中的快照上,即可查看
uiautomatorviewer
工具識別的 UI 元件。屬性會列於右下方面板,而版面配置階層則會列於右上方面板。 - 您可以按一下「切換 NAF 節點」按鈕,查看 UI Automator 無法存取的 UI 元件。這些元件可能只提供有限的資訊。
如要瞭解 Android 提供的常見 UI 元件類型,請參閱「使用者介面」一文。
確保活動可供存取
在已實作 Android 無障礙功能的應用程式上,UI Automator 測試架構的效能會更好。如果您使用 View
類型的 UI 元素,或 SDK 中的 View
子類別,就不需要實作無障礙支援,因為這些類別已為您完成這項工作。
不過,有些應用程式會使用自訂 UI 元素,提供更豐富的使用者體驗。這類元素不會提供自動無障礙支援。如果您的應用程式包含非 SDK 的 View
子類別例項,請務必完成下列步驟,為這些元素新增無障礙功能:
- 建立擴充 ExploreByTouchHelper 的具體類別。
- 呼叫 setAccessibilityDelegate(),將新類別的例項與特定自訂 UI 元素建立關聯。
如要進一步瞭解如何在自訂檢視畫面元素中新增無障礙功能,請參閱「建構無障礙自訂檢視畫面」。如要進一步瞭解 Android 無障礙功能的一般最佳做法,請參閱「讓應用程式更易於存取」。
建立 UI Automator 測試類別
您的 UI Automator 測試類別應以與 JUnit 4 測試類別相同的方式編寫。如要進一步瞭解如何建立 JUnit 4 測試類別,以及使用 JUnit 4 斷言和註解,請參閱「建立檢測設備單元測試類別」。
在測試類別定義開頭處加入 @RunWith(AndroidJUnit4.class) 註解。您還需要指定 AndroidX 測試中提供的 AndroidJUnitRunner 類別,做為預設測試執行器。如要進一步瞭解這個步驟,請參閱「在裝置或模擬器上執行 UI Automator 測試」。
在 UI Automator 測試類別中實作下列程式設計模式:
- 請呼叫 getInstance() 方法,並將 Instrumentation 物件做為引數,藉此取得
UiDevice
物件,以便存取要測試的裝置。 - 呼叫 findObject() 方法,取得
UiObject2
物件,以便存取裝置上顯示的 UI 元件 (例如前景中的目前檢視畫面)。 - 呼叫
UiObject2
方法,模擬要在該 UI 元件上執行的特定使用者互動,例如呼叫 scrollUntil() 以捲動,以及呼叫 setText() 以編輯文字欄位。您可以視需要重複呼叫步驟 2 和 3 中的 API,以便測試涉及多個 UI 元件或使用者動作序列的更複雜使用者互動。 - 在執行這些使用者互動後,請檢查 UI 是否反映預期的狀態或行為。
下節將詳細說明這些步驟。
存取 UI 元件
UiDevice
物件是您存取及操控裝置狀態的主要方式。在測試中,您可以呼叫 UiDevice
方法,檢查各種屬性的狀態,例如目前的方向或顯示大小。測試可以使用 UiDevice
物件執行裝置層級動作,例如強制裝置進入特定旋轉角度、按下方向鍵硬體按鈕,以及按下主畫面和選單按鈕。
建議您從裝置的主畫面開始測試。您可以在主畫面 (或裝置中選擇的其他起始位置) 呼叫 UI Automator API 提供的方法,選取並與特定 UI 元素互動。
以下程式碼片段顯示測試如何取得 UiDevice
的例項,並模擬按下 Home 鍵:
Kotlin
import org.junit.Before import androidx.test.runner.AndroidJUnit4 import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until ... private const val BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample" private const val LAUNCH_TIMEOUT = 5000L private const val STRING_TO_BE_TYPED = "UiAutomator" @RunWith(AndroidJUnit4::class) @SdkSuppress(minSdkVersion = 18) class ChangeTextBehaviorTest2 { private lateinit var device: UiDevice @Before fun startMainActivityFromHomeScreen() { // Initialize UiDevice instance device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) // Start from the home screen device.pressHome() // Wait for launcher val launcherPackage: String = device.launcherPackageName assertThat(launcherPackage, notNullValue()) device.wait( Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT ) // Launch the app val context = ApplicationProvider.getApplicationContext<Context>() val intent = context.packageManager.getLaunchIntentForPackage( BASIC_SAMPLE_PACKAGE).apply { // Clear out any previous instances addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) } context.startActivity(intent) // Wait for the app to appear device.wait( Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT ) } }
Java
import org.junit.Before; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.By; import androidx.test.uiautomator.Until; ... @RunWith(AndroidJUnit4.class) @SdkSuppress(minSdkVersion = 18) public class ChangeTextBehaviorTest { private static final String BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample"; private static final int LAUNCH_TIMEOUT = 5000; private static final String STRING_TO_BE_TYPED = "UiAutomator"; private UiDevice device; @Before public void startMainActivityFromHomeScreen() { // Initialize UiDevice instance device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); // Start from the home screen device.pressHome(); // Wait for launcher final String launcherPackage = device.getLauncherPackageName(); assertThat(launcherPackage, notNullValue()); device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT); // Launch the app Context context = ApplicationProvider.getApplicationContext(); final Intent intent = context.getPackageManager() .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE); // Clear out any previous instances intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); // Wait for the app to appear device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT); } }
在這個範例中,@SdkSuppress(minSdkVersion = 18) 陳述式可確保測試只在搭載 Android 4.3 (API 級別 18) 以上版本的裝置上執行,符合 UI Automator 架構的要求。
使用 findObject()
方法擷取 UiObject2
,代表符合指定選取器條件的檢視畫面。您可以視需要重複使用在應用程式測試其他部分建立的 UiObject2
例項。請注意,每次測試使用 UiObject2
例項點選 UI 元素或查詢屬性時,UI Automator 測試架構都會搜尋目前顯示畫面中的相符項目。
以下程式碼片段說明測試如何建構代表應用程式中「取消」按鈕和「確定」按鈕的 UiObject2
例項。
Kotlin
val okButton: UiObject2 = device.findObject( By.text("OK").clazz("android.widget.Button") ) // Simulate a user-click on the OK button, if found. if (okButton != null) { okButton.click() }
Java
UiObject2 okButton = device.findObject( By.text("OK").clazz("android.widget.Button") ); // Simulate a user-click on the OK button, if found. if (okButton != null) { okButton.click(); }
指定選取器
如果您想存取應用程式中的特定 UI 元件,請使用 By
類別建構 BySelector
例項。BySelector
代表對顯示 UI 中特定元素的查詢。
如果找到多個相符元素,系統會將版面配置階層中的第一個相符元素傳回做為目標 UiObject2
。建構 BySelector
時,您可以將多個資源鏈結在一起,以便精進搜尋結果。如果找不到相符的 UI 元素,系統會傳回 null
。
您可以使用 hasChild()
或 hasDescendant()
方法巢狀排列多個 BySelector
例項。舉例來說,以下程式碼範例說明測試如何指定搜尋作業,找出第一個含有文字屬性子項 UI 元素的 ListView
。
Kotlin
val listView: UiObject2 = device.findObject( By.clazz("android.widget.ListView") .hasChild( By.text("Apps") ) )
Java
UiObject2 listView = device.findObject( By.clazz("android.widget.ListView") .hasChild( By.text("Apps") ) );
在選取器條件中指定物件狀態可能很有幫助。舉例來說,如果您想選取所有已勾選元素的清單,以便取消勾選這些元素,請將引數設為 true 並呼叫 checked()
方法。
執行動作
測試取得 UiObject2
物件後,您就可以呼叫 UiObject2
類別中的各項方法,在該物件所代表的 UI 元件上執行使用者互動。您可以指定以下動作:
click()
:點選 UI 元素可見邊界區塊的中心。drag()
:將這個物件拖曳至任意座標。setText()
:清除欄位內容後,在可編輯欄位中設定文字。相反地,clear()
方法會清除可編輯欄位中的現有文字。swipe()
:執行滑動動作,朝指定方向滑動。scrollUntil()
:向指定方向執行捲動動作,直到Condition
或EventCondition
滿足為止。
透過 UI 自動化測試架構,您可以透過 getContext()
取得 Context 物件,在不使用殼層指令的情況下,傳送Intent 或啟動Activity。
以下程式碼片段說明測試如何使用意圖啟動測試中的應用程式。如果您只想測試計算機應用程式,而不需要啟動器,這種做法就很實用。
Kotlin
fun setUp() { ... // Launch a simple calculator app val context = getInstrumentation().context val intent = context.packageManager.getLaunchIntentForPackage(CALC_PACKAGE).apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) } // Clear out any previous instances context.startActivity(intent) device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT) }
Java
public void setUp() { ... // Launch a simple calculator app Context context = getInstrumentation().getContext(); Intent intent = context.getPackageManager() .getLaunchIntentForPackage(CALC_PACKAGE); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances context.startActivity(intent); device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT); }
驗證結果
InstrumentationTestCase 會擴充 TestCase,因此您可以使用標準 JUnit Assert 方法,測試應用程式中的 UI 元件是否會傳回預期的結果。
以下程式碼片段說明測試如何在計算機應用程式中找出多個按鈕,依序按下按鈕,然後驗證是否顯示正確的結果。
Kotlin
private const val CALC_PACKAGE = "com.myexample.calc" fun testTwoPlusThreeEqualsFive() { // Enter an equation: 2 + 3 = ? device.findObject(By.res(CALC_PACKAGE, "two")).click() device.findObject(By.res(CALC_PACKAGE, "plus")).click() device.findObject(By.res(CALC_PACKAGE, "three")).click() device.findObject(By.res(CALC_PACKAGE, "equals")).click() // Verify the result = 5 val result: UiObject2 = device.findObject(By.res(CALC_PACKAGE, "result")) assertEquals("5", result.text) }
Java
private static final String CALC_PACKAGE = "com.myexample.calc"; public void testTwoPlusThreeEqualsFive() { // Enter an equation: 2 + 3 = ? device.findObject(By.res(CALC_PACKAGE, "two")).click(); device.findObject(By.res(CALC_PACKAGE, "plus")).click(); device.findObject(By.res(CALC_PACKAGE, "three")).click(); device.findObject(By.res(CALC_PACKAGE, "equals")).click(); // Verify the result = 5 UiObject2 result = device.findObject(By.res(CALC_PACKAGE, "result")); assertEquals("5", result.getText()); }
在裝置或模擬器上執行 UI Automator 測試
您可以透過 Android Studio 或指令列執行 UI Automator 測試。請務必將 AndroidJUnitRunner
指定為專案中的預設檢測執行器。
其他示例
與系統 UI 互動
UI Automator 可以與畫面上的所有內容互動,包括應用程式以外的系統元素,如以下程式碼片段所示:
Kotlin
// Opens the System Settings. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.executeShellCommand("am start -a android.settings.SETTINGS")
Java
// Opens the System Settings. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.executeShellCommand("am start -a android.settings.SETTINGS");
Kotlin
// Opens the notification shade. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.openNotification()
Java
// Opens the notification shade. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.openNotification();
Kotlin
// Opens the Quick Settings shade. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.openQuickSettings()
Java
// Opens the Quick Settings shade. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.openQuickSettings();
Kotlin
// Get the system clock. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock")) print(clock.getText())
Java
// Get the system clock. device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock")); print(clock.getText());
等待轉換

螢幕轉場可能需要一些時間,而且無法可靠地預測其持續時間,因此您應讓 UI Automator 在執行作業後等待。UI Automator 提供多種方法來執行這項操作:
UiDevice.performActionAndWait(Runnable action, EventCondition<U> condition, long timeout)
:舉例來說,如要點選按鈕並等待新視窗出現,請呼叫device.performActionAndWait(() -> button.click(), Until.newWindow(), timeout)
UiDevice.wait(Condition<Object, U> condition, long timeout)
:舉例來說,如要等到裝置上有特定UiObject2
,請呼叫device.wait(Until.hasObject(By.text("my_text")), timeout);
UiObject2.wait(@NonNull Condition<Object, U> condition, long timeout)
:舉例來說,如要等待核取方塊勾選,請呼叫checkbox.wait(Until.checked(true), timeout);
UiObject2.clickAndWait(@NonNull EventCondition<U> condition, long timeout)
:例如,如果要點選按鈕並等待新視窗出現,請呼叫button.clickAndWait(Until.newWindow(), timeout);
UiObject2.scrollUntil(@NonNull Direction direction, @NonNull Condition<Object, U> condition)
:例如,如果要捲動至出現新物件,請呼叫object.scrollUntil(Direction.DOWN, Until.hasObject(By.text('new_obj')));
UiObject2.scrollUntil(@NonNull Direction direction, @NonNull EventCondition<U> condition)
:例如,如要捲動至畫面底部,請呼叫object.scrollUntil(Direction.DOWN, Until.scrollFinished(Direction.DOWN));
下列程式碼片段說明如何使用 UI Automator,透過等待轉換的 performActionAndWait()
方法,在系統設定中關閉「請勿打擾」模式:
Kotlin
@Test @SdkSuppress(minSdkVersion = 21) @Throws(Exception::class) fun turnOffDoNotDisturb() { device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) device.performActionAndWait({ try { device.executeShellCommand("am start -a android.settings.SETTINGS") } catch (e: IOException) { throw RuntimeException(e) } }, Until.newWindow(), 1000) // Check system settings has been opened. Assert.assertTrue(device.hasObject(By.pkg("com.android.settings"))) // Scroll the settings to the top and find Notifications button var scrollableObj: UiObject2 = device.findObject(By.scrollable(true)) scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP)) val notificationsButton = scrollableObj.findObject(By.text("Notifications")) // Click the Notifications button and wait until a new window is opened. device.performActionAndWait({ notificationsButton.click() }, Until.newWindow(), 1000) scrollableObj = device.findObject(By.scrollable(true)) // Scroll down until it finds a Do Not Disturb button. val doNotDisturb = scrollableObj.scrollUntil( Direction.DOWN, Until.findObject(By.textContains("Do Not Disturb")) ) device.performActionAndWait({ doNotDisturb.click() }, Until.newWindow(), 1000) // Turn off the Do Not Disturb. val turnOnDoNotDisturb = device.findObject(By.text("Turn on now")) turnOnDoNotDisturb?.click() Assert.assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000)) }
Java
@Test @SdkSuppress(minSdkVersion = 21) public void turnOffDoNotDisturb() throws Exception{ device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.performActionAndWait(() -> { try { device.executeShellCommand("am start -a android.settings.SETTINGS"); } catch (IOException e) { throw new RuntimeException(e); } }, Until.newWindow(), 1000); // Check system settings has been opened. assertTrue(device.hasObject(By.pkg("com.android.settings"))); // Scroll the settings to the top and find Notifications button UiObject2 scrollableObj = device.findObject(By.scrollable(true)); scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP)); UiObject2 notificationsButton = scrollableObj.findObject(By.text("Notifications")); // Click the Notifications button and wait until a new window is opened. device.performActionAndWait(() -> notificationsButton.click(), Until.newWindow(), 1000); scrollableObj = device.findObject(By.scrollable(true)); // Scroll down until it finds a Do Not Disturb button. UiObject2 doNotDisturb = scrollableObj.scrollUntil(Direction.DOWN, Until.findObject(By.textContains("Do Not Disturb"))); device.performActionAndWait(()-> doNotDisturb.click(), Until.newWindow(), 1000); // Turn off the Do Not Disturb. UiObject2 turnOnDoNotDisturb = device.findObject(By.text("Turn on now")); if(turnOnDoNotDisturb != null) { turnOnDoNotDisturb.click(); } assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000)); }
其他資源
如要進一步瞭解如何在 Android 測試中使用 UI Automator,請參閱下列資源。
參考說明文件:
範例
- BasicSample:基本 UI Automator 範例。