使用Espresso在索引处选择子视图

iju*_*nes 30 android android-espresso

使用带有子图像视图的自定义小部件视图时,使用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}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ues 60

 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);
      }
    };
  }
Run Code Online (Sandbox Code Playgroud)

使用它

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

  • 在最后一条指令中使用`return parentMatcher.matches(view.getParent())&& view.equals(group.getChildAt(childPosition))`会更安全,以避免在父节点中有足够的子视图时出现NullPointerException (5认同)

esk*_*ski 23

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

解决方案是创建一个自定义Matcher <View>,它为父视图包装另一个Matcher <View>,并使子视图的索引匹配.

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);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

详细说明

您可以覆盖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.
}
Run Code Online (Sandbox Code Playgroud)

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

如果父级没有大于子位置的childCount,则getChildAt将返回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.
}
Run Code Online (Sandbox Code Playgroud)


Nic*_*ong 11

如果您可以获得父视图.可能是这个链接定义了一个匹配器来获取视图的第一个孩子可以给你一些线索.

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

            }
        };
    }
Run Code Online (Sandbox Code Playgroud)

  • 如果您需要获取第 n 个,则应使用 getChildAt(n)。 (2认同)

Adi*_*ain 9

虽然该线程中的答案确实有效,但我只是想指出,可以获取特定视图的特定子级的句柄,而无需定义新类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))
}
Run Code Online (Sandbox Code Playgroud)

您将按如下方式调用此方法:

onView(
    withPositionInParent(R.id.parent, 0)
).check(
    matches(withId(R.id.child))
)
Run Code Online (Sandbox Code Playgroud)

  • 在发布的解决方案中,这个是最优雅的。谢谢你! (2认同)