เขียนการทดสอบอัตโนมัติด้วย UI Automator

เฟรมเวิร์กการทดสอบ UI Automator มีชุด API สำหรับสร้างการทดสอบ UI ที่โต้ตอบกับแอปของผู้ใช้และแอปของระบบ

ข้อมูลเบื้องต้นเกี่ยวกับการทดสอบ UI Automator ที่ทันสมัย

UI Automator 2.4 ขอแนะนำ Domain Specific Language (DSL) ที่ปรับปรุงให้ใช้งานง่ายและเหมาะกับ Kotlin ซึ่งช่วยลดความซับซ้อนในการเขียนการทดสอบ UI สำหรับ Android API ใหม่นี้มุ่งเน้นไปที่การค้นหาองค์ประกอบตามเพรดิเคตและการควบคุมสถานะของแอปอย่างชัดเจน ใช้ API นี้เพื่อสร้างการทดสอบอัตโนมัติที่ดูแลรักษาง่ายและเชื่อถือได้มากขึ้น

UI Automator ช่วยให้คุณทดสอบแอปจากภายนอกกระบวนการของแอปได้ ซึ่งช่วยให้คุณทดสอบแอปเวอร์ชันที่เผยแพร่โดยใช้การลดขนาดโค้ดได้ นอกจากนี้ UI Automator ยังมีประโยชน์เมื่อเขียนการทดสอบ Macrobenchmark

ฟีเจอร์สำคัญของแนวทางที่ทันสมัย ได้แก่

  • ขอบเขตการทดสอบ uiAutomator เฉพาะสำหรับการเขียนโค้ดทดสอบที่ชัดเจนและสื่อความหมายมากขึ้น
  • เมธอดต่างๆ เช่น onElement, onElements และ onElementOrNull สำหรับการค้นหาองค์ประกอบ UI ด้วยเพรดิเคตที่ชัดเจน
  • กลไกการรอในตัวสำหรับองค์ประกอบแบบมีเงื่อนไข onElement*(timeoutMs: Long = 10000)
  • การจัดการสถานะของแอปอย่างชัดเจน เช่น waitForStable และ waitForAppToBeVisible
  • การโต้ตอบโดยตรงกับโหนดหน้าต่างการช่วยเหลือพิเศษสำหรับสถานการณ์การทดสอบแบบหลายหน้าต่าง
  • ความสามารถในการจับภาพหน้าจอในตัวและ ResultsReporter สำหรับการทดสอบและการแก้ไขข้อบกพร่องด้วยภาพ

ตั้งค่าโครงการ

หากต้องการเริ่มใช้ UI Automator API ที่ทันสมัย ให้อัปเดตไฟล์ build.gradle.kts ของโปรเจ็กต์ให้มี Dependency ล่าสุดดังนี้

Kotlin

dependencies {
  ...
  androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}

ดึงดูด

dependencies {
  ...
  androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}

แนวคิดหลักของ API

ส่วนต่อไปนี้อธิบายแนวคิดหลักของ UI Automator API ที่ทันสมัย

ขอบเขตการทดสอบ uiAutomator

เข้าถึง UI Automator API ใหม่ทั้งหมดภายในบล็อก uiAutomator { ... } ฟังก์ชันนี้จะสร้าง UiAutomatorTestScope ที่มีสภาพแวดล้อมที่กระชับและปลอดภัยสำหรับประเภทการดำเนินการทดสอบ

uiAutomator {
  // All your UI Automator actions go here
  startApp("com.example.targetapp")
  onElement { textAsString() == "Hello, World!" }.click()
}

ค้นหาองค์ประกอบ UI

ใช้ UI Automator API กับเพรดิเคตเพื่อค้นหาองค์ประกอบ 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 exists
    
  • onElements { 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()

จัดการสถานะของแอปและตัวตรวจสอบ

จัดการวงจรของแอปและจัดการองค์ประกอบ 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 ที่ไม่คาดคิด

API watchFor ช่วยให้คุณกำหนดแฮนเดิลสำหรับองค์ประกอบ UI ที่ไม่คาดคิด เช่น กล่องโต้ตอบสิทธิ์ ซึ่งอาจปรากฏขึ้นระหว่างขั้นตอนการทดสอบ API นี้ใช้กลไกตัวตรวจสอบภายใน แต่มีความยืดหยุ่นมากกว่า

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")

ใช้ API waitForStable() เพื่อยืนยันว่า 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()

ใช้ UI Automator สำหรับ Macrobenchmark และโปรไฟล์พื้นฐาน

ใช้ UI Automator สำหรับการทดสอบประสิทธิภาพด้วย Jetpack Macrobenchmark และสำหรับการสร้าง โปรไฟล์พื้นฐาน เนื่องจาก UI Automator เป็นวิธีที่เชื่อถือได้ในการ โต้ตอบกับแอปและวัดประสิทธิภาพจากมุมมองของผู้ใช้

Macrobenchmark ใช้ UI Automator API เพื่อขับเคลื่อน UI และวัดการโต้ตอบ ตัวอย่างเช่น ในการทดสอบประสิทธิภาพการเริ่มต้น คุณสามารถใช้ onElement เพื่อตรวจหาเวลาที่เนื้อหา UI โหลดเสร็จสมบูรณ์ ซึ่งช่วยให้คุณวัด เวลาที่ใช้ในการแสดงผลแบบเต็ม (TTFD) ได้ ในการทดสอบประสิทธิภาพการกระตุก UI Automator API จะใช้เพื่อเลื่อนรายการหรือเรียกใช้ภาพเคลื่อนไหวเพื่อวัดเวลาเฟรม ฟังก์ชันต่างๆ เช่น startActivity() หรือ startIntent() มีประโยชน์ในการทำให้แอปอยู่ในสถานะที่ถูกต้องก่อนที่จะเริ่มการวัด

เมื่อสร้างโปรไฟล์พื้นฐาน คุณจะทำให้เส้นทางของผู้ใช้ที่สำคัญ (CUJ) ของแอปเป็นแบบอัตโนมัติเพื่อบันทึกคลาสและเมธอดที่ต้องมีการคอมไพล์ล่วงหน้า UI Automator เป็นเครื่องมือที่เหมาะสำหรับการเขียนสคริปต์การทำงานอัตโนมัติเหล่านี้ การค้นหาองค์ประกอบตามเพรดิเคตและกลไกการรอในตัว (onElement) ของ DSL ที่ทันสมัยทำให้การดำเนินการทดสอบมีประสิทธิภาพและกำหนดได้มากขึ้นเมื่อเทียบกับวิธีอื่นๆ ความเสถียรนี้ช่วยลดความไม่แน่นอนและช่วยให้โปรไฟล์พื้นฐานที่สร้างขึ้นแสดงถึงเส้นทางโค้ดที่ดำเนินการระหว่างโฟลว์ของผู้ใช้ที่สำคัญที่สุดได้อย่างถูกต้อง

ฟีเจอร์ขั้นสูง

ฟีเจอร์ต่อไปนี้มีประโยชน์สำหรับสถานการณ์การทดสอบที่ซับซ้อนมากขึ้น

โต้ตอบกับหลายหน้าต่าง

UI Automator API ช่วยให้คุณโต้ตอบและตรวจสอบองค์ประกอบ 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 สำหรับ Bitmap ช่วยลดความซับซ้อนในการบันทึกรูปภาพที่จับภาพไปยังเส้นทางที่ระบุ

ใช้ 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)