UI Automator를 사용하여 자동화된 테스트 작성

UI Automator 테스트 프레임워크는 사용자 앱과 시스템 앱과 상호작용하는 UI 테스트를 빌드하기 위한 API 세트를 제공합니다.

최신 UI Automator 테스트 소개

UI Automator 2.4에서는 Android용 UI 테스트 작성을 간소화하는 간소화된 Kotlin 친화적인 도메인 특정 언어 (DSL)가 도입되었습니다. 이 새로운 API 노출 영역은 술어 기반 요소 찾기와 앱 상태에 대한 명시적 제어에 중점을 둡니다. 이를 사용하여 유지관리 가능하고 안정적인 자동 테스트를 만드세요.

UI Automator를 사용하면 앱 프로세스 외부에서 앱을 테스트할 수 있습니다. 이렇게 하면 축소 기능이 적용된 출시 버전을 테스트할 수 있습니다. UI Automator는 Macrobenchmark 테스트를 작성할 때도 유용합니다.

최신 접근 방식의 주요 기능은 다음과 같습니다.

  • 더 깔끔하고 표현력이 뛰어난 테스트 코드를 위한 전용 uiAutomator 테스트 범위
  • 명확한 술어로 UI 요소를 찾는 onElement, onElements, onElementOrNull와 같은 메서드
  • 조건부 요소 onElement*(timeoutMs: Long = 10000)의 기본 제공 대기 메커니즘
  • waitForStablewaitForAppToBeVisible과 같은 명시적 앱 상태 관리
  • 다중 창 테스트 시나리오를 위해 접근성 창 노드와 직접 상호작용
  • 시각적 테스트 및 디버깅을 위한 기본 제공 스크린샷 기능과 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 개념

다음 섹션에서는 최신 UI Automator API의 핵심 개념을 설명합니다.

uiAutomator 테스트 범위

uiAutomator { ... } 블록 내에서 모든 새 UI Automator API에 액세스합니다. 이 함수는 테스트 작업을 위한 간결하고 유형 안전한 환경을 제공하는 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 { id == "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 요소가 지정된 술어와 일치할 때까지 기다린 후 일치하는 모든 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 { id == "first" }
      .onElement { id == "second" }
      .onElement { id == "third" }
      .click()
    
  • 밀리초를 나타내는 값을 전달하여 onElement* 함수의 시간 제한을 지정합니다.

    // Find a Ui element with a zero timeout (instant check)
    onElement(0) { id == "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 처리

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()
}

PermissionDialogScopedWatcher<T>의 예시이며, 여기서 TwatchFor의 블록에 범위로 전달된 객체입니다. 이 패턴을 기반으로 맞춤 감시자를 만들 수 있습니다.

앱 공개 상태 및 안정성 대기

테스트에서 요소가 표시되거나 안정화될 때까지 기다려야 하는 경우가 있습니다. 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 { id == "my_loading_indicator" }.waitForStable()

고급 기능

다음 기능은 더 복잡한 테스트 시나리오에 유용합니다.

여러 창과 상호작용

UI Automator API를 사용하면 UI 요소와 직접 상호작용하고 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 { id == "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 스튜디오의 테스트 결과와 직접 연결하여 더 쉽게 검사하고 디버깅할 수 있습니다.

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 버전에서 이전

이전 API 표면으로 작성된 기존 UI Automator 테스트가 있는 경우 다음 표를 참고하여 최신 접근 방식으로 이전하세요.

작업 유형 이전 UI Automator 메서드 새 UI Automator 메서드
진입점 UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) uiAutomator { ... } 범위에서 테스트 로직을 래핑합니다.
UI 요소 찾기 device.findObject(By.res("com.example.app:id/my_button")) onElement { id == "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)