كتابة الاختبارات المبرمجة باستخدام واجهة المستخدم التلقائية

يوفّر إطار عمل اختبار UI Automator مجموعة من واجهات برمجة التطبيقات لإنشاء اختبارات واجهة مستخدم تتفاعل مع تطبيقات المستخدم وتطبيقات النظام.

مقدمة عن اختبار UI Automator الحديث

يقدّم UI Automator 2.4 لغة خاصة بالنطاق (DSL) مبسطة وسهلة الاستخدام مع Kotlin، ما يسهّل كتابة اختبارات واجهة المستخدم لتطبيق Android. تركز واجهة برمجة التطبيقات الجديدة هذه على العثور على العناصر المستندة إلى المسندات والتحكّم الصريح في حالات التطبيق. يمكنك استخدامها لإنشاء اختبارات مبرمَجة أكثر سهولة في الصيانة وموثوقية.

يتيح لك UI Automator اختبار تطبيق من خارج عملية التطبيق. يتيح لك ذلك اختبار إصدارات الإصدار مع تطبيق التصغير. يساعد UI Automator أيضًا في كتابة اختبارات قياس الأداء على نطاق واسع.

تشمل الميزات الرئيسية للطريقة الحديثة ما يلي:

  • نطاق اختبار uiAutomator مخصّص لرمز اختبار أكثر وضوحًا وتعبيراً
  • طُرق مثل onElement وonElements وonElementOrNull للعثور على عناصر واجهة المستخدم باستخدام مسندات واضحة
  • آلية انتظار مضمّنة للعناصر الشرطية onElement*(timeoutMs: Long = 10000)
  • إدارة حالة التطبيق بشكل صريح، مثل waitForStable وwaitForAppToBeVisible
  • تفاعل مباشر مع عُقد نافذة تسهيل الاستخدام لسيناريوهات اختبار النوافذ المتعددة
  • إمكانات مضمّنة لأخذ لقطات الشاشة وResultsReporter للاختبار المرئي وتصحيح الأخطاء

إعداد المشروع

لبدء استخدام واجهات برمجة التطبيقات الحديثة في UI Automator، عدِّل ملف مشروعك build.gradle.kts لتضمين أحدث اعتمادية:

Kotlin

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

أنيق

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

المفاهيم الأساسية لواجهة برمجة التطبيقات

توضّح الأقسام التالية المفاهيم الأساسية لواجهة برمجة التطبيقات الحديثة في UI Automator.

نطاق اختبار uiAutomator

يمكنك الوصول إلى جميع واجهات برمجة التطبيقات الجديدة في 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 }: يعرض أول عنصر في واجهة المستخدم يطابق المسند خلال مهلة تلقائية. تطرح الدالة استثناءً إذا لم تعثر على عنصر مطابق.

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

التعامل مع حالات التطبيق والمراقِبين

يمكنك إدارة مراحل نشاط تطبيقك والتعامل مع عناصر واجهة المستخدم غير المتوقّعة التي قد تظهر أثناء الاختبارات.

إدارة مراحل نشاط التطبيق

توفّر واجهات برمجة التطبيقات طرقًا للتحكّم في حالة التطبيق قيد الاختبار:

// 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 تحديد معالِجات لعناصر واجهة المستخدم غير المتوقّعة، مثل مربّعات حوار الأذونات، التي قد تظهر أثناء مسار الاختبار. تستخدم هذه الواجهة آلية المراقبة الداخلية، ولكنها توفّر مرونة أكبر.

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 عدة واجهات برمجة تطبيقات للمساعدة في ذلك.

تنتظر الدالة waitForAppToBeVisible("com.example.targetapp") إلى أن يظهر على الشاشة عنصر في واجهة المستخدم يحمل اسم الحزمة المحدّد خلال مهلة قابلة للتخصيص.

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

استخدِم واجهة برمجة التطبيقات 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()

استخدام UI Automator لقياس الأداء على نطاق واسع وإنشاء الملفات الشخصية الأساسية

يمكنك استخدام UI Automator لاختبار الأداء باستخدام Jetpack Macrobenchmark ولإنشاء الملفات الشخصية الأساسية، لأنّه يوفّر طريقة موثوقة للتفاعل مع تطبيقك وقياس الأداء من منظور المستخدم النهائي.

يستخدم Macrobenchmark واجهات برمجة التطبيقات في UI Automator لتشغيل واجهة المستخدم وقياس التفاعلات. على سبيل المثال، في قياس الأداء عند بدء التشغيل، يمكنك استخدام onElement لرصد وقت تحميل محتوى واجهة المستخدم بالكامل، ما يتيح لك قياس الوقت اللازم للعرض الكامل (TTFD). في إيقاف مؤقت لعرض واجهة المستخدم في قياس الأداء، يتم استخدام واجهات برمجة التطبيقات في UI Automator للتنقّل في القوائم أو تشغيل الصور المتحركة لقياس وقت عرض اللقطة. تكون الدوال مثل startActivity() أو startIntent() مفيدة لوضع التطبيق في الحالة الصحيحة قبل بدء القياس.

عند إنشاء الملفات الشخصية الأساسية، يمكنك برمجة رحلات المستخدمين الأساسية في تطبيقك لتسجيل الفئات والطُرق التي تتطلب التجميع المسبق. يُعدّ UI Automator أداة مثالية لكتابة نصوص البرمجة المبرمَجة هذه. يؤدي العثور على العناصر المستندة إلى المسندات وآليات الانتظار المضمّنة (onElement) في لغة DSL الحديثة إلى تنفيذ اختبار تجريبي أكثر قوةً وتحديدًا مقارنةً بالطُرق الأخرى. يقلّل هذا الاستقرار من عدم الاتساق ويضمن أنّ الملف الشخصي للمرجع الذي تم إنشاؤه يعكس بدقة مسارات الرموز البرمجية التي تم تنفيذها أثناء أهم مسارات المستخدم.

الميزات المتقدمة

تكون الميزات التالية مفيدة لسيناريوهات الاختبار الأكثر تعقيدًا.

التفاعل مع نوافذ متعددة

تتيح لك واجهات برمجة التطبيقات في UI Automator التفاعل مباشرةً مع عناصر واجهة المستخدم وفحصها. يكون ذلك مفيدًا بشكل خاص في السيناريوهات التي تتضمّن نوافذ متعددة، مثل وضع "صورة داخل صورة" أو تنسيقات الشاشة المقسّمة.

// 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 في ربط عناصر الاختبار، مثل لقطات الشاشة، مباشرةً بنتائج الاختبار في استوديو 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 الأقدم

إذا كانت لديك اختبارات حالية في UI Automator مكتوبة باستخدام واجهات برمجة تطبيقات أقدم، استخدِم الجدول التالي كمرجع للترحيل إلى الطريقة الحديثة:

نوع الإجراء طريقة 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() ننصح باستخدام آلية المهلة المضمّنة في onElement، وإلا استخدِم activeWindow().waitForStable()
العثور على العناصر الثانوية طلبات findObject المتداخلة يدويًا ربط onElement().onElement()
التعامل مع مربّعات حوار الأذونات UiAutomator.registerWatcher() watchFor(PermissionDialog)