android - Espresso, el desplazamiento no funciona cuando NestedScrollView o RecyclerView están en CoordinatorLayout
android-recyclerview android-espresso (7)
Parece que CoordinatorLayout
rompe el comportamiento de las acciones de Espresso como scrollTo()
o RecyclerViewActions.scrollToPosition()
.
Problema con NestedScrollView
Para un diseño como este:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
...
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
...
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
Si intento desplazarme a cualquier vista dentro de NestedScrollView
usando ViewActions.scrollTo()
el primer problema que encuentro es que obtengo una PerformException
. Esto se debe a que esta acción solo admite ScrollView
y NestedScrollView
no la amplía. here se explica una solución para este problema, básicamente podemos copiar el código en scrollTo()
y cambiar las restricciones para admitir NestedScrollView
. Esto parece funcionar si NestedScrollView
no está en un CoordinatorLayout
pero tan pronto como lo pones dentro de un CoordinatorLayout
la acción de desplazamiento falla.
Problema con RecyclerView
Para el mismo diseño, si sustituyo el NestedScrollView
por un RecyclerView
también hay problemas con el desplazamiento.
En este caso, estoy usando RecyclerViewAction.scrollToPosition(position)
. A diferencia de NestedScrollView
, aquí puedo ver algunos eventos de desplazamiento. Sin embargo, parece que se desplaza a la posición incorrecta. Por ejemplo, si me desplazo a la última posición, hace visible la segunda para durar, pero no la última. Cuando muevo el RecyclerView
fuera del CoordinatorLayout
el desplazamiento funciona como debería.
En este momento no podemos escribir ninguna prueba de Espresso para las pantallas que usan CoordinatorLayout
debido a estos problemas. ¿Alguien experimenta los mismos problemas o conoce una solución?
Aquí es cómo hice lo mismo que hizo @miszmaniac en Kotlin. Con la delegación en Kotlin , es mucho más limpio y fácil porque no tengo que anular los métodos que no necesito.
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
// remove CoordinatorLayout.LayoutParams from NestedScrollView
NestedScrollView nestedScrollView = (NestedScrollView)activity.findViewById(scrollViewId);
CoordinatorLayout.LayoutParams params =
(CoordinatorLayout.LayoutParams)nestedScrollView.getLayoutParams();
params.setBehavior(null);
nestedScrollView.requestLayout();
}
});
El scrollTo(R.id.button)
funciona en todo tipo de vistas desplazables, también en NestedScrollView
.
Es útil para solucionar este tipo de problemas con Espresso. Lo desarrollamos y lo usamos solo para escribir las pruebas Espresso de una manera rápida y confiable. Y aquí hay un enlace: https://github.com/SchibstedSpain/Barista
Esto sucede porque el método espresso scrollTo () comprueba explícitamente la clase de diseño y solo funciona para ScrollView y HorizontalScrollView. Internamente, está usando View.requestRectangleOnScreen (...), así que espero que realmente funcione bien para muchos diseños.
Mi solución para NestedScrollView fue tomar ScrollToAction y modificar esa restricción. La acción modificada funcionó bien para NestedScrollView con ese cambio.
Método modificado en la clase ScrollToAction:
public Matcher<View> getConstraints() {
return allOf(withEffectiveVisibility(Visibility.VISIBLE), isDescendantOfA(anyOf(
isAssignableFrom(ScrollView.class), isAssignableFrom(HorizontalScrollView.class), isAssignableFrom(NestedScrollView.class))));
}
Método de conveniencia:
public static ViewAction betterScrollTo() {
return ViewActions.actionWithAssertions(new NestedScrollToAction());
}
He hecho una clase NestedScrollViewScrollToAction.
Creo que es mejor lugar para hacer cosas específicas de la actividad allí.
Lo único que vale la pena mencionar es que el código busca el nestedScrollView principal y elimina el comportamiento de CoordinatorLayout.
https://gist.github.com/miszmaniac/12f720b7e898ece55d2464fe645e1f36
La solución del Sr. Mido puede funcionar en algunas situaciones, pero no siempre. Si tiene alguna vista en la parte inferior de la pantalla, el desplazamiento de su RecyclerView no se realizará porque el clic comenzará fuera de RecyclerView.
Una forma de solucionar este problema es escribir una SwipeAction personalizada. Me gusta esto:
1 - Crear el CenterSwipeAction
public class CenterSwipeAction implements ViewAction {
private final Swiper swiper;
private final CoordinatesProvider startCoordProvide;
private final CoordinatesProvider endCoordProvide;
private final PrecisionDescriber precDesc;
public CenterSwipeAction(Swiper swiper, CoordinatesProvider startCoordProvide,
CoordinatesProvider endCoordProvide, PrecisionDescriber precDesc) {
this.swiper = swiper;
this.startCoordProvide = startCoordProvide;
this.endCoordProvide = endCoordProvide;
this.precDesc = precDesc;
}
@Override public Matcher<View> getConstraints() {
return withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE);
}
@Override public String getDescription() {
return "swipe from middle of screen";
}
@Override
public void perform(UiController uiController, View view) {
float[] startCoord = startCoordProvide.calculateCoordinates(view);
float[] finalCoord = endCoordProvide.calculateCoordinates(view);
float[] precision = precDesc.describePrecision();
// you could try this for several times until Swiper.Status is achieved or try count is reached
try {
swiper.sendSwipe(uiController, startCoord, finalCoord, precision);
} catch (RuntimeException re) {
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(re)
.build();
}
// ensures that the swipe has been run.
uiController.loopMainThreadForAtLeast(ViewConfiguration.getPressedStateDuration());
}
}
2 - Crear el método para devolver la ViewAction
private static ViewAction swipeFromCenterToTop() {
return new CenterSwipeAction(Swipe.FAST,
GeneralLocation.CENTER,
view -> {
float[] coordinates = GeneralLocation.CENTER.calculateCoordinates(view);
coordinates[1] = 0;
return coordinates;
},
Press.FINGER);
}
3 - Luego úsalo para desplazarte por la pantalla:
onView(withId(android.R.id.content)).perform(swipeFromCenterToTop());
¡Y eso es! De esta manera puedes controlar cómo va a pasar el desplazamiento en tu pantalla.
Se ha informado este problema (¿quizás por el OP?), Consulte el Número 203684
Uno de los comentarios a ese problema sugiere una solución al problema cuando NestedScrollView está dentro de un CoordinatorLayout:
debe eliminar el comportamiento de diseño de
@string/appbar_scrolling_view_behavior
o de cualquier vista principal en la que se incluye@string/appbar_scrolling_view_behavior
Aquí hay una implementación de esa solución:
class ScrollToAction(
private val original: android.support.test.espresso.action.ScrollToAction = android.support.test.espresso.action.ScrollToAction()
) : ViewAction by original {
override fun getConstraints(): Matcher<View> = anyOf(
allOf(
withEffectiveVisibility(Visibility.VISIBLE),
isDescendantOfA(isAssignableFrom(NestedScrollView::class.java))),
original.constraints
)
}
Pude hacer que mis pruebas funcionaran mediante:
- Realización de una acción scrollTo () personalizada (según lo referenciado por el OP y Turnsole)
- Eliminar los parámetros de diseño de NestedScrollView como se muestra aquí
Tuve este problema con CoordinatorLayout-> ViewPager-> NestedScrollView. Me resultó muy fácil solucionar el mismo comportamiento de scrollTo () simplemente deslizar hacia arriba en la pantalla:
onView(withId(android.R.id.content)).perform(ViewActions.swipeUp());