【问题标题】:Espresso NestedScrollView浓缩咖啡 NestedScrollView
【发布时间】:2015-10-15 09:16:27
【问题描述】:

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

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”上执行“滚动到”时出错。 原因:java.lang.RuntimeException:由于目标视图不匹配以下一个或多个约束,将不会执行操作: (视图具有有效的可见性=VISIBLE 并且是 a 的后代:(可从类分配:类 android.widget.ScrollView 或可从类分配:类 android.widget.Horizo​​ntalScrollView))

如何正确设计此测试,以便可以测试需要滚动到可见的 EditText?

【问题讨论】:

    标签: android android-testing android-espresso


    【解决方案1】:

    我没有任何使用 NestedScrollView 的经验,但似乎 requestRectangleOnScreen(),这是 espresso 在常规 ScrollView 中滚动的方式,应该同样适用于 NestedScrollView。

    唯一的问题是 ScrollView 约束被硬编码到 scrollTo() 操作中,并且 NestedScrollView 没有继承常规的 ScrollView。

    我认为这里唯一的解决方案是将整个 ScrollToAction 类复制并粘贴到您自己的此操作实现中,并替换讨厌的约束。

    【讨论】:

    • 在我也有 NestedScrollView 的情况下,即使复制 ScrollToAction 也不起作用。对我有用的只是简单地使用swipeUp() 而不是scrollTo()
    • @WahibUlHaq 你救了我的命。非常感谢:)
    【解决方案2】:

    我编写了单个 ViewAction 来处理滚动到属于 NestedScrollView 的子视图的视图。它还考虑到 CoordinatorLayout 可能是根 - 所以你不必害怕工具栏改变它的大小。

    有一些代码。您需要将此类复制粘贴到您的项目中的某个地方。然后你可以像这样使用它:

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

    重要提示:它不是 scrollTo() 的替代品,它是另一个滚动 ViewAction,您应该在处理 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();
        }
    }
    

    【讨论】:

    • 这是迄今为止我发现的唯一一个也适用于动画 BottomNavigationView 的解决方案。
    【解决方案3】:

    使用

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

    【讨论】:

    • onView(withId(R.id. register_scroll_view)).perform(swipeUp()) 应该足够了:)
    【解决方案4】:

    这是@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
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多