Подтвердить что ты не робот

Espresso, прокрутка не работает, когда NestedScrollView или RecyclerView находится в CoordinatorLayout

Похоже, что CoordinatorLayout нарушает поведение действий Espresso, таких как scrollTo() или RecyclerViewActions.scrollToPosition().

Проблема с NestedScrollView

Для макета, подобного этому:

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

Если я попытаюсь прокрутить любое представление внутри NestedScrollView, используя ViewActions.scrollTo(), первая проблема, которую я нахожу, это то, что я получаю PerformException. Это связано с тем, что это действие поддерживает только ScrollView и NestedScrollView не расширяет его. Обходной путь для этой проблемы объясняется здесь, в основном мы можем скопировать код в scrollTo() и изменить ограничения для поддержки NestedScrollView. Кажется, что это работает, если NestedScrollView не находится в CoordinatorLayout, но как только вы помещаете его в CoordinatorLayout, действие прокрутки завершается с ошибкой.

Проблема с RecyclerView

Для того же макета, если я заменю NestedScrollView на RecyclerView, также есть проблемы с прокруткой.

В этом случае я использую RecyclerViewAction.scrollToPosition(position). В отличие от NestedScrollView, здесь я вижу прокрутку. Однако похоже, что он прокручивается в неправильное положение. Например, если я прокручиваю до последней позиции, она делает видимым вторую, но не последнюю. Когда я перемещаю RecyclerView из CoordinatorLayout, прокрутка работает так, как должна.

В настоящий момент мы не можем написать тест Espresso для экранов, которые используют CoordinatorLayout из-за этих проблем. Кто-нибудь испытывает те же проблемы или знает обходное решение?

4b9b3361

Ответ 1

Это происходит потому, что метод scrollTo() Espresso явно проверяет класс макета и работает только для ScrollView и HorizontalScrollView. Внутри он использует View.requestRectangleOnScreen(...), поэтому я ожидаю, что он действительно отлично работает для многих макетов.

Моим обходным решением для NestedScrollView было использование ScrollToAction и изменение этого ограничения. Измененное действие отлично работало для NestedScrollView с этим изменением.

Измененный метод в классе ScrollToAction:

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

Метод удобства:

public static ViewAction betterScrollTo() {
    return ViewActions.actionWithAssertions(new NestedScrollToAction());
}

Ответ 2

У меня была эта проблема с CoordinatorLayout- > ViewPager- > NestedScrollView, легкая работа со мной, чтобы получить одно и то же поведение scrollTo(), чтобы просто прокрутить вверх по экрану:

onView(withId(android.R.id.content)).perform(ViewActions.swipeUp());

Ответ 3

Эта проблема была сообщена (возможно, OP?), см. Проблема 203684

Один из комментариев к этой проблеме предполагает обход проблемы, когда NestedScrollView находится внутри координатораLayout:

вам нужно удалить поведение макета @string/appbar_scrolling_view_behavior для ScrollingView или любого родительского представления, которое этот ScrollingView включен в

Вот реализация этой работы:

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

Мне удалось выполнить мои тесты:

  • Выполнение пользовательского действия scrollTo() (как указано OP и Turnsole)
  • Удаление параметров макета NestedScrollView, как показано здесь.

Ответ 4

Я создал класс NestedScrollViewScrollToAction.

Я думаю, что лучше разместить там конкретные вещи.

Единственное, что стоит упомянуть, это то, что код ищет родительский вложенныйScrollView и удаляет его поведение CoordinatorLayout.

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

Ответ 5

Barista scrollTo(R.id.button) работает со всеми прокручиваемыми видами, также на NestedScrollView.

Полезно исправлять такие проблемы с помощью Espresso. Мы разрабатываем и используем его только для быстрого и надежного написания тестов Espresso. И здесь ссылка: https://github.com/SchibstedSpain/Barista

Ответ 6

Решение г-на Мидо может работать в некоторых ситуациях, но не всегда. Если у вас есть какое-то представление в нижней части экрана, прокрутка вашего RecyclerView не произойдет, потому что щелчок начнется за пределами RecyclerView.

Один из способов решения этой проблемы - написать собственный SwipeAction. Вот так:

1 - Создайте 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 - Создайте метод для возврата 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 - Затем используйте его для прокрутки экрана:

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

И это! Таким образом вы можете контролировать, как прокрутка будет происходить на вашем экране.

Ответ 7

Вот как я сделал то же самое, что @miszmaniac сделал в Котлине. С делегацией в Котлине она намного чище и проще, потому что мне не нужно переопределять методы, которые мне не нужны.

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