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

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

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

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

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

تشمل الميزات الرئيسية للأسلوب الحديث ما يلي:

  • نطاق اختبار مخصّص 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")
}

Groovy

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

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

التفاعل مع عناصر واجهة المستخدم

التفاعل مع عناصر واجهة المستخدم من خلال محاكاة النقرات أو ضبط النص في الحقول القابلة للتعديل

// 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 { id == "my_loading_indicator" }.waitForStable()

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

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

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

تتيح لك واجهات برمجة التطبيقات 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 { 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 في ربط عناصر الاختبار، مثل لقطات الشاشة، بنتائج الاختبار مباشرةً في &quot;استوديو Android&quot; لتسهيل الفحص وتحديد الأخطاء وحلّها.

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 { id == "my\_button" }
العثور على عناصر واجهة المستخدم device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
انتظار واجهة المستخدم غير النشطة device.waitForIdle() استخدام آلية المهلة المضمّنة في onElement، وإلا activeWindow().waitForStable()
العثور على العناصر الفرعية المكالمات المتداخلة findObjectيدويًا onElement().onElement() الربط
التعامل مع مربّعات حوار الأذونات UiAutomator.registerWatcher() watchFor(PermissionDialog)