تست های خودکار را با UI Automator بنویسید

چارچوب تست UI Automator مجموعه‌ای از APIها را برای ساخت تست‌های UI که با برنامه‌های کاربر و برنامه‌های سیستمی تعامل دارند، فراهم می‌کند.

مقدمه‌ای بر تست خودکار رابط کاربری مدرن

UI Automator 2.4 یک زبان خاص دامنه (DSL) ساده و سازگار با کاتلین را معرفی می‌کند که نوشتن تست‌های رابط کاربری برای اندروید را ساده می‌کند. این سطح API جدید بر یافتن عناصر مبتنی بر گزاره و کنترل صریح بر حالت‌های برنامه تمرکز دارد. از آن برای ایجاد تست‌های خودکار قابل نگهداری‌تر و قابل اعتمادتر استفاده کنید.

UI Automator به شما امکان می‌دهد یک برنامه را از خارج از فرآیند برنامه آزمایش کنید. این به شما امکان می‌دهد نسخه‌های منتشر شده را با اعمال فشرده‌سازی آزمایش کنید. UI Automator همچنین هنگام نوشتن تست‌های ماکروبنچمارک کمک می‌کند.

ویژگی‌های کلیدی رویکرد مدرن عبارتند از:

  • یک محدوده تست اختصاصی uiAutomator برای کد تست تمیزتر و رساتر.
  • متدهایی مانند onElement ، onElements و onElementOrNull برای یافتن عناصر رابط کاربری با گزاره‌های واضح.
  • مکانیزم انتظار داخلی برای عناصر شرطی onElement*(timeoutMs: Long = 10000)
  • مدیریت صریح وضعیت برنامه مانند waitForStable و waitForAppToBeVisible .
  • تعامل مستقیم با گره‌های پنجره دسترسی برای سناریوهای تست چند پنجره‌ای.
  • قابلیت‌های داخلی اسکرین‌شات و یک ResultsReporter برای تست بصری و اشکال‌زدایی.

پروژه خود را تنظیم کنید

برای شروع استفاده از API های مدرن UI Automator، فایل build.gradle.kts پروژه خود را به‌روزرسانی کنید تا آخرین وابستگی را شامل شود:

کاتلین

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

گرووی

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

مفاهیم اصلی API

بخش‌های زیر مفاهیم اصلی رابط برنامه‌نویسی کاربردی (API) مدرن UI Automator را شرح می‌دهند.

دامنه تست uiAutomator

به تمام APIهای جدید UI Automator در بلوک uiAutomator { ... } دسترسی پیدا کنید. این تابع یک UiAutomatorTestScope ایجاد می‌کند که محیطی مختصر و ایمن از نظر نوع داده را برای عملیات تست شما فراهم می‌کند.

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

یافتن عناصر رابط کاربری

از رابط‌های برنامه‌نویسی کاربردی UI Automator به همراه گزاره‌ها برای مکان‌یابی عناصر رابط کاربری استفاده کنید. این گزاره‌ها به شما امکان می‌دهند شرایطی را برای ویژگی‌هایی مانند متن، حالت انتخاب‌شده یا متمرکز و توضیحات محتوا تعریف کنید.

  • onElement { predicate } : اولین عنصر رابط کاربری که با predicate در یک timeout پیش‌فرض مطابقت دارد را برمی‌گرداند. اگر تابع عنصر منطبق را پیدا نکند، یک استثنا ایجاد می‌کند.

    // 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 } : منتظر می‌ماند تا حداقل یک عنصر رابط کاربری با گزاره داده شده مطابقت داشته باشد، سپس لیستی از تمام عناصر رابط کاربری منطبق را برمی‌گرداند.

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

تعامل با عناصر رابط کاربری

با شبیه‌سازی کلیک‌ها یا تنظیم متن در فیلدهای قابل ویرایش، با عناصر رابط کاربری تعامل داشته باشید.

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

مدیریت وضعیت‌ها و ناظران برنامه

چرخه عمر برنامه خود را مدیریت کنید و عناصر رابط کاربری غیرمنتظره‌ای را که ممکن است در طول تست‌های شما ظاهر شوند، مدیریت کنید.

مدیریت چرخه عمر برنامه

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

مدیریت رابط کاربری غیرمنتظره

API watchFor به شما امکان می‌دهد تا برای عناصر رابط کاربری غیرمنتظره، مانند دیالوگ‌های مجوز، که ممکن است در طول جریان تست شما ظاهر شوند، کنترل‌کننده‌هایی تعریف کنید. این API از مکانیزم داخلی watcher استفاده می‌کند اما انعطاف‌پذیری بیشتری را ارائه می‌دهد.

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 شیء‌ای است که به عنوان یک scope به بلوک watchFor ارسال می‌شود. می‌توانید بر اساس این الگو watcherهای سفارشی ایجاد کنید.

منتظر قابلیت مشاهده و پایداری برنامه باشید

گاهی اوقات تست‌ها باید منتظر بمانند تا عناصر قابل مشاهده یا پایدار شوند. UI Automator چندین API برای کمک به این امر ارائه می‌دهد.

تابع waitForAppToBeVisible("com.example.targetapp") ‎ منتظر می‌ماند تا یک عنصر رابط کاربری با نام بسته‌ی داده شده، در یک بازه زمانی قابل تنظیم، روی صفحه نمایش داده شود.

// Wait for the app to be visible after launching it
startApp("com.example.targetapp")
waitForAppToBeVisible("com.example.targetapp")

قبل از تعامل با برنامه، از API waitForStable() برای تأیید پایدار بودن رابط کاربری برنامه استفاده کنید.

// 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) به شما امکان می‌دهند مستقیماً با عناصر رابط کاربری تعامل داشته باشید و آنها را بررسی کنید. این امر به ویژه برای سناریوهایی که شامل چندین پنجره هستند، مانند حالت تصویر در تصویر (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()

اسکرین‌شات‌ها و ادعاهای بصری

از کل صفحه، پنجره‌های خاص یا عناصر رابط کاربری به صورت مستقیم در تست‌های خود اسکرین‌شات بگیرید. این کار برای تست رگرسیون بصری و اشکال‌زدایی مفید است.

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 به شما کمک می‌کند تا مصنوعات تست، مانند اسکرین‌شات‌ها، را مستقیماً با نتایج تست خود در اندروید استودیو مرتبط کنید تا بررسی و اشکال‌زدایی آسان‌تر شود.

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 { ... } قرار دهید.
یافتن عناصر رابط کاربری device.findObject(By.res("com.example.app:id/my_button")) onElement { viewIdResourceName == "my\_button" }
یافتن عناصر رابط کاربری device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
منتظر رابط کاربری غیرفعال باشید device.waitForIdle() مکانیزم timeout داخلی onElement را ترجیح دهید؛ در غیر این صورت، activeWindow().waitForStable()
یافتن عناصر فرزند فراخوانی‌های تو در تو findObject به صورت دستی زنجیره‌سازی onElement().onElement()
مدیریت پنجره‌های محاوره‌ای مجوز UiAutomator.registerWatcher() watchFor(PermissionDialog)