قوائم الإسبريسو

توفر Espresso آليات للتمرير إلى عنصر معين أو اتخاذ إجراء بشأنه لنوعين من القوائم: طريقة عرض المحول وعروض إعادة التدوير.

عند التعامل مع القوائم، لا سيّما تلك التي تم إنشاؤها باستخدام عنصر RecyclerView أو AdapterView، قد لا تظهر طريقة العرض التي تهمّك حتى على الشاشة، لأنّه يتم عرض عدد قليل فقط من الأطفال وإعادة تدويرها أثناء التنقّل. لا يمكن استخدام طريقة scrollTo() في هذه الحالة لأنها تتطلّب طريقة عرض حالية.

التفاعل مع عناصر قائمة عرض المحوّل

بدلاً من استخدام طريقة onView()، ابدأ البحث باستخدام onData()، وقدِّم مطابقة مع البيانات التي تدعم الملف الشخصي الذي تريد مطابقته. ينفّذ قهوة Espresso كل مهام العثور على الصف في العنصر Adapter ويجعل العنصر مرئيًا في إطار العرض.

مطابقة البيانات باستخدام أداة مطابقة الملفات الشخصية المخصّصة

يحتوي النشاط أدناه على ListView، ويتم دعمه بواسطة SimpleAdapter التي تحتوي على بيانات لكل صف في كائن Map<String, Object>.

إنّ نشاط القائمة المعروض حاليًا على الشاشة يتضمّن قائمة
          23 عنصرًا. يحتوي كل عنصر على رقم مخزَّن كسلسلة، ويتم ربطه برقم مختلف ويتم تخزينه ككائن بدلاً من ذلك.

تحتوي كل خريطة على إدخالَين: المفتاح "STR" الذي يتضمّن سلسلة، مثل "item: x"، والمفتاح "LEN" الذي يتضمّن Integer الذي يمثّل طول المحتوى. مثلاً:

{"STR" : "item: 0", "LEN": 7}

يظهر رمز النقرة على الصف الذي يحتوي على "العنصر: 50" على النحو التالي:

Kotlin

onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo("STR"),
        `is`("item: 50")))).perform(click())

Java

onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50"))))
    .perform(click());

تجدر الإشارة إلى أنّ قهوة الإسبريسو تتنقّل في القائمة تلقائيًا حسب الحاجة.

لنفكك Matcher<Object> من داخل onData(). تساعد الطريقة is(instanceOf(Map.class)) في تضييق نطاق البحث إلى أي عنصر من AdapterView، المستند إلى كائن Map.

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

Kotlin

hasEntry(equalTo("STR"), `is`("item: 50"))

Java

hasEntry(equalTo("STR"), is("item: 50"))

سيطابق Matcher<String, Object> هذا أي خريطة تحتوي على إدخال بالمفتاح "STR" والقيمة "item: 50". بما أنّ الرمز المطلوب للبحث عن هذا الجزء طويل ونريد استخدامه في مواقع أخرى، لنكتب مُطابقًا withItemContent مخصّصًا لذلك:

Kotlin

return object : BoundedMatcher<Object, Map>(Map::class.java) {
    override fun matchesSafely(map: Map): Boolean {
        return hasEntry(equalTo("STR"), itemTextMatcher).matches(map)
    }

    override fun describeTo(description: Description) {
        description.appendText("with item content: ")
        itemTextMatcher.describeTo(description)
    }
}

Java

return new BoundedMatcher<Object, Map>(Map.class) {
    @Override
    public boolean matchesSafely(Map map) {
        return hasEntry(equalTo("STR"), itemTextMatcher).matches(map);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("with item content: ");
        itemTextMatcher.describeTo(description);
    }
};

يتم استخدام BoundedMatcher كقاعدة لمطابقة الكائنات من نوع Map فقط. تجاهُل طريقة matchesSafely()، ووضع أداة المطابقة التي تم العثور عليها سابقًا، ومطابقتها مع Matcher<String> التي يمكنك تمريرها كوسيطة هذا يسمح لك بالاتصال بـ withItemContent(equalTo("foo")). لاختصار الرمز، يمكنك إنشاء أداة مطابقة أخرى تستدعي equalTo() وتقبل كائن String:

Kotlin

fun withItemContent(expectedText: String): Matcher<Object> {
    checkNotNull(expectedText)
    return withItemContent(equalTo(expectedText))
}

Java

public static Matcher<Object> withItemContent(String expectedText) {
    checkNotNull(expectedText);
    return withItemContent(equalTo(expectedText));
}

أصبحت الآن التعليمة البرمجية للنقر على العنصر بسيطة:

Kotlin

onData(withItemContent("item: 50")).perform(click())

Java

onData(withItemContent("item: 50")).perform(click());

للاطّلاع على الرمز الكامل لهذا الاختبار، يُرجى الاطّلاع على طريقة testClickOnItem50() ضمن فئة AdapterViewTest وأداة المطابقة LongListMatchers المخصّصة هذه على GitHub.

مطابقة عرض طفل محدّد

يُصدر النموذج أعلاه نقرة في منتصف الصفّ الكامل لسمة ListView. لكن ماذا لو أردنا العمل على عنصر ثانوي معين من الصف؟ على سبيل المثال، نود النقر على العمود الثاني من الصف LongListActivity، والذي يعرض String.length للمحتوى في العمود الأول:

في هذا المثال، سيكون من المفيد استخراج طول جزء معيّن فقط من المحتوى. وتتضمّن هذه العملية تحديد قيمة العمود الثاني على التوالي.

ما عليك سوى إضافة مواصفات onChildView() إلى عملية تنفيذ DataInteraction:

Kotlin

onData(withItemContent("item: 60"))
    .onChildView(withId(R.id.item_size))
    .perform(click())

Java

onData(withItemContent("item: 60"))
    .onChildView(withId(R.id.item_size))
    .perform(click());

التفاعل مع عناصر قائمة عرض المستخدمين الذين يعيدون تدويرها

تعمل كائنات RecyclerView بشكل مختلف عن AdapterView، لذلك لا يمكن استخدام onData() للتفاعل معها.

للتفاعل مع RecyclerViews باستخدام Espresso، يمكنك استخدام حزمة espresso-contrib التي تتضمن مجموعة من RecyclerViewActions التي يمكن استخدامها للانتقال إلى المواضع أو لتنفيذ إجراءات على العناصر:

  • scrollTo() - للانتقال إلى "العرض المطابق" إن وجد.
  • scrollToHolder() - للانتقال إلى صاحب الملف الشخصي المطابق، إن وجد.
  • scrollToPosition() - للانتقال إلى موضع معين
  • actionOnHolderItem() - يتم تنفيذ إجراء مشاهدة على أي ملف شخصي مطابق.
  • actionOnItem(): لتنفيذ إجراء مشاهدة على عرض مطابق.
  • actionOnItemAtPosition() - لتنفيذ "إجراء عرض" على ملف شخصي في موضع معيّن.

تعرض المقتطفات التالية بعض الأمثلة من المثال RecyclerViewSample:

Kotlin

@Test(expected = PerformException::class)
fun itemWithText_doesNotExist() {
    // Attempt to scroll to an item that contains the special text.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(
            // scrollTo will fail the test if no item matches.
            RecyclerViewActions.scrollTo(
                hasDescendant(withText("not in the list"))
            )
        )
}

Java

@Test(expected = PerformException.class)
public void itemWithText_doesNotExist() {
    // Attempt to scroll to an item that contains the special text.
    onView(ViewMatchers.withId(R.id.recyclerView))
            // scrollTo will fail the test if no item matches.
            .perform(RecyclerViewActions.scrollTo(
                    hasDescendant(withText("not in the list"))
            ));
}

Kotlin

@Test fun scrollToItemBelowFold_checkItsText() {
    // First, scroll to the position that needs to be matched and click on it.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(
            RecyclerViewActions.actionOnItemAtPosition(
                ITEM_BELOW_THE_FOLD,
                click()
            )
        )

    // Match the text in an item below the fold and check that it's displayed.
    val itemElementText = "${activityRule.activity.resources
        .getString(R.string.item_element_text)} ${ITEM_BELOW_THE_FOLD.toString()}"
    onView(withText(itemElementText)).check(matches(isDisplayed()))
}

Java

@Test
public void scrollToItemBelowFold_checkItsText() {
    // First, scroll to the position that needs to be matched and click on it.
    onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.actionOnItemAtPosition(ITEM_BELOW_THE_FOLD,
            click()));

    // Match the text in an item below the fold and check that it's displayed.
    String itemElementText = activityRule.getActivity().getResources()
            .getString(R.string.item_element_text)
            + String.valueOf(ITEM_BELOW_THE_FOLD);
    onView(withText(itemElementText)).check(matches(isDisplayed()));
}

Kotlin

@Test fun itemInMiddleOfList_hasSpecialText() {
    // First, scroll to the view holder using the isInTheMiddle() matcher.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()))

    // Check that the item has the special text.
    val middleElementText = activityRule.activity.resources
            .getString(R.string.middle)
    onView(withText(middleElementText)).check(matches(isDisplayed()))
}

Java

@Test
public void itemInMiddleOfList_hasSpecialText() {
    // First, scroll to the view holder using the isInTheMiddle() matcher.
    onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()));

    // Check that the item has the special text.
    String middleElementText =
            activityRule.getActivity().getResources()
            .getString(R.string.middle);
    onView(withText(middleElementText)).check(matches(isDisplayed()));
}

مراجع إضافية

لمزيد من المعلومات حول استخدام قوائم Espresso في اختبارات Android، يُرجى الرجوع إلى الموارد التالية.

عيّنات

  • DataAdapterعيّن: يعرض نقطة دخول onData() لـ Espresso للقوائم والكائنات AdapterView.