Espresso,当NestedScrollView或RecyclerView在CoordinatorLayout中时,滚动不起作用

它看起来像CoordinatorLayout打破了Espresso的行为,如scrollTo()RecyclerViewActions.scrollToPosition()

问题与NestedScrollView

对于这样的布局:

   ...   ...   

如果我尝试使用ViewActions.scrollTo()滚动到NestedScrollView内的任何视图,我发现第一个问题是,我得到一个PerformException 。 这是因为这个动作只支持ScrollViewNestedScrollView不能扩展它。 这个问题的解决方法在这里解释,基本上我们可以在scrollTo()复制代码,并改变约束来支持NestedScrollView 。 这似乎工作,如果NestedScrollView不是在CoordinatorLayout但只要你把它放在一个CoordinatorLayout滚动操作失败。

问题与RecyclerView

对于相同的布局,如果我将NestedScrollView替换为RecyclerView ,滚动也会出现问题。

在这种情况下,我使用RecyclerViewAction.scrollToPosition(position) 。 不像NestedScrollView ,在这里我可以看到一些滚动发生。 但是,它看起来像滚动到错误的位置。 例如,如果我滚动到最后一个位置,它将显示倒数第二,但不是最后一个。 当我将RecyclerView移出CoordinatorLayout ,滚动就像它应该的那样工作。

目前,由于这个问题,我们无法为使用CoordinatorLayout的屏幕编写Espresso测试。 任何人遇到同样的问题或知道解决方法?

这是因为Espresso scrollTo()方法显式检查布局类,只适用于ScrollView和Horizo​​ntalScrollView。 在内部,它使用View.requestRectangleOnScreen(…),所以我期望它实际上很好的布局。

我的NestedScrollView的解决方法是采取ScrollToAction并修改该约束。 修改后的操作对于NestedScrollView可以正常工作。

更改ScrollToAction类中的方法:

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

我有这个问题与CoordinatorLayout-> ViewPager-> NestedScrollView从我得到相同的scrollTo()行为只是在屏幕上向上轻扫工作:

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

这个问题已经被报道(可能由OP?),参见问题203684

对这个问题的一个评论意味着当NestedScrollView位于CoordinatorLayout内部时,解决问题:

您需要移除ScrollingView或此ScrollingView所包含的任何父视图的@string/appbar_scrolling_view_behavior布局行为

这是一个解决方法的实现:

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

我能够通过以下方式得到我的测试工作:

  1. 制作自定义的scrollTo()动作(由OP和Turnsole引用)
  2. 删除NestedScrollView的布局参数,如下所示

我做了一个NestedScrollViewScrollToAction类。

我认为这是更好的地方,而不是特定的活动。

值得一提的是,代码搜索父亲nestedScrollView,并删除它的CoordinatorLayout行为。

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

Barista的scrollTo(R.id.button)适用于各种可滚动的视图,也在NestedScrollView

用Espresso解决这类问题是很有用的。 我们开发和使用它只是用快速和可靠的方式编写Espresso测试。 这里有一个链接: https : //github.com/SchibstedSpain/Barista

Mido先生的解决方案可能适用于某些情况,但并不总是如此。 如果您在屏幕底部有一些视图,您的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 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()); 

而就是这样! 这样你就可以控制滚动在你的屏幕上如何发生。

下面是我做了同样的事情@米兹曼克在科特林做的。 有了Kotlin的代表团 ,它就更加清洁和简单,因为我不必重写我不需要的方法。

 class ScrollToAction( private val original: android.support.test.espresso.action.ScrollToAction = android.support.test.espresso.action.ScrollToAction() ) : ViewAction by original { override fun getConstraints(): Matcher = anyOf( allOf( withEffectiveVisibility(Visibility.VISIBLE), isDescendantOfA(isAssignableFrom(NestedScrollView::class.java))), original.constraints ) }