web-dev-qa-db-fra.com

Comment cliquer sur un élément à l'intérieur d'un RecyclerView dans Espresso

J'ai un RecyclerView (R.id.recyclerView) où chaque ligne a une image (R.id.row_image) et un TextView. Je veux cliquer sur l'image dans la première rangée.
J'ai essayé d'utiliser onData (..) mais cela ne semble pas fonctionner.

33
Jacki

Utilisez RecyclerViewActions

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

Incluez ceci dans votre script gradle:

dependencies {
    androidTestCompile 'com.Android.support.test:testing-support-lib:0.1'
    androidTestCompile 'com.Android.support.test.espresso:espresso-core:2.0'
    androidTestCompile 'com.Android.support.test.espresso:espresso-contrib:2.0'
}
69
Gabor

Juste pour ajouter à la réponse de Gabor (qui est la réponse correcte et complète depuis Espresso 2.0).

Vous pouvez rencontrer des problèmes au moment de l’utilisation de espresso-contrib et RecyclerViews (voir Ticket Android-test-kit ). 

Une solution de contournement consiste à ajouter cette exclusion dans la dépendance espresso-contrib Gabor mentionnée ci-dessus:

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

(Ceci est une réponse au lieu d'un commentaire sur la réponse de Gabor car je n'ai pas encore le droit de poster des commentaires)

21
Sir4ur0n

Modifier:

Espresso 2.0 est sorti, le changelog comprend les éléments suivants:

Nouvelles fonctionnalités

  • espresso-contrib
    • RecyclerViewActions: gère les interactions avec RecyclerViews

Réponse ancienne

Je n'ai pas encore testé cela moi-même, mais Thomas Keller a posté ceci sur G + avec une brève explication et un lien vers un Gist avec les points de vue nécessaires.

Étant donné que la nouvelle API RecyclerView hérite de ViewGroup et non de AdapterView, vous ne pouvez pas utiliser la fonction onData() de Espresso pour tester les dispositions à l'aide de ce composant.

Lien vers Gist .

Je vais attacher le code, juste pour être complet (note: pas le mien! Tout le mérite revient à Thomas Keller)

ViewMatcher:

public class ViewMatchers {
    @SuppressWarnings("unchecked")
    public static Matcher<View> withRecyclerView(@IdRes int viewId) {
        return allOf(isAssignableFrom(RecyclerView.class), withId(viewId));
    }

    @SuppressWarnings("unchecked")
    public static ViewInteraction onRecyclerItemView(@IdRes int identifyingView, Matcher<View> identifyingMatcher, Matcher<View> childMatcher) {
        Matcher<View> itemView = allOf(withParent(withRecyclerView(R.id.start_grid)),
                withChild(allOf(withId(identifyingView), identifyingMatcher)));
        return Espresso.onView(allOf(isDescendantOfA(itemView), childMatcher));
    }
}

Et exemple d'utilisation:

onRecyclerItemView(R.id.item_title, withText("Test"),  withId(R.id.item_content))
    .matches(check(withText("Test Content")));
15
aried3r

Vous devez utiliser un ViewAction personnalisé:

public void clickOnImageViewAtRow(int position) {
    onView(withId(R.id.recycler_view)).perform(RecyclerViewActions.actionOnItemAtPosition(position, new ClickOnImageView()));
}

public class ClickOnImageView implements ViewAction{
    ViewAction click = click();

    @Override
    public Matcher<View> getConstraints() {
        return click.getConstraints();
    }

    @Override
    public String getDescription() {
        return " click on custom image view";
    }

    @Override
    public void perform(UiController uiController, View view) {
        click.perform(uiController, view.findViewById(R.id.imageView));
    }
}
5
fede1608

J'ai suivi la réponse de @Gabor mais quand j'ai inclus les bibliothèques, je ai atteint la limite de dex!

J'ai donc supprimé les bibliothèques, ajouté cette getInstrumentation().waitForIdleSync(); et juste appelé onView(withId...))...

Marche parfaitement.

Dans votre cas, vous aurez plusieurs vues d’image avec le même identifiant. Vous devrez donc trouver un moyen de sélectionner un élément de la liste.

1
vedant

Vous n'avez pas besoin d'ajouter "testing-support-lib", ni "espresso: expresso-core". Ils sont ajoutés à la suite de l'expression "espresso: espresso-contrib".

build.grade

dependencies {
    androidTestCompile 'com.Android.support.test:runner:0.3'
    androidTestCompile 'com.Android.support.test:rules:0.3'
    androidTestCompile 'com.Android.support.test.espresso:espresso-contrib:2.2'
}

Utilisation :

onView(withId(R.id.recyclerView)).perform(
            RecyclerViewActions.actionOnItemAtPosition(0, click()));
1
Víctor Albertos

Comme je l'ai posté ici vous pouvez implémenter votre correcteur RecyclerView personnalisé. Supposons que vous ayez RecyclerView où chaque élément a le même sujet que vous voulez apparier:

public static Matcher<RecyclerView.ViewHolder> withItemSubject(final String subject) {
    Checks.checkNotNull(subject);
    return new BoundedMatcher<RecyclerView.ViewHolder, MyCustomViewHolder>(
            MyCustomViewHolder.class) {

        @Override
        protected boolean matchesSafely(MyCustomViewHolder viewHolder) {
            TextView subjectTextView = (TextView)viewHolder.itemView.findViewById(R.id.subject_text_view_id);

            return ((subject.equals(subjectTextView.getText().toString())
                    && (subjectTextView.getVisibility() == View.VISIBLE)));
        }

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

Et utilisation:

onView(withId(R.id.my_recycler_view_id)
    .perform(RecyclerViewActions.actionOnHolderItem(withItemSubject("My subject"), click()));

Fondamentalement, vous pouvez faire correspondre tout ce que vous voulez. Dans cet exemple, nous avons utilisé le sujet TextView mais il peut s'agir de n'importe quel élément de l'élément RecyclerView.

Une dernière chose à clarifier est de vérifier la visibilité (subjectTextView.getVisibility() == View.VISIBLE). Nous avons besoin de l'avoir parce que parfois d'autres vues dans RecyclerView peuvent avoir le même sujet mais ce serait avec View.GONE. De cette façon, nous évitons les correspondances multiples de notre élément de correspondance personnalisé et de l'élément cible uniquement qui affiche réellement notre sujet.

1
denys