Selecting child view at index using Espresso

While the answers in this thread do work, I just wanted to point out that it is possible to get a handle on a particular child of a particular view without having to define a new Matcher class.

You can do so by joining up the view matchers offered by Espresso into a method as follows:

/**
 * @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))
}

And then use this method as follows:

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

If you can get the parent view. May be this link which defined a matcher to get the first child of a view can give you some clue.

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

            }
        };
    }

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.
}

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