androidandroid-espressoandroid-espresso-recorder

Espresso AmbiguousViewMatcherException when trying to click a icon (TextView)


LinearLayout (productList) is dynamically populated with child views at runtime, as below:

@ViewById(R.id.ll_products)
LinearLayout productList;

public void createProductList() {
    productList.addView(getView(mobilePhone))
    productList.addView(getView(internet))
    productList.addView(getView(television))
}

public View getView(Product product) {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    TextView layout = (LinearLayout) inflater.inflate(R.layout.row_product, null);
    TextView productIcon = (TextView) layout.findViewById(R.id.tv_product_row_icon);
    productIcon.setText(product.getProductIcon());
    productName.setText(product.getName());
}

productList is deliberately LinearLayout, not ListView.

Product list has three products - each product has icon (duplications are possible).

I would like to record a scenario where I click on the icon a second product. How do such a scenario avoiding AmbiguousViewMatcherException ?

Unfortunately, the following code will not work - three R.id.tv_product_row_icon will be found...

    ViewInteraction appCompatTextView = onView(withId(R.id.tv_product_row_icon));
    appCompatTextView.perform(scrollTo(), click());

How to specify that the second icon is to be clicked?


Solution

  • Unfortunately you will have to create a custom Matcher for such case.

    The following matches the View with specified index:

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

    Usage appropriate for your case:

    Matcher<View> secondIconMatcher = allOf(withId(R.id.tv_product_row_icon));
    onView(withIndex(secondIconMatcher , 1)).perform(click());