UI Automator 是一種 UI 測試架構,適用於系統和已安裝應用程式的跨應用程式功能 UI 測試。無論焦點位於哪個 Activity
,您都可以使用 UI Automator API 與裝置上顯示的元素互動,因此可以在測試裝置中執行開啟「設定」選單或應用程式啟動器等作業。測試時,您可以使用便利的描述元 (例如元件中顯示的文字或內容說明),查詢 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 元件。為確保 UI Automator 測試可以存取這些元件,請確認這些元件有顯示文字標籤、android:contentDescription
值,或兩者皆有。
uiautomatorviewer
工具提供便利的視覺化介面,可檢查版面配置階層,以及查看裝置前景所顯示 UI 元件的屬性。您可以使用 UI Automator,根據這項資訊建立更精細的測試。例如,您可以建立符合特定可見屬性的 UI 選取器。
如要啟動 uiautomatorviewer
工具:
- 在實體裝置上啟動目標應用程式。
- 將裝置連線至開發機器。
- 開啟終端機視窗,然後前往
<android-sdk>/tools/
目錄。 - 使用下列指令執行工具:
$ uiautomatorviewer
如要查看應用程式的 UI 屬性,請按照下列步驟操作:
- 在
uiautomatorviewer
介面中,按一下「Device Screenshot」按鈕。 - 將游標懸停在左側面板中的快照上,即可查看
uiautomatorviewer
工具識別的使用者介面元件。屬性會列在右下方面板中,而版面配置階層則會列在右上方面板中。 - (選用) 按一下「Toggle NAF Nodes」(切換 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 Test 中提供的 AndroidJUnitRunner 類別做為預設測試執行器。如需這個步驟的詳細說明,請參閱「在裝置或模擬器上執行 UI Automator 測試」。
在 UI Automator 測試類別中實作下列程式設計模型:
- 呼叫 getInstance() 方法並傳遞 Instrumentation 物件做為引數,即可取得
UiDevice
物件,存取要測試的裝置。 - 呼叫 findObject() 方法,取得
UiObject2
物件,存取裝置上顯示的 UI 元件 (例如前景中的目前檢視畫面)。 - 呼叫
UiObject2
方法,模擬要在該 UI 元件上執行的特定使用者互動;例如,呼叫 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
執行個體。請注意,每當測試使用 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")
)
);
在選取器條件中指定物件狀態可能很有用。舉例來說,如要選取所有已勾選的元素清單,以便清除這些元素,請呼叫 checked()
方法,並將引數設為 true。
執行動作
測試取得 UiObject2
物件後,即可呼叫 UiObject2
類別中的方法,對該物件代表的 UI 元件執行使用者互動。您可以指定下列動作:
click()
:點選 UI 元素可見邊界的中心。drag()
:將這個物件拖曳至任意座標。setText()
:在清除欄位內容後,設定可編輯欄位中的文字。反之,clear()
方法會清除可編輯欄位中的現有文字。swipe()
:朝指定方向執行滑動動作。scrollUntil()
:朝指定方向執行捲動動作,直到滿足Condition
或EventCondition
為止。
您可以使用 UI Automator 測試架構,透過 getContext()
取得 Context 物件,藉此傳送 Intent 或啟動 Activity,而不必使用殼層指令。
下列程式碼片段顯示測試如何使用 Intent 啟動受測應用程式。如果您只對測試計算機應用程式感興趣,不在意啟動器,這個方法就非常實用。
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 範例。