Espresso, scrolling ne fonctionne pas lorsque NestedScrollView ou RecyclerView est en CoordinatorLayout

Il ressemble à CoordinatorLayout brise le comportement des actions Espresso telles que scrollTo() ou RecyclerViewActions.scrollToPosition().

Problème avec NestedScrollView

Pour une mise en page comme ceci:

<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 j'essaie de faire défiler à tout point de vue à l'intérieur de l' NestedScrollView en utilisant ViewActions.scrollTo() le premier problème que j'ai trouver c'est que je reçois un PerformException. C'est parce que cette action ne supporte que ScrollView et NestedScrollView ne l'étend pas. Une solution pour contourner ce problème est expliquée ici, fondamentalement, nous pouvons copier le code dans scrollTo() et changez la contrainte de support NestedScrollView. Cela semble fonctionner si le NestedScrollView n'est pas dans un CoordinatorLayout mais dès que vous le mettez à l'intérieur d'un CoordinatorLayout l'action de défilement échoue.

problème avec RecyclerView

Pour la même mise en page, si je remplace le NestedScrollView avec un RecyclerView il y a aussi des problèmes avec le défilement.

dans ce cas, j'utilise RecyclerViewAction.scrollToPosition(position). Contrairement à l' NestedScrollView, ici, je peux voir quelques défilement qui se passe. Cependant, il semble qu'il défile à la mauvaise position. Par exemple, si je fais défiler jusqu'à la dernière position, cela rend visible l'avant-dernier mais pas le dernier. Quand je bouge l' RecyclerView de la CoordinatorLayout le défilement fonctionne comme il se doit.

pour le moment nous ne pouvons pas écrire de test Espresso pour les écrans qui utilisent CoordinatorLayout en raison de ce problème. Quelqu'un rencontre le même problème ou connait une solution?

24
demandé sur Community 2016-02-08 18:14:39

7 réponses

cela se produit parce que la méthode Espresso scrollTo() vérifie explicitement la classe layout et ne fonctionne que pour ScrollView & HorizontalScrollView. En interne, c'est à l'aide de la Vue.requestRectangleOnScreen(...) donc je m'attends vraiment beau travail pour de nombreux modèles.

ma solution de contournement pour NestedScrollView était de prendre ScrollToAction et de modifier cette contrainte. L'action modifiée a bien fonctionné pour NestedScrollView avec ce changement.

public Matcher<View> getConstraints() {
    return allOf(withEffectiveVisibility(Visibility.VISIBLE), isDescendantOfA(anyOf(
            isAssignableFrom(ScrollView.class), isAssignableFrom(HorizontalScrollView.class), isAssignableFrom(NestedScrollView.class))));
}

méthode de commodité:

public static ViewAction betterScrollTo() {
    return ViewActions.actionWithAssertions(new NestedScrollToAction());
}
18
répondu Turnsole 2016-03-09 19:40:57

j'ai eu ce problème avec CoordinatorLayout->ViewPager->NestedScrollView un travail facile, autour de moi, pour obtenir le même scrollTo() le comportement a été à juste balayez vers le haut sur l'écran:

onView(withId(android.R.id.content)).perform(ViewActions.swipeUp());
8
répondu MR Mido 2016-12-14 21:26:33

Ce problème a été signalé (peut-être par l'OP?), voir numéro 203684

L'un des commentaires à ce sujet suggère une solution de rechange au problème lorsque le lecteur NestedScrollView est à l'intérieur D'un tableau de coordination:

vous devez supprimer le @string/appbar_scrolling_view_behavior comportement de mise en page de la vue ScrollingView ou d'une vue parent cette vue ScrollingView est incluse dans

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

je j'ai réussi à faire fonctionner mes tests en:

  1. Faire un personnalisé scrollTo() action (tel que référencé par les OP et Turnsole)
  2. suppression des paramètres de mise en page de NestedScrollView comme indiqué ici
3
répondu Clo Knibbe 2016-08-09 19:34:53

Voici comment j'ai fait la même chose que @miszmaniac à Kotlin. délégation de Kotlin, c'est beaucoup plus propre et plus facile parce que je n'ai pas à outrepasser les méthodes dont je n'ai pas besoin.

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
  )
}
3
répondu tasomaniac 2017-11-23 09:00:54

j'ai fait une classe NestedScrollViewScrollToAction.

je pense que c'est un meilleur endroit pour y faire des activités spécifiques.

la seule chose à mentionner est que le code cherche pour le parent nestedScrollView et supprime son comportement de CoordinatorLayout.

https://gist.github.com/miszmaniac/12f720b7e898ece55d2464fe645e1f36

2
répondu miszmaniac 2016-09-12 12:52:49

LA solution DE M. Mido peut fonctionner dans certaines situations, mais pas toujours. Si vous avez une vue en bas de l'écran, le scroll de votre RecyclerView ne se produira pas parce que le clic commencera à l'extérieur de la RecyclerView.

Une façon de contourner ce problème est d'écrire une coutume SwipeAction. Comme ceci:

1 - Créer le 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 - Créer la méthode pour retourner le 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 - Puis l'utiliser pour faites défiler l'écran:

onView(withId(android.R.id.content)).perform(swipeFromCenterToTop());

Et c'est tout! De cette façon, vous pouvez contrôler le défilement est va arriver dans votre écran.

2
répondu Leandro Borges Ferreira 2017-05-01 18:44:57

Barista est scrollTo(R.id.button) fonctionne sur toutes sortes de vues scrollables, aussi sur NestedScrollView.

il est utile de régler ce genre de problèmes avec L'Espresso. Nous le développons et l'utilisons juste pour écrire des tests Espresso d'une manière rapide et fiable. Et voici un lien: https://github.com/SchibstedSpain/Barista

2
répondu Roc Boronat 2017-06-08 14:42:02