【问题标题】:Selecting child view at index using Espresso使用 Espresso 在索引处选择子视图
【发布时间】:2014-07-15 00:44:49
【问题描述】:

当使用带有子图像视图的自定义小部件视图时,使用 Espresso 时,我可以使用哪种 Matcher 类型来选择第 n 个孩子? 示例:

+--------->NumberSlider{id=2131296844, res-name=number_slider, visibility=VISIBLE, width=700, height=95, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=10.0, y=0.0, child-count=7}
|
+---------->NumberView{id=-1, visibility=VISIBLE, width=99, height=95, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0}
|
+---------->NumberView{id=-1, visibility=VISIBLE, width=100, height=95, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=99.0, y=0.0}
|
+---------->NumberView{id=-1, visibility=VISIBLE, width=100, height=95, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=199.0, y=0.0}
|
+---------->NumberView{id=-1, visibility=VISIBLE, width=100, height=95, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=299.0, y=0.0}
|
+---------->NumberView{id=-1, visibility=VISIBLE, width=100, height=95, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=399.0, y=0.0}
|
+---------->NumberView{id=-1, visibility=VISIBLE, width=100, height=95, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=499.0, y=0.0}
|
+---------->NumberView{id=-1, visibility=VISIBLE, width=100, height=95, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=599.0, y=0.0}

【问题讨论】:

    标签: android android-espresso


    【解决方案1】:
     public static Matcher<View> nthChildOf(final Matcher<View> parentMatcher, final int childPosition) {
        return new TypeSafeMatcher<View>() {
          @Override
          public void describeTo(Description description) {
            description.appendText("with "+childPosition+" child view of type parentMatcher");
          }
    
          @Override
          public boolean matchesSafely(View view) {
            if (!(view.getParent() instanceof ViewGroup)) {
              return parentMatcher.matches(view.getParent());
            }
    
            ViewGroup group = (ViewGroup) view.getParent();
            return parentMatcher.matches(view.getParent()) && group.getChildAt(childPosition).equals(view);
          }
        };
      }
    

    使用它

    onView(nthChildOf(withId(R.id.parent_container), 0)).check(matches(withText("I am the first child")));
    

    【讨论】:

    • 如果父级中没有足够的子视图,在最后一条指令中使用return parentMatcher.matches(view.getParent()) &amp;&amp; view.equals(group.getChildAt(childPosition)) 会更安全,以避免出现 NullPointerException
    【解决方案2】:

    为了尝试改进 Maragues 的解决方案,我做了一些更改。

    解决方案是创建一个自定义的Matcher,它为父视图包装另一个Matcher,并将子视图的索引设为匹配。

    public static Matcher<View> nthChildOf(final Matcher<View> parentMatcher, final int childPosition) {
        return new TypeSafeMatcher<View>() {
            @Override
            public void describeTo(Description description) {
                description.appendText("position " + childPosition + " of parent ");
                parentMatcher.describeTo(description);
            }
    
            @Override
            public boolean matchesSafely(View view) {
                if (!(view.getParent() instanceof ViewGroup)) return false;
                ViewGroup parent = (ViewGroup) view.getParent();
    
                return parentMatcher.matches(parent)
                        && parent.getChildCount() > childPosition
                        && parent.getChildAt(childPosition).equals(view);
            }
        };
    }
    

    详细说明

    您可以覆盖 describeTo 方法,以便通过附加到 Description 参数来提供易于理解的匹配器描述。您还需要将 describeTo 调用传播到父匹配器,这样它的描述也会被添加。

    @Override
    public void describeTo(Description description) {
        description.appendText("position " + childPosition + " of parent "); // Add this matcher's description.
        parentMatcher.describeTo(description); // Add the parentMatcher description.
    }
    

    接下来,您应该重写 matchesSafely,这将确定何时在视图层次结构中找到匹配项。当使用其父级匹配提供的父级匹配器的视图调用时,请检查该视图是否等于提供位置的子级。

    如果父级没有大于子级位置的 childCountgetChildAt 将返回 null 并导致测试崩溃。最好避免崩溃并允许测试失败,以便我们获得正确的测试报告和错误消息。

    @Override
    public boolean matchesSafely(View view) {
    if (!(view.getParent() instanceof ViewGroup)) return false; // If it's not a ViewGroup we know it doesn't match.
        ViewGroup parent = (ViewGroup) view.getParent();
    
        return parentMatcher.matches(parent) // Check that the parent matches.
                && parent.getChildCount() > childPosition // Make sure there's enough children.
                && parent.getChildAt(childPosition).equals(view); // Check that this is the right child.
    }
    

    【讨论】:

    • 很好的答案,感谢您非常详细的解释。
    【解决方案3】:

    如果你能得到父视图。可能是this link,它定义了一个匹配器来获取视图的第一个孩子可以给你一些线索。

         public static Matcher<View> firstChildOf(final Matcher<View> parentMatcher) {
            return new TypeSafeMatcher<View>() {
                @Override
                public void describeTo(Description description) {
                    description.appendText("with first child view of type parentMatcher");
                }
    
                @Override
                public boolean matchesSafely(View view) {       
    
                    if (!(view.getParent() instanceof ViewGroup)) {
                        return parentMatcher.matches(view.getParent());                   
                    }
                    ViewGroup group = (ViewGroup) view.getParent();
                    return parentMatcher.matches(view.getParent()) && group.getChildAt(0).equals(view);
    
                }
            };
        }
    

    【讨论】:

    • 如果需要获取第n个,应该使用getChildAt(n)。
    • 它对我有用。谢谢。我建议改进以重用该解决方案,使用名为 isChildOfAtPosition(@IdRes parentId: Int, position: Int) gist.github.com/luisfernandezbr/… 的匹配器
    【解决方案4】:

    虽然此线程中的答案确实有效,但我只是想指出,无需定义新的 Matcher 类即可获得特定视图的特定子级的句柄。

    您可以通过将 Espresso 提供的视图匹配器加入如下方法来实现:

    /**
     * @param parentViewId the resource id of the parent [View].
     * @param position the child index of the [View] to match.
     * @return a [Matcher] that matches the child [View] which has the given [position] within the specified parent.
     */
    fun withPositionInParent(parentViewId: Int, position: Int): Matcher<View> {
        return allOf(withParent(withId(parentViewId)), withParentIndex(position))
    }
    

    然后使用这个方法如下:

    onView(
        withPositionInParent(R.id.parent, 0)
    ).check(
        matches(withId(R.id.child))
    )
    

    【讨论】:

      猜你喜欢
      • 2011-01-13
      • 1970-01-01
      • 1970-01-01
      • 2021-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多