Klicken Sie mit Espresso auf die Ansicht im RecyclerView-Element


100

Wie kann ich mit Espresso auf eine bestimmte Ansicht in einem RecyclerView- Element klicken ? Ich weiß, dass ich auf Position 0 klicken kann, indem ich:

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

Ich muss jedoch auf eine bestimmte Ansicht in diesem Element klicken und nicht auf das Element selbst.

Danke im Voraus.

- bearbeiten -

Genauer gesagt: Ich habe ein RecyclerView ( R.id.recycler_view), dessen Elemente CardView ( R.id.card_view) sind. In jeder CardView habe ich (unter anderem) vier Schaltflächen und möchte auf eine bestimmte Schaltfläche klicken ( R.id.bt_deliver).

Ich möchte die neuen Funktionen von Espresso 2.0 nutzen, bin mir aber nicht sicher, ob dies möglich ist.

Wenn nicht möglich, möchte ich so etwas verwenden (mit Thomas Keller Code):

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

aber ich weiß nicht, was ich auf die Fragezeichen setzen soll.


Überprüfen Sie dies
Skizo-ozᴉʞS

1
Hallo Panzer für die schnelle Antwort! :-) Ich habe diese Frage gesehen, kann aber keine Hilfe finden, wie ich RecyclerViewActions verwenden kann , um das zu tun, was ich will. Soll ich die alte Antwort verwenden ? Vielen Dank.
Filipe Ramos

Haben Sie eine Lösung für Ihr Problem gefunden?
HowieH

1
@ HowieH: Nein. Ich habe aufgegeben und benutze jetzt Robotium ...
Filipe Ramos

Antworten:


135

Sie können dies tun, indem Sie die Ansichtsaktion anpassen.

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();
            }
        };
    }

}

Dann können Sie mit darauf klicken

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

2
Ich würde die Nullprüfung entfernen, damit, wenn die untergeordnete Ansicht nicht gefunden wird, eine Ausnahme ausgelöst wird, anstatt nichts stillschweigend zu tun.
Alvaro Gutierrez Perez

Danke für die Antwort. Aber in einem Problem getroffen. Wenn ich in der Funktion onPerform () die Funktion onView (withId ()) verwendet habe, wird die Funktion aktiviert. Was ist der Grund für dieses Verhalten?
Passagier

3
funktioniert mit Espresso: Espresso-Contrib: 2.2.2 aber nicht mit 2.2.1 irgendwelche Gründe warum? Ich habe einige Abhängigkeiten, von denen ich 2.2.2 nicht verwenden kann.
RosAng

3
Ich hatte ursprünglich eine NullPointerException damit, also musste ich getConstraints () implementieren, um dies zu vermeiden. Folgendes hat für mich funktioniert: public Matcher <View> getConstraints () {return isAssignableFrom (View.class); }
Dfinn

Die obige Lösung funktioniert bei mir nicht. Ich erhalte die folgende Fehlermeldung: android.support.test.espresso.PerformException: Fehler beim Ausführen von 'android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemAtPositionViewAction@fad9df' on view 'mit der ID: adamhurwitz.github.io.doas : id / recyclerView '.
Adam Hurwitz

66

Mit android.support.test.espresso.contrib ist es jetzt einfacher geworden:

1) Testabhängigkeit hinzufügen

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 Module ausschließen, da Sie es höchstwahrscheinlich bereits haben

2) Dann mach so etwas

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

Oder

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

Oder

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

1
Wenn Sie diese Module nicht ausschließen, wird für mich ein java.lang.IncompatibleClassChangeError angezeigt.
hboy

8
und was tun, um auf eine bestimmte Ansicht im fünften Element der Liste zu klicken? In den bereitgestellten Beispielen sehe ich nur, wie man auf das fünfte Element klickt oder auf ein Element mit Nachkommenansicht klickt, aber nicht beide zusammen, oder habe ich etwas verpasst?
Donfuxx

1
Ich musste tunexclude group: 'com.android.support' exclude module: 'recyclerview-v7'
Jemshit Iskenderov

1
Es ist nutzlos, wenn Sie in RecyclerView auf das Kontrollkästchen klicken möchten.
Farid

2
In Kotlin ist actionOnItemAtPositionein Typparameter erforderlich. Ich bin mir nicht sicher, was ich dort hinstellen soll.
ZeroDivide

7

Hier ist, wie ich das Problem in Kotlin gelöst habe:

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

und dann

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

Ich bin auf E / TestRunner gestoßen: java.lang.NullPointerException: Versuch, die virtuelle Methode 'void android.view.View.getLocationOnScreen (int [])' für eine Nullobjektreferenz aufzurufen; Irgendeine Idee warum?
Sayvortana

6

Versuchen Sie den nächsten Ansatz:

    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

Sie können auf den 3. Punkt von Gefällt mir klicken :recyclerView

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

Vergessen Sie nicht , den ViewHolderTyp anzugeben, damit die Inferenz nicht fehlschlägt.


1
Ich wurde vermisst, <RecyclerView.ViewHolder>danke, Sir;)
Skizo-ozᴉʞS

0

Alle oben genannten Antworten haben bei mir nicht funktioniert, daher habe ich eine neue Methode erstellt, die alle Ansichten in einer Zelle durchsucht, um die Ansicht mit der angeforderten ID zurückzugeben. Es erfordert zwei Methoden (könnte zu einer kombiniert werden):

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;
}

Anschließend können Sie dies in der Recycler-Ansicht wie folgt verwenden:

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

Sie können einen Rückruf verwenden, um die Ansicht zurückzugeben, wenn Sie etwas anderes damit tun möchten, oder einfach 3-4 verschiedene Versionen davon erstellen, um andere Aufgaben zu erledigen.


0

Ich habe immer wieder verschiedene Methoden ausprobiert, um herauszufinden, warum die Antwort von @ Blade bei mir nicht funktioniert hat. OnTouchListener()Um nur zu erkennen, dass ich eine habe , habe ich die ViewAction entsprechend geändert:

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

-5

Geben Sie Ihren Schaltflächen zunächst eindeutige Inhaltsbeschreibungen, dh "Zeile 5 für Zustellschaltflächen".

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

Scrollen Sie dann zur Zeile:

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

Wählen Sie dann die Ansicht basierend auf contentDescription aus.

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

Die Inhaltsbeschreibung ist eine großartige Möglichkeit, OnView von Espresso zu verwenden und Ihre App zugänglicher zu machen.


5
Die Inhaltsbeschreibung sollte nicht so verwendet werden - sie macht Ihre App nicht für Ansichten zugänglicher, um technische Informationen oder Debug-Informationen für Benutzer mit a11y-Anforderungen bereitzustellen - die CD hierfür sollte wahrscheinlich so etwas wie "Bestellung <Artikelzusammenfassung>" sein Taste". withText("Order")würde auch funktionieren und würde nicht erfordern, dass Sie zur Testzeit wissen, was Sie bestellen.
Ataulm
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.