Використовуючи Espresso, натисніть на перегляд всередині елемента RecyclerView


100

Як я можу використовувати Espresso для натискання певного виду всередині елемента RecyclerView ? Я знаю, що можу натиснути елемент у позиції 0 за допомогою:

onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));

Але мені потрібно натиснути на конкретний вигляд всередині цього елемента, а не на сам предмет.

Заздалегідь спасибі.

- редагувати -

Якщо точніше: у мене є RecyclerView ( R.id.recycler_view), які елементи є CardView ( R.id.card_view). Всередині кожного CardView у мене є чотири кнопки (серед іншого), і я хочу натиснути певну кнопку ( R.id.bt_deliver).

Я хотів би використовувати нові функції Espresso 2.0, але не впевнений, що це можливо.

Якщо це неможливо, я хочу використовувати щось подібне (використовуючи код Томаса Келлера):

onRecyclerItemView(R.id.card_view, ???, withId(R.id.bt_deliver)).perform(click());

але я не знаю, що поставити на питаннях.


Перевірте це
Skizo-ozᴉʞS

1
Привіт, танки для швидкої відповіді! :-) Я вже бачив це запитання, але не можу знайти жодної допомоги щодо використання RecyclerViewActions, щоб робити те, що я хочу. Чи варто використовувати старий відповідь ? Дякую.
Філіпе Рамос

Ви знайшли рішення для своєї проблеми?
HowieH

1
@HowieH: Ні. Я відмовився, і зараз я використовую Robotium ...
Filipe Ramos

Відповіді:


136

Ви можете зробити це за допомогою налаштування дії перегляду.

public class MyViewAction {

    public static ViewAction clickChildViewWithId(final int id) {
        return new ViewAction() {
            @Override
            public Matcher<View> getConstraints() {
                return null;
            }

            @Override
            public String getDescription() {
                return "Click on a child view with specified id.";
            }

            @Override
            public void perform(UiController uiController, View view) {
                View v = view.findViewById(id);
                v.performClick();
            }
        };
    }

}

Потім ви можете натиснути його

onView(withId(R.id.rv_conference_list)).perform(
            RecyclerViewActions.actionOnItemAtPosition(0, MyViewAction.clickChildViewWithId(R.id. bt_deliver)));

2
Я б зняв нульову перевірку, так що якщо не знайдено дитячого перегляду, викидається, а не робити нічого мовчки.
Альваро Гутьєррес Перрес

Дякую за відповідь. Але вразила одна проблема. У функції onPerform (), якщо я використовував функцію onView (withId ()), то ця функція буде вражена. У чому причина такої поведінки?
theparkpassenger

3
працює з еспресо: espresso-contrib: 2.2.2, але не з 2.2.1 будь-яких причин? У мене є деякі залежності, через які я не можу використовувати 2.2.2.
RosAng

3
Я отримував NullPointerException з цим спочатку, тому мені довелося реалізувати getConstraints (), щоб уникнути цього. Для мене працювало наступне: public Matcher <View> getConstraints () {return isAssignableFrom (View.class); }
dfinn

Наведене вище рішення не працює для мене. Я отримую таку помилку: android.support.test.espresso.PerformException: Помилка виконання 'android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemAtPositionViewAction@fad9df' на перегляд 'з id: adamhurwitz.github.io.doordashlite : id / recilerView '.
Адам Гурвіц

67

Тепер з android.support.test.espresso.contrib стало легше:

1) Додайте тестову залежність

androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.0') {
    exclude group: 'com.android.support', module: 'appcompat'
    exclude group: 'com.android.support', module: 'support-v4'
    exclude module: 'recyclerview-v7'
}

* виключіть 3 модулі, тому що, швидше за все, у вас вже є

2) Тоді роби щось подібне

onView(withId(R.id.recycler_grid))
            .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));

Або

onView(withId(R.id.recyclerView))
  .perform(RecyclerViewActions.actionOnItem(
            hasDescendant(withText("whatever")), click()));

Або

onView(withId(R.id.recycler_linear))
            .check(matches(hasDescendant(withText("whatever"))));

1
Дякую, не виключаючи цих модулів, це призводить до java.lang.IncompatibleClassChangeError для мене.
hboy

8
і що робити, щоб натиснути, скажімо, у п'ятому пункті списку конкретний вигляд? у наведених прикладах я бачу лише, як натиснути 5-й елемент або клацнути та вилучити елемент із поданням нащадка, але не обидва разом, чи я щось пропустив?
donfuxx

1
мені довелося зробитиexclude group: 'com.android.support' exclude module: 'recyclerview-v7'
Джемшит Іскендеров

1
Марно, коли ви хочете натиснути прапорець всередині RecyclerView.
Фарид

2
У котліні actionOnItemAtPositionпотрібен парам типу. Не впевнений, що туди поставити.
ZeroDivide

7

Ось, як я вирішив проблему в kotlin:

fun clickOnViewChild(viewId: Int) = object : ViewAction {
    override fun getConstraints() = null

    override fun getDescription() = "Click on a child view with specified id."

    override fun perform(uiController: UiController, view: View) = click().perform(uiController, view.findViewById<View>(viewId))
}

і потім

onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(position, clickOnViewChild(R.id.viewToClickInTheRow)))

Я зіткнувся з E / TestRunner: java.lang.NullPointerException: спроба викликати віртуальний метод 'void android.view.View.getLocationOnScreen (int [])' в нульовій посилання на об'єкт; будь-яка ідея чому?
сайвортана

6

Спробуйте наступний підхід:

    onView(withRecyclerView(R.id.recyclerView)
                    .atPositionOnView(position, R.id.bt_deliver))
                    .perform(click());

    public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
            return new RecyclerViewMatcher(recyclerViewId);
    }

public class RecyclerViewMatcher {
    final int mRecyclerViewId;

    public RecyclerViewMatcher(int recyclerViewId) {
        this.mRecyclerViewId = recyclerViewId;
    }

    public Matcher<View> atPosition(final int position) {
        return atPositionOnView(position, -1);
    }

    public Matcher<View> atPositionOnView(final int position, final int targetViewId) {

        return new TypeSafeMatcher<View>() {
            Resources resources = null;
            View childView;

            public void describeTo(Description description) {
                int id = targetViewId == -1 ? mRecyclerViewId : targetViewId;
                String idDescription = Integer.toString(id);
                if (this.resources != null) {
                    try {
                        idDescription = this.resources.getResourceName(id);
                    } catch (Resources.NotFoundException var4) {
                        idDescription = String.format("%s (resource name not found)", id);
                    }
                }

                description.appendText("with id: " + idDescription);
            }

            public boolean matchesSafely(View view) {

                this.resources = view.getResources();

                if (childView == null) {
                    RecyclerView recyclerView =
                            (RecyclerView) view.getRootView().findViewById(mRecyclerViewId);
                    if (recyclerView != null) {

                        childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
                    }
                    else {
                        return false;
                    }
                }

                if (targetViewId == -1) {
                    return view == childView;
                } else {
                    View targetView = childView.findViewById(targetViewId);
                    return view == targetView;
                }

            }
        };
    }
}

3

Ви можете натиснути на 3 - й пункт з , recyclerViewяк це:

onView(withId(R.id.recyclerView)).perform(
                RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(2,click()))

Не забудьте вказати ViewHolderтип, щоб висновок не провалювався.


1
Я пропустив <RecyclerView.ViewHolder>спасибі, сер;)
Skizo-ozᴉʞS

0

Усі відповіді, описані вище, не працювали для мене, тому я створив новий метод, який здійснює пошук у всіх видах усередині клітини, щоб повернути представлення даних із запитуваним ідентифікатором. Для цього потрібні два методи (можна об'єднати в один):

fun performClickOnViewInCell(viewID: Int) = object : ViewAction {
    override fun getConstraints(): org.hamcrest.Matcher<View> = click().constraints
    override fun getDescription() = "Click on a child view with specified id."
    override fun perform(uiController: UiController, view: View) {
        val allChildViews = getAllChildrenBFS(view)
        for (child in allChildViews) {
            if (child.id == viewID) {
                child.callOnClick()
            }
        }
    }
}


private fun  getAllChildrenBFS(v: View): List<View> {
    val visited = ArrayList<View>();
    val unvisited = ArrayList<View>();
    unvisited.add(v);

    while (!unvisited.isEmpty()) {
        val child = unvisited.removeAt(0);
        visited.add(child);
        if (!(child is ViewGroup)) continue;
        val group = child
        val childCount = group.getChildCount();
        for (i in 0 until childCount) { unvisited.add(group.getChildAt(i)) }
    }

    return visited;
}

Тоді остаточним ви можете скористатись цим у програмі Recycler View, виконавши наступне:

onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition<RecyclerView.ViewHolder>(0, getViewFromCell(R.id.cellInnerView) {
            val requestedView = it
}))

Ви можете використовувати зворотний виклик, щоб повернути вигляд, якщо ви хочете зробити щось інше з ним, або просто створити 3-4 різні версії цього, щоб виконати будь-які інші завдання.


0

Я постійно випробовував різні методи, щоб знайти, чому відповідь @ blade не працює для мене, щоб тільки зрозуміти, що у мене є OnTouchListener(), я відповідно змінив ViewAction:

fun clickTopicToWeb(id: Int): ViewAction {

        return object : ViewAction {
            override fun getDescription(): String {...}

            override fun getConstraints(): Matcher<View> {...}

            override fun perform(uiController: UiController?, view: View?) {

                view?.findViewById<View>(id)?.apply {

                    //Generalized for OnClickListeners as well
                    if(isEnabled && isClickable && !performClick()) {
                        //Define click event
                        val event: MotionEvent = MotionEvent.obtain(
                          SystemClock.uptimeMillis(),
                          SystemClock.uptimeMillis(),
                          MotionEvent.ACTION_UP,
                          view.x,
                          view.y,
                          0)

                        if(!dispatchTouchEvent(event))
                            throw Exception("Not clicking!")
                    }
                }
            }
        }
    }

0

Ви навіть можете узагальнити такий підхід, щоб підтримувати більше дій click. Ось моє рішення для цього:

fun <T : View> recyclerChildAction(@IdRes id: Int, block: T.() -> Unit): ViewAction {
  return object : ViewAction {
    override fun getConstraints(): Matcher<View> {
      return any(View::class.java)
    }

    override fun getDescription(): String {
      return "Performing action on RecyclerView child item"
    }

    override fun perform(
        uiController: UiController,
        view: View
    ) {
      view.findViewById<T>(id).block()
    }
  }
 
}

І тоді для EditTextвас можна зробити щось подібне:

onView(withId(R.id.yourRecyclerView))
        .perform(
            actionOnItemAtPosition<YourViewHolder>(
                0,
                recyclerChildAction<EditText>(R.id.editTextId) { setText("1000") }
            )
        )

-5

Спочатку дайте своїм кнопкам унікальний опис вмісту, тобто "рядок кнопки доставки 5".

<button android:contentDescription=".." />

Потім перейдіть до рядка:

onView(withId(R.id.recycler_view)).perform(RecyclerViewActions.scrollToPosition(5));

Потім виберіть подання на основі contentDescription.

onView(withContentDescription("delivery button row 5")).perform(click());

Опис вмісту - це чудовий спосіб використання функції перегляду Espresso та зробити ваш додаток більш доступним.


5
Опис вмісту не слід використовувати таким чином - це не робить ваш додаток більш доступним для переглядів, щоб видати технічну інформацію або інформацію про налагодження для користувачів з потребами a11y - компакт-диск для цього, ймовірно, повинен бути чимось на кшталт "Замовлення <резюме товару> кнопка ". withText("Order")також працюватиме і не вимагає, щоб ви в тестовий час знали, що ви замовляєте.
ataulm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.