เฟรมเวิร์กการทดสอบ UI Automator มีชุด API สำหรับสร้างการทดสอบ UI ที่ โต้ตอบกับแอปของผู้ใช้และแอปของระบบ
ข้อมูลเบื้องต้นเกี่ยวกับการทดสอบ UI Automator สมัยใหม่
UI Automator 2.4 ขอแนะนำ Domain Specific Language (DSL) ที่ปรับปรุงแล้วและเป็นมิตรกับ Kotlin ซึ่งช่วยลดความซับซ้อนในการเขียนการทดสอบ UI สำหรับ Android API Surface ใหม่นี้ มุ่งเน้นที่การค้นหาองค์ประกอบตามตัวบ่งชี้และการควบคุมสถานะแอปอย่างชัดเจน ใช้เพื่อสร้างการทดสอบอัตโนมัติที่ดูแลรักษาได้ง่ายและเชื่อถือได้มากขึ้น
UI Automator ช่วยให้คุณทดสอบแอปจากภายนอกกระบวนการของแอปได้ ซึ่งจะช่วยให้คุณทดสอบเวอร์ชันที่เผยแพร่โดยใช้การลดขนาดได้ นอกจากนี้ UI Automator ยังช่วยในการเขียนการทดสอบ Macrobenchmark ด้วย
ฟีเจอร์สำคัญของแนวทางที่ทันสมัยมีดังนี้
uiAutomatorขอบเขตการทดสอบเฉพาะเพื่อโค้ดทดสอบที่สะอาดและสื่อความหมายมากขึ้น- เมธอดต่างๆ เช่น
onElement,onElementsและonElementOrNullสำหรับการค้นหาองค์ประกอบ UI ที่มีเพรดิเคตที่ชัดเจน - กลไกการรอในตัวสำหรับองค์ประกอบแบบมีเงื่อนไข
onElement*(timeoutMs: Long = 10000) - การจัดการสถานะของแอปอย่างชัดเจน เช่น
waitForStableและwaitForAppToBeVisible - การโต้ตอบโดยตรงกับโหนดหน้าต่างการช่วยเหลือพิเศษสำหรับสถานการณ์การทดสอบแบบหลายหน้าต่าง
- ความสามารถในการถ่ายภาพหน้าจอในตัวและ
ResultsReporterสำหรับการทดสอบภาพ และการแก้ไขข้อบกพร่อง
สร้างโปรเจ็กต์
หากต้องการเริ่มใช้ UI Automator API ที่ทันสมัย ให้อัปเดตไฟล์ build.gradle.kts ของโปรเจ็กต์ให้มีการอ้างอิงล่าสุด
Kotlin
dependencies {
...
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}
Groovy
dependencies {
...
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}
แนวคิดหลักของ API
ส่วนต่อไปนี้จะอธิบายแนวคิดหลักของ Modern UI Automator API
ขอบเขตการทดสอบ uiAutomator
เข้าถึง API ของ UI Automator ใหม่ทั้งหมดภายในบล็อก uiAutomator { ... }
ฟังก์ชันนี้จะสร้าง UiAutomatorTestScope ที่มีสภาพแวดล้อมที่กระชับ
และปลอดภัยสำหรับประเภทการดำเนินการทดสอบ
uiAutomator {
// All your UI Automator actions go here
startApp("com.example.targetapp")
onElement { textAsString() == "Hello, World!" }.click()
}
ค้นหาองค์ประกอบ UI
ใช้ UI Automator API กับ Predicate เพื่อค้นหาองค์ประกอบ UI โดยตัวบ่งชี้เหล่านี้ ช่วยให้คุณกำหนดเงื่อนไขสำหรับพร็อพเพอร์ตี้ เช่น ข้อความ สถานะที่เลือกหรือโฟกัส และคำอธิบายเนื้อหา
onElement { predicate }: แสดงผลองค์ประกอบ UI แรกที่ตรงกับ เพรดิเคตภายในระยะหมดเวลาเริ่มต้น ฟังก์ชันจะส่งข้อยกเว้นหากไม่พบองค์ประกอบที่ตรงกัน// Find a button with the text "Submit" and click it onElement { textAsString() == "Submit" }.click() // Find a UI element by its resource ID onElement { viewIdResourceName == "my_button_id" }.click() // Allow a permission request watchFor(PermissionDialog) { clickAllow() }onElementOrNull { predicate }: คล้ายกับonElementแต่จะแสดงผลเป็นnullหากฟังก์ชันไม่พบองค์ประกอบที่ตรงกันภายในระยะหมดเวลา โดยไม่ส่งข้อยกเว้น ใช้วิธีนี้กับองค์ประกอบที่ไม่บังคับval optionalButton = onElementOrNull { textAsString() == "Skip" } optionalButton?.click() // Click only if the button existsonElements { predicate }: รอจนกว่าองค์ประกอบ UI อย่างน้อย 1 รายการจะตรงกับ เพรดิเคตที่ระบุ จากนั้นจะแสดงผลรายการองค์ประกอบ UI ที่ตรงกันทั้งหมด// Get all items in a list Ui element val listItems = onElements { className == "android.widget.TextView" && isClickable } listItems.forEach { it.click() }
เคล็ดลับในการใช้การโทร onElement มีดังนี้
การเรียก
onElementแบบต่อเนื่องสำหรับองค์ประกอบที่ซ้อนกัน: คุณสามารถเรียกonElementแบบต่อเนื่องเพื่อค้นหาองค์ประกอบภายในองค์ประกอบอื่นๆ ตามลำดับชั้นขององค์ประกอบระดับบนสุดและองค์ประกอบย่อย// Find a parent Ui element with ID "first", then its child with ID "second", // then its grandchild with ID "third", and click it. onElement { viewIdResourceName == "first" } .onElement { viewIdResourceName == "second" } .onElement { viewIdResourceName == "third" } .click()ระบุระยะหมดเวลาสำหรับฟังก์ชัน
onElement*โดยส่งค่าที่แสดงถึง มิลลิวินาที// Find a Ui element with a zero timeout (instant check) onElement(0) { viewIdResourceName == "something" }.click() // Find a Ui element with a custom timeout of 10 seconds onElement(10_000) { textAsString() == "Long loading text" }.click()
โต้ตอบกับองค์ประกอบ UI
โต้ตอบกับองค์ประกอบ UI โดยจำลองการคลิกหรือตั้งค่าข้อความใน ช่องที่แก้ไขได้
// Click a Ui element
onElement { textAsString() == "Tap Me" }.click()
// Set text in an editable field
onElement { className == "android.widget.EditText" }.setText("My input text")
// Perform a long click
onElement { contentDescription == "Context Menu" }.longClick()
จัดการสถานะแอปและ Watcher
จัดการวงจรของแอปและจัดการองค์ประกอบ UI ที่ไม่คาดคิดซึ่งอาจปรากฏขึ้นระหว่างการทดสอบ
การจัดการวงจรของแอป
API มีวิธีควบคุมสถานะของแอปที่อยู่ระหว่างการทดสอบดังนี้
// Start a specific app by package name. Used for benchmarking and other
// self-instrumenting tests.
startApp("com.example.targetapp")
// Start a specific activity within the target app
startActivity(SomeActivity::class.java)
// Start an intent
startIntent(myIntent)
// Clear the app's data (resets it to a fresh state)
clearAppData("com.example.targetapp")
จัดการ UI ที่ไม่คาดคิด
watchFor API ช่วยให้คุณกำหนดตัวแฮนเดิลสำหรับองค์ประกอบ UI ที่ไม่คาดคิดได้
เช่น กล่องโต้ตอบสิทธิ์ ซึ่งอาจปรากฏขึ้นระหว่างขั้นตอนการทดสอบ ซึ่ง
ใช้กลไกการตรวจสอบภายในแต่มีความยืดหยุ่นมากกว่า
import androidx.test.uiautomator.PermissionDialog
@Test
fun myTestWithPermissionHandling() = uiAutomator {
startActivity(MainActivity::class.java)
// Register a watcher to click "Allow" if a permission dialog appears
watchFor(PermissionDialog) { clickAllow() }
// Your test steps that might trigger a permission dialog
onElement { textAsString() == "Request Permissions" }.click()
// Example: You can register a different watcher later if needed
clearAppData("com.example.targetapp")
// Now deny permissions
startApp("com.example.targetapp")
watchFor(PermissionDialog) { clickDeny() }
onElement { textAsString() == "Request Permissions" }.click()
}
PermissionDialog เป็นตัวอย่างของ ScopedWatcher<T> โดยที่ T คือ
ออบเจ็กต์ที่ส่งเป็นขอบเขตไปยังบล็อกใน watchFor คุณสร้าง
โปรแกรมตรวจสอบที่กำหนดเองตามรูปแบบนี้ได้
รอให้แอปปรากฏและมีความเสถียร
บางครั้งการทดสอบอาจต้องรอให้องค์ประกอบปรากฏหรือเสถียรก่อน UI Automator มี API หลายรายการที่จะช่วยในเรื่องนี้
waitForAppToBeVisible("com.example.targetapp") รอให้องค์ประกอบ UI ที่มี
ชื่อแพ็กเกจที่ระบุปรากฏบนหน้าจอภายในระยะหมดเวลาที่ปรับแต่งได้
// Wait for the app to be visible after launching it
startApp("com.example.targetapp")
waitForAppToBeVisible("com.example.targetapp")
ใช้ waitForStable() API เพื่อยืนยันว่า UI ของแอปถือว่าเสถียรก่อนที่จะโต้ตอบกับ UI
// Wait for the entire active window to become stable
activeWindow().waitForStable()
// Wait for a specific Ui element to become stable (e.g., after a loading animation)
onElement { viewIdResourceName == "my_loading_indicator" }.waitForStable()
ฟีเจอร์ขั้นสูง
ฟีเจอร์ต่อไปนี้มีประโยชน์สำหรับสถานการณ์การทดสอบที่ซับซ้อนมากขึ้น
โต้ตอบกับหลายหน้าต่าง
API ของ UI Automator ช่วยให้คุณโต้ตอบและตรวจสอบองค์ประกอบ UI ได้โดยตรง ซึ่งจะมีประโยชน์อย่างยิ่งสำหรับสถานการณ์ที่เกี่ยวข้องกับหลายหน้าต่าง เช่น โหมดการแสดงภาพซ้อนภาพ (PiP) หรือเลย์เอาต์แบบแยกหน้าจอ
// Find the first window that is in Picture-in-Picture mode
val pipWindow = windows()
.first { it.isInPictureInPictureMode == true }
// Now you can interact with elements within that specific window
pipWindow.onElement { textAsString() == "Play" }.click()
ภาพหน้าจอและการยืนยันด้วยภาพ
ถ่ายภาพหน้าจอทั้งหน้าจอ หน้าต่างที่เฉพาะเจาะจง หรือ องค์ประกอบ UI แต่ละรายการได้โดยตรงภายในเทสต์ ซึ่งจะเป็นประโยชน์ต่อการทดสอบและการแก้ไขข้อบกพร่องของ การถดถอยของภาพ
uiautomator {
// Take a screenshot of the entire active window
val fullScreenBitmap: Bitmap = activeWindow().takeScreenshot()
fullScreenBitmap.saveToFile(File("/sdcard/Download/full_screen.png"))
// Take a screenshot of a specific UI element (e.g., a button)
val buttonBitmap: Bitmap = onElement { viewIdResourceName == "my_button" }.takeScreenshot()
buttonBitmap.saveToFile(File("/sdcard/Download/my_button_screenshot.png"))
// Example: Take a screenshot of a PiP window
val pipWindowScreenshot = windows()
.first { it.isInPictureInPictureMode == true }
.takeScreenshot()
pipWindowScreenshot.saveToFile(File("/sdcard/Download/pip_screenshot.png"))
}
ฟังก์ชันส่วนขยาย saveToFile สำหรับบิตแมปช่วยให้บันทึกรูปภาพที่แคปเจอร์ไปยังเส้นทางที่ระบุได้ง่ายขึ้น
ใช้ ResultsReporter เพื่อการแก้ไขข้อบกพร่อง
ResultsReporter ช่วยให้คุณเชื่อมโยงอาร์ติแฟกต์การทดสอบ เช่น ภาพหน้าจอ
กับผลการทดสอบใน Android Studio ได้โดยตรง เพื่อให้ตรวจสอบและ
แก้ไขข้อบกพร่องได้ง่ายขึ้น
uiAutomator {
startApp("com.example.targetapp")
val reporter = ResultsReporter("MyTestArtifacts") // Name for this set of results
val file = reporter.addNewFile(
filename = "my_screenshot",
title = "Accessible button image" // Title that appears in Android Studio test results
)
// Take a screenshot of an element and save it using the reporter
onElement { textAsString() == "Accessible button" }
.takeScreenshot()
.saveToFile(file)
// Report the artifacts to instrumentation, making them visible in Android Studio
reporter.reportToInstrumentation()
}
ย้ายข้อมูลจาก UI Automator เวอร์ชันเก่า
หากคุณมีการทดสอบ UI Automator ที่เขียนด้วย API เวอร์ชันเก่าอยู่แล้ว ให้ใช้ตารางต่อไปนี้เป็นข้อมูลอ้างอิงในการย้ายข้อมูลไปใช้แนวทางที่ทันสมัย
| ประเภทการดำเนินการ | วิธีการ UI Automator แบบเดิม | วิธีการ UI Automator ใหม่ |
|---|---|---|
| จุดแรกเข้า | UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) |
รวมตรรกะการทดสอบไว้ในขอบเขต uiAutomator { ... } |
| ค้นหาองค์ประกอบ UI | device.findObject(By.res("com.example.app:id/my_button")) |
onElement { viewIdResourceName == "my\_button" } |
| ค้นหาองค์ประกอบ UI | device.findObject(By.text("Click Me")) |
onElement { textAsString() == "Click Me" } |
| รอ UI ที่ไม่ได้ใช้งาน | device.waitForIdle() |
ต้องการใช้กลไกการหมดเวลาในตัวของ onElement หรือไม่ก็ activeWindow().waitForStable() |
| ค้นหาองค์ประกอบย่อย | การซ้อนfindObjectการโทรด้วยตนเอง |
onElement().onElement() การเชื่อมโยง |
| จัดการกล่องโต้ตอบสิทธิ์ | UiAutomator.registerWatcher() |
watchFor(PermissionDialog) |