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 測試之前,請務必先設定測試 如設定專案所述,原始碼位置和專案依附元件 。
在 Android 應用程式模組的 build.gradle
檔案中,您必須設定依附元件
UI Automator 程式庫的參照:
Kotlin
dependencies {
...
androidTestImplementation('androidx.test.uiautomator:uiautomator:2.3.0-alpha03')
}
Groovy
dependencies {
...
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0-alpha03'
}
如要最佳化 UI Automator 測試,您應先檢查目標應用程式的 UI 元件,並確保這些元件可供存取。這些最佳化提示 下述兩節的說明。
在裝置上檢查 UI
設計測試之前,請檢查
裝置。為確保 UI Automator 測試能存取這些元件,請
查看這些元件是否具有可見的文字標籤
android:contentDescription
值,或同時指定兩者。
uiautomatorviewer
工具提供便利的視覺化介面供您檢查
版面配置階層,並查看顯示 UI 元件的屬性
放進裝置前景您可以運用這項資訊
使用 UI Automator 進行精細的測試舉例來說,您可以建立 UI 選取器
與特定可見屬性相符
如要啟動 uiautomatorviewer
工具:
- 在實體裝置上啟動目標應用程式。
- 將裝置連線至開發機器。
- 開啟終端機視窗,然後前往
<android-sdk>/tools/
目錄。 - 使用下列指令執行工具:
$ uiautomatorviewer
如要查看應用程式的 UI 屬性:
- 在
uiautomatorviewer
介面中,按一下「Device Screenshot」按鈕。 - 將滑鼠遊標懸停在左側面板的快照上,查看 UI 元件
uiautomatorviewer
工具識別出。該屬性會列在 低於 右側面板和版面配置階層。 - 您也可以點選「Toggle NAF Nodes」按鈕,查看 UI 元件 也就是 UI Automator 中無法存取的內容只有部分資訊 這些元件可用的程式庫
如要瞭解 Android 提供的常見 UI 元件類型,請參閱 介面。
確保你的活動可供存取
UI Automator 測試架構在已實作的應用程式上成效較佳
Android 無障礙功能。使用 View
類型的 UI 元素時
來自 SDK 的 View
子類別,因此您不需要導入無障礙功能
但這些課程已經為您完成這些步驟。
不過,有些應用程式會使用自訂 UI 元素來提供更豐富的使用者體驗。
這類元素不會提供自動支援。如果您的應用程式
包含非來自 SDK 的 View
子類別執行個體,請
請依序完成
步驟如下:
- 建立擴充 ExploreByTouchHelper 的具體類別。
- 透過下列方式,將新類別的例項與特定自訂 UI 元素建立關聯: 呼叫 setAccessibilityDelegate()。
如需在自訂檢視畫面中新增無障礙功能的其他指引 元素,請參閱建構可存取的自訂檢視區塊。如要進一步瞭解 針對 Android 上的無障礙功能提供一般最佳做法,請參閱讓應用程式更臻完善 易於存取:
建立 UI Automator 測試類別
您的 UI Automator 測試類別應與 JUnit 4 測試相同的編寫方式 類別進一步瞭解如何建立 JUnit 4 測試類別及使用 JUnit 4 斷言和註解,請參閱「建立檢測設備單元測試類別」。
在測試開始時新增 @RunWith(AndroidJUnit4.class) 註解 類別定義。您也需要指定 AndroidJUnitRunner 類別, 做為預設測試執行器。此步驟會說明 詳情請參閱「在裝置或模擬器上執行 UI Automator 測試」。
在 UI Automator 測試類別中實作下列程式設計模型:
- 呼叫,取得要測試的裝置的
UiDevice
物件,以便存取 getInstance() 方法並傳送 Instrumentation 物件, 引數。 - 取得
UiObject2
物件,存取顯示在畫面上的 UI 元件 裝置 (例如在前景的目前檢視畫面),方法是呼叫 findObject() 方法。 - 模擬特定使用者互動來執行該 UI 元件:
呼叫
UiObject2
方法;例如呼叫 可捲動 scrollUntil() 來捲動;使用 setText() 編輯文字欄位。 您可以按照步驟 2 和 3 中的 API 呼叫 視需要重複執行,測試更複雜的使用者互動,例如: 多個 UI 元件或使用者動作序列 - 檢查 UI 在使用者離開後,是否反映了預期的狀態或行為 過程中會執行互動
下文將詳細說明相關步驟。
存取 UI 元件
UiDevice
物件是您存取及操控
裝置狀態。在測試中,您可以呼叫 UiDevice
方法進行檢查
各種屬性的狀態,例如目前的螢幕方向或顯示大小。
測試可以使用 UiDevice
物件執行裝置層級的操作。
例如強制讓裝置旋轉特定角度、按下 D-Pad 硬體
按鈕,並按下主畫面和選單按鈕。
建議您在裝置的主畫面中開始測試。最低價格: 主畫面 (或你在裝置上選擇的其他開始位置)、 您可以呼叫 UI Automator API 所提供的方法,藉此選取和互動 搭配特定 UI 元素
下列程式碼片段顯示測試如何取得
UiDevice
並模擬按下主畫面按鈕的動作:
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
視需要在應用程式測試其他部分建立的執行個體。
請注意,UI Automator 測試架構會搜尋目前的顯示內容,
每次測試使用 UiObject2
例項點擊使用者介面時
元素或查詢屬性
下列程式碼片段說明如何在測試中建構 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
執行個體。例如,以下程式碼範例顯示
測試可指定搜尋的方式,找出第一個 ListView
具有含文字屬性的子項 UI 元素。
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") ) );
建議您在選擇器條件中指定物件狀態,適用對象
舉例來說,如果您想要選取所有已勾選元素的清單
請取消勾選 checked()
方法,並將引數設為 true。
執行動作
測試取得 UiObject2
物件後,您可以在
UiObject2
類別,用於在 UI 元件上執行使用者互動
由該物件表示的您可以指定下列動作:
click()
:點選 UI 元素可見邊界的中心點。drag()
:將此物件拖曳至任意座標。setText()
:在清除 欄位中的內容。相反地,clear()
方法會清除現有文字 對應於可編輯欄位中swipe()
:往指定方向執行滑動動作。scrollUntil()
:朝指定方向執行捲動動作 直到滿足Condition
或EventCondition
為止。
UI Automator 測試架構可讓您傳送 Intent 或啟動
在不使用殼層指令的情況下建立 Activity,方法是取得結構定義
物件更新路徑getContext()
。
下列程式碼片段說明如何在測試中使用意圖啟動 測試中的應用程式。如果您只想在測試時 計算機應用程式執行時,系統並不在意啟動器。
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 斷言方法,測試應用程式中的 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 或
建立服務帳戶請務必將 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(device.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 範例。