0

My Android activity's menu is populated dynamically and i'd like to test the items are shown with Espresso. I know that there should be at least 1 item with title containing some title string "N" and at least 1 item with title string containing "M", eg.:

  • Item N1
  • Item N2
  • Item M1
  • Item M2

I'm getting AmbiguousViewMatcherException exception for the test:

openActionBarOverflowOrOptionsMenu( getInstrumentation().getTargetContext());

    // go to subitem level 1
    onView(
        allOf(
            withId(R.id.title),
            withText("Settings"),
            isDisplayed()))
                .perform(click());
    SystemClock.sleep(50);

    // go to subitem level 2
    onView(
        allOf(
            withId(R.id.title),
            withText("Item type"),
            isDisplayed()))
                .perform(click());
    SystemClock.sleep(50);

    // items are shown

    // assertions
    onView(
        allOf(withId(R.id.title),
            withText("N"),
            isDisplayed()))
                .check(matches(isDisplayed()));

    onView(
        allOf(withId(R.id.title),
            withText("M"),
            isDisplayed()))
                .check(matches(isDisplayed()));

What will be the right assertion meaning "at least 1 view with the following assertions (let's say "title contains ... ") is shown"?

I know i can catch the exception and actually it means that the test is passed, but i'd like to do the thing right.

4ntoine
  • 19,816
  • 21
  • 96
  • 220

2 Answers2

2

As far as I know this is not that easy in espresso. You have to use a custom matcher to get one of the matching views and then perform your checks.

So if you use this custom matcher:

public static Matcher<View> withIndex(final Matcher<View> matcher, final int index) {
    return new TypeSafeMatcher<View>() {
        int currentIndex = 0;

        @Override
        public void describeTo(Description description) {
            description.appendText("with index: ");
            description.appendValue(index);
            matcher.describeTo(description);
        }

        @Override
        public boolean matchesSafely(View view) {
            return matcher.matches(view) && currentIndex++ == index;
        }
    };
}

then you could check for the first view with text "M" like:

   withIndex(allOf(withId(R.id.title), withText("M"), 
             isDisplayed())), 0)
             .matches(isDisplayed());

This code is taken from here: https://stackoverflow.com/a/39756832/2567799. Another option would be to write the matcher that it just returns the first element.

Community
  • 1
  • 1
stamanuel
  • 3,731
  • 1
  • 30
  • 45
0

In my particular case i decided to catch an exception and fail if it's not thrown:

try {
    onView(
        allOf(
            withId(R.id.title),
            withText(containsString("N")),
            isDisplayed()))
        .check(matches(isDisplayed()));
    fail("We should have multiple suitable items, so AmbiguousViewMatcherException exception should be thrown");
} catch (AmbiguousViewMatcherException e) {
    // that's ok - we have multiple items with "N" in the title
}
4ntoine
  • 19,816
  • 21
  • 96
  • 220
  • 1
    I suggest you use a custom matcher like I described below instead of catching the exception. You can not entirely be sure what caused the exception while this matcher returns the first element that matches your description. So you actually CAN be sure that there is a view element that matches. Furthermore catching exceptions is certainly not clean code since they are not not intended to control logic flow like this. – stamanuel May 05 '17 at 09:30