android - Cómo hacer clic en un elemento dentro de RecyclerView en Espresso
android-recyclerview android-espresso (8)
Tengo un RecyclerView (R.id.recyclerView) donde cada fila tiene una imagen (R.id.row_image) y un TextView. Quiero hacer clic en la imagen en la primera fila.
Intenté usar onData (..) pero parece que no funciona.
Editar:
Espresso 2.0 ha sido lanzado, el registro de cambios incluye lo siguiente:
Nuevas características
- espresso-contrib
- RecyclerViewActions: maneja las interacciones con RecyclerViews
Vieja respuesta
Todavía no lo he probado yo mismo, pero Thomas Keller publicó esto en G + con una breve explicación y un enlace a un Gist con los correctores de vistas necesarios.
Dado que la nueva API de
RecyclerView
hereda deViewGroup
y no deAdapterView
, no puede usaronData()
Espresso para probar diseños utilizando este componente.
Adjuntaré el código, solo para completar (nota: ¡ no mío! Todo el crédito es para 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));
}
}
Y uso de muestra:
onRecyclerItemView(R.id.item_title, withText("Test"), withId(R.id.item_content))
.matches(check(withText("Test Content")));
Como publiqué here , puedes implementar tu marcador RecyclerView
personalizado. Supongamos que tiene RecyclerView
donde cada elemento tiene un tema que no concuerda:
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);
}
};
}
Y el uso:
onView(withId(R.id.my_recycler_view_id)
.perform(RecyclerViewActions.actionOnHolderItem(withItemSubject("My subject"), click()));
Básicamente puedes hacer coincidir todo lo que quieras. En este ejemplo usamos TextView
pero puede ser cualquier elemento dentro del elemento RecyclerView
.
Una cosa más para aclarar es verificar la visibilidad (subjectTextView.getVisibility() == View.VISIBLE)
. Necesitamos tenerlo porque a veces otras vistas dentro de RecyclerView
pueden tener el mismo tema pero sería con View.GONE
. De esta forma, evitamos las coincidencias múltiples de nuestro marcador personalizado y solo el elemento objetivo que realmente muestra nuestro tema.
Deberías usar una vista de acción personalizada:
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));
}
}
He encontrado dos formas:
- Suponiendo que tiene una vista de texto con id "R.id.description" para cada elemento en RecyclerView. Puedes hacer esto para unir a un niño específico:
onView(allOf(withId(R.id.place_description),withText("what"))).perform(click());
- Un tutorial de Android Testing Codelab https://codelabs.developers.google.com/codelabs/android-testing/#0
`
public Matcher<View> withItemText(final String itemText) {
checkArgument(!TextUtils.isEmpty(itemText),"cannot be null");
return new TypeSafeMatcher<View>() {
@Override
protected boolean matchesSafely(View item) {
return allOf(isDescendantOfA(isAssignableFrom(RecyclerView.class)),withText(itemText)).matches(item);
}
@Override
public void describeTo(Description description) {
description.appendText("is descendant of a RecyclerView with text" + itemText);
}
};
}
`
Y luego, haz esto:
onView(withItemText("what")).perform(click());
No necesita agregar "testing-support-lib", ni "espresso: espresso-core". Se agregan transitivos cuando se agrega "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''
}
Uso :
onView(withId(R.id.recyclerView)).perform(
RecyclerViewActions.actionOnItemAtPosition(0, click()));
Seguí la respuesta de @Gabor, pero cuando incluí las bibliotecas, ¡ alcancé el límite de dex!
Entonces, getInstrumentation().waitForIdleSync();
las bibliotecas, agregué este getInstrumentation().waitForIdleSync();
y luego llamé a onView(withId...))...
Funciona perfectamente.
En su caso, tendrá múltiples vistas de imágenes con la misma ID, por lo que tendrá que averiguar algo sobre cómo puede seleccionar el elemento de la lista en particular.
Solo para agregar a la respuesta de Gabor (que es la respuesta correcta y completa desde Espresso 2.0).
Puede encontrar problemas en el momento de usar espresso-contrib
y RecyclerView
s (consulte el ticket de android-test-kit ).
Una solución consiste en agregar esta exclusión en la dependencia de espresso-contrib
Gabor mencionó anteriormente:
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''
}
(Esta es una respuesta en lugar de un comentario a la respuesta de Gabor porque todavía no tengo derecho a publicar comentarios)
onView(withId(R.id.recyclerView))
.perform(actionOnItemAtPosition(0, click()));
Incluye esto en tu script de 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''
}