浓缩咖啡嵌套滚动视图

13

我正在尝试测试位于NestedScrollView中的表单中的EditTexts。我正在运行以下代码:

onView(withId(R.id.register_scroll_view)).perform(scrollTo()).perform(click());

其中register_scroll_view是我的NestedScrollView。 但是,我收到了一个异常:

  

android.support.test.espresso.PerformException:在视图“具有id:com.eazyigz.myapp:id / register_scroll_view”的滚动上执行错误。     造成原因:由于目标视图不符合以下一个或多个约束条件,操作将不会执行:     (视图的有效可见性= VISIBLE,并且是a的后代:(可分配给类:class android.widget.ScrollView或可分配给类:class android.widget.HorizontalScrollView))

如何正确设计此测试以使需要滚动才能变为可见的EditTexts被测试?

4个回答

15

我没有使用过 NestedScrollView,但它似乎支持与普通 ScrollView 相同的 requestRectangleOnScreen() 滚动方式。

唯一的问题是 ScrollView 的限制已经硬编码到 scrollTo() 操作中,而 NestedScrollView 并未继承普通 ScrollView。

我认为这里唯一的解决方案是将整个 ScrollToAction 类复制并粘贴到您自己的实现中,并替换掉烦人的限制。


2
即使复制了ScrollToAction,在我也有NestedScrollView的情况下,它并没有起作用。对我有效的是简单地使用swipeUp()而不是scrollTo() - Wahib Ul Haq
1
@WahibUlHaq,你救了我的命。非常感谢 :) - Ale

9

我已经编写了一个ViewAction,用于处理NestedScrollView的子视图滚动。它还考虑到了CoordinatorLayout可能是根布局的情况 - 所以您不必担心工具栏会改变其大小。

以下是一些代码。您需要将此类复制粘贴到项目的某个位置。然后,您可以像这样使用它:

onView(withId(R.id.register_scroll_view))
        .perform(CustomScrollActions.nestedScrollTo, click());

重要提示:它不能替代scrollTo()方法,而是另一种ScrollView滚动视图操作,适用于处理NestedScrollView的情况。

所以我说的就是一个ViewAction:

public class CustomScrollActions {

    public static ViewAction nestedScrollTo() {
        return new ViewAction() {

            @Override
            public Matcher<View> getConstraints() {
                return Matchers.allOf(
                        isDescendantOfA(isAssignableFrom(NestedScrollView.class)),
                        withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE));
            }

            @Override
            public String getDescription() {
                return "Find parent with type " + NestedScrollView.class +
                        " of matched view and programmatically scroll to it.";
            }

            @Override
            public void perform(UiController uiController, View view) {
                try {
                    NestedScrollView nestedScrollView = (NestedScrollView)
                            findFirstParentLayoutOfClass(view, NestedScrollView.class);
                    if (nestedScrollView != null) {
                        CoordinatorLayout coordinatorLayout =
                                (CoordinatorLayout) findFirstParentLayoutOfClass(view, CoordinatorLayout.class);
                        if (coordinatorLayout != null) {
                            CollapsingToolbarLayout collapsingToolbarLayout =
                                    findCollapsingToolbarLayoutChildIn(coordinatorLayout);
                            if (collapsingToolbarLayout != null) {
                                int toolbarHeight = collapsingToolbarLayout.getHeight();
                                nestedScrollView.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                                nestedScrollView.dispatchNestedPreScroll(0, toolbarHeight, null, null);
                            }
                        }
                        nestedScrollView.scrollTo(0, view.getTop());
                    } else {
                        throw new Exception("Unable to find NestedScrollView parent.");
                    }
                } catch (Exception e) {
                    throw new PerformException.Builder()
                            .withActionDescription(this.getDescription())
                            .withViewDescription(HumanReadables.describe(view))
                            .withCause(e)
                            .build();
                }
                uiController.loopMainThreadUntilIdle();
            }
        };
    }

    private static CollapsingToolbarLayout findCollapsingToolbarLayoutChildIn(ViewGroup viewGroup) {
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            View child = viewGroup.getChildAt(i);
            if (child instanceof CollapsingToolbarLayout) {
                return (CollapsingToolbarLayout) child;
            } else if (child instanceof ViewGroup) {
                return findCollapsingToolbarLayoutChildIn((ViewGroup) child);
            }
        }
        return null;
    }

    private static View findFirstParentLayoutOfClass(View view, Class<? extends View> parentClass) {
        ViewParent parent = new FrameLayout(view.getContext());
        ViewParent incrementView = null;
        int i = 0;
        while (parent != null && !(parent.getClass() == parentClass)) {
            if (i == 0) {
                parent = findParent(view);
            } else {
                parent = findParent(incrementView);
            }
            incrementView = parent;
            i++;
        }
        return (View) parent;
    }

    private static ViewParent findParent(View view) {
        return view.getParent();
    }

    private static ViewParent findParent(ViewParent view) {
        return view.getParent();
    }
}

2
这是我迄今为止找到的唯一一个也适用于动画BottomNavigationView的解决方案。 - Till Krempel

4

使用

onView(withId(R.id.register_scroll_view))
        .perform(swipeUp(), click())

1
onView(withId(R.id. register_scroll_view)).perform(swipeUp()) 应该就足够了 :) - Ryan Amaral

1
这是@F1sher方案的Kotlin版本,做了一些改进:
import android.view.View
import android.view.ViewParent
import androidx.core.view.get
import androidx.core.widget.NestedScrollView
import androidx.test.espresso.PerformException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.util.HumanReadables
import org.hamcrest.Matcher
import org.hamcrest.Matchers

object CustomActions {

    const val TOP_X = 0

    fun nestedScrollTo(): ViewAction {
        return object : ViewAction {
            override fun getConstraints(): Matcher<View> {
                return Matchers.allOf(
                    isDescendantOfA(isAssignableFrom(NestedScrollView::class.java)),
                    withEffectiveVisibility(Visibility.VISIBLE)
                )
            }

            override fun getDescription(): String {
                return "Find parent with type " + NestedScrollView::class.java +
                        " of matched view and programmatically scroll to it."
            }

            override fun perform(uiController: UiController, view: View) {
                try {
                    findParentOrNull<NestedScrollView>(view) ?: throw Exception("Unable to find NestedScrollView parent.")
                } catch (e: Exception) {
                    throw PerformException.Builder()
                        .withActionDescription(description)
                        .withViewDescription(HumanReadables.describe(view))
                        .withCause(e)
                        .build()
                }
                uiController.loopMainThreadUntilIdle()
            }
        }
    }

    private inline fun <reified T> findFirstParentOrNull(view: View): T? {
        var parent: ViewParent? = null
        var incrementView: ViewParent? = null
        var isMatchInitialParent = false
        while (parent?.javaClass != T::class.java) {
            parent = if (isMatchInitialParent.not()) {
                isMatchInitialParent = true
                view.parent
            } else {
                incrementView?.parent
            }
            incrementView = parent
        }
        return parent as? T
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接