Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Selecting child view at index using Espresso

With Espresso when using a custom widget view with child image views, which Matcher type can I use to select the nth child? Example:

+--------->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}
like image 582
ijunes Avatar asked Jul 15 '14 00:07

ijunes


2 Answers

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

To use it

onView(nthChildOf(withId(R.id.parent_container), 0)).check(matches(withText("I am the first child")));
like image 114
Maragues Avatar answered Sep 18 '22 14:09

Maragues


To try and improve a little on Maragues's solution, I've made a few changes.

The solution is to create a custom Matcher<View> that wraps another Matcher<View> for the parent view, and takes the index of the child view to be matched.

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

Detailed Explanation

You can override the describeTo method in order to give an easy to understand description of the matcher by appending to the Description argument. You'll also want to propagate the describeTo call to the parent matcher so it's description also gets added.

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

Next, you should override matchesSafely which will determine when a match in the view hierarchy has been found. When called with a view whose parent matches the provided parent matcher, check that the view is equal to the child at the provided position.

If the parent doesn't have a childCount greater than the child position, getChildAt will return null and cause the test to crash. It's better to avoid crashing and allow the test to fail so that we get a proper test report and error message.

@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.
}
like image 29
eski Avatar answered Sep 21 '22 14:09

eski