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

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

مقدمه ای بر تست مدرن UI Automator

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

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

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

  • یک محدوده آزمایشی اختصاصی uiAutomator برای کد تست تمیزتر و گویاتر.
  • روش‌هایی مانند onElement ، onElements و onElementOrNull برای یافتن عناصر UI با محمول‌های واضح.
  • مکانیزم انتظار داخلی برای عناصر شرطی 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 را پیدا کنید

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

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

    // 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 وجود دارد:

  • Chain 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()

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

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

مدیریت چرخه عمر اپلیکیشن

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

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

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 استفاده کنید تا مطمئن شوید که رابط کاربری برنامه قبل از تعامل با آن پایدار است.

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

ویژگی های پیشرفته

ویژگی های زیر برای سناریوهای آزمایش پیچیده تر مفید هستند.

تعامل با چندین ویندوز

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 { 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 برای 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 { 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)