In Espresso, how to avoid AmbiguousViewMatcherException when multiple views match

前端 未结 9 1440
情深已故
情深已故 2020-11-30 03:16

Having gridView which has some images. The gridView\'s cell comes out from same predefined layout, which has same id and desc.

R.id.item_image == 2131

相关标签:
9条回答
  • 2020-11-30 03:20

    You should use onData() to operate on GridView:

    onData(withId(R.id.item_image))
            .inAdapterView(withId(R.id.grid_adapter_id))
            .atPosition(0)
            .perform(click());
    

    This code will click on the image inside first item in GridView

    0 讨论(0)
  • 2020-11-30 03:24

    Tried @FrostRocket answer as was looking most promissing but needed to add some customisations:

    public static Matcher<View> withIndex(final Matcher<View> matcher, final int index) {
        return new TypeSafeMatcher<View>() {
            int currentIndex;
            int viewObjHash;
    
            @SuppressLint("DefaultLocale") @Override
            public void describeTo(Description description) {
                description.appendText(String.format("with index: %d ", index));
                matcher.describeTo(description);
            }
    
            @Override
            public boolean matchesSafely(View view) {
                if (matcher.matches(view) && currentIndex++ == index) {
                    viewObjHash = view.hashCode();
                }
                return view.hashCode() == viewObjHash;
            }
        };
    }
    
    0 讨论(0)
  • 2020-11-30 03:27

    I was amazed that I couldn't find a solution by simply providing an index along with a matcher (i.e. withText, withId). The accepted answer only solves the problem when you're dealing with onData and ListViews.

    If you have more than one view on the screen with the same resId/text/contentDesc, you can choose which one you want without causing an AmbiguousViewMatcherException by using 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;
            }
        };
    }
    

    For example:

    onView(withIndex(withId(R.id.my_view), 2)).perform(click());
    

    will perform a click action on the third instance of R.id.my_view.

    0 讨论(0)
  • 2020-11-30 03:27

    I created a ViewMatcher which matches the first view it finds. Maybe it is helpful for somebody. E.g. when you don't have an AdapterView to use onData() on.

    /**
     * Created by stost on 15.05.14.
     * Matches any view. But only on first match()-call.
     */
    public class FirstViewMatcher extends BaseMatcher<View> {
    
    
       public static boolean matchedBefore = false;
    
       public FirstViewMatcher() {
           matchedBefore = false;
       }
    
       @Override
       public boolean matches(Object o) {
           if (matchedBefore) {
               return false;
           } else {
               matchedBefore = true;
               return true;
           }
       }
    
       @Override
       public void describeTo(Description description) {
           description.appendText(" is the first view that comes along ");
       }
    
       @Factory
       public static <T> Matcher<View> firstView() {
           return new FirstViewMatcher();
       }
    }
    

    Use it like this:

     onView(FirstViewMatcher.firstView()).perform(click());
    
    0 讨论(0)
  • 2020-11-30 03:30

    Also not specifically related to grid view but in case it may be useful to someone else I had a similar problem where both an element on my RecyclerView and on my root layout had the same id and were both displayed to the screen. What helped me to solve was to check descendancy such as:

     onView(allOf(withId(R.id.my_view), not(isDescendantOfA(withId(R.id.recyclerView))))).check(matches(withText("My Text")));
    

    0 讨论(0)
  • 2020-11-30 03:31

    Not completely related to grid view situation but you can use hamcrest allOf matchers to combine multiple conditions:

    import static org.hamcrest.CoreMatchers.allOf;
    
    onView(allOf(withId(R.id.login_password), 
                 withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
            .check(matches(isCompletelyDisplayed()))
            .check(matches(withHint(R.string.password_placeholder)));
    
    0 讨论(0)
提交回复
热议问题