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
из-за этих проблем. Кто-нибудь испытывает те же проблемы или знает обходное решение?
Ответы
Ответ 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
)
}