36

I am trying to click on a text in a list view using Espresso. I know they have this guide, but I can't see how to make this work by looking for text. This is what I have tried

Espresso.onData(Matchers.allOf(Matchers.is(Matchers.instanceOf(ListView.class)), Matchers.hasToString(Matchers.startsWith("ASDF")))).perform(ViewActions.click());

As expected, this didn't work. The error said no view in hierarchy. Does anyone know how to select a String? ("ASDF" in this case) Thanks in advance.

Update due to @haffax

I received error:

com.google.android.apps.common.testing.ui.espresso.AmbiguousViewMatcherException: 'is assignable from class: class android.widget.AdapterView' matches multiple views in the hierarchy.

Second error

With this code

onData(hasToString(startsWith("ASDF"))).inAdapterView(withContentDescription("MapList")).perform(click());

I get this error

com.google.android.apps.common.testing.ui.espresso.PerformException: Error performing 'load adapter data' on view 'with content description: is "MapList"'.

Caused by: java.lang.RuntimeException: No data found matching: asString(a string starting with "ASDF")


Solution

onData(anything()).inAdapterView(withContentDescription("desc")).atPosition(x).perform(click())

Chad Bingham
  • 32,650
  • 19
  • 86
  • 115
  • The interesting bit of this exception is further below in the stack trace. There should be a "Caused by:" part in the whole stack trace. That describes what really went wrong. Please share the complete stack trace for analysis. – haffax Apr 10 '14 at 21:36
  • @haffax, Check that. That string is for sure there. – Chad Bingham Apr 10 '14 at 21:38
  • @haffax, however, there is a Linear layout first. – Chad Bingham Apr 10 '14 at 21:39
  • This is getting more complicated. So, you actualy don't have access to the adapted type, is this correct? – haffax Apr 10 '14 at 21:46
  • @haffax The adapter has a linear layout in the list items. the text is within that. – Chad Bingham Apr 10 '14 at 21:47
  • Do you know the type of what myListView.getAdapter().getItem(0) returns? – haffax Apr 10 '14 at 21:49
  • There is a difference between the View of an item at position 0 and the adapted data at the same position.If your Adapter is an ArrayAdapter, then the adapted data is of type String, but the View representing that item can be e.g. a TextView. For onData(myMatcher) myMatcher does not match the TextView, but it matches the String. – haffax Apr 10 '14 at 21:51
  • 3
    If you don't have access to the backing data type, then you need to go over the position of that item in the ListView, using onData(anything()).inAdapterView(...).atPosition(x).perform(click()) to click on the x'th item in the list. – haffax Apr 10 '14 at 21:53
  • @haffax `myListView.getAdapter().getItem(0)` would return a Relative layout, with 4 linear layouts contained in it – Chad Bingham Apr 11 '14 at 15:45
  • @haffax, would this work? – Chad Bingham Apr 15 '14 at 15:10
  • I assume anything() is a custom matcher you created. Care to share it with us as well? – suomi35 Dec 08 '14 at 15:56
  • I have been running into issues using a custom list view class that extends AdapterView. Been seeing this error. (java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints: is assignable from class: class android.widget.AdapterView) – wapples May 12 '16 at 18:52

3 Answers3

45

The problem is, that you try to match the list view itself with the instanceOf(ListView.class) as argument for onData(). onData() requires a data matcher that matches the adapted data of the ListView, not the ListView itself, and also not the View that Adapter.getView() returns, but the actual data.

If you have something like this in your production code:

ListView listView = (ListView)findViewById(R.id.myListView);
ArrayAdapter<MyDataClass> adapter = getAdapterFromSomewhere();
listView.setAdapter(adapter);

Then the Matcher argument of Espresso.onData() should match the desired instance of MyDataClass. So, something like this should work:

onData(hasToString(startsWith("ASDF"))).perform(click());

(You can use another Matcher using a method of org.hamcrest.Matchers)

In case you have multiple adapter views in your activity, you can call ViewMatchers.inAdapterView() with a view matcher that specifies the AdapterView like this:

onData(hasToString(startsWith("ASDF")))
    .inAdapterView(withId(R.id.myListView))
    .perform(click());
Vince
  • 1,570
  • 3
  • 27
  • 48
haffax
  • 5,898
  • 1
  • 32
  • 30
  • Answer extended to include solution for multiple AdapterViews in hierarchy. – haffax Apr 10 '14 at 14:48
  • I am getting the same error, but with less `**MATCHES**` – Chad Bingham Apr 10 '14 at 15:13
  • The two that match, seem to be the same though. The only difference I can see is one `is=focused=false` – Chad Bingham Apr 10 '14 at 15:26
  • Well, you'll have to sort the matching out yourself. Seems weird to have so many different list views with the same id. Can't you give your list views different IDs? Or is this because the are used in ListFragments? Other ways to further distinguish: Assign a tag to the view and use the hasTag() matcher or use isDescendantOfA() to distinguish via hierarchy. – haffax Apr 10 '14 at 16:48
  • Yeah I think it is odd too. i didn't write the app, I just test it. But yes, you're right, I can't give them different id's cause they are in ListFragments. I will look in to adding a tag. – Chad Bingham Apr 10 '14 at 18:27
  • If I look in the uiautomatorviewer, I only see one ListView. I don't get how its finding two?? – Chad Bingham Apr 10 '14 at 18:30
  • Alright, so I have given one list a content description. How could i click by text within content description? – Chad Bingham Apr 10 '14 at 20:25
  • Be aware that if you have e.g. nav drawer with list view, it can be a second adapter view! so as it was already said, `inAdapterView()` must be used in that case to differentiate between them – Tomask Apr 29 '16 at 19:02
3

If adapter have custom model class for example Item:

public static Matcher<Object> withItemValue(final String value) {
        return new BoundedMatcher<Object, Item>(Item.class) {
            @Override
            public void describeTo(Description description) {
                description.appendText("has value " + value);
            }

            @Override
            public boolean matchesSafely(Item item) {
                return item.getName().toUpperCase().equals(String.valueOf(value));
            }
        };
    }

Then call following:

onData(withItemValue("DRINK1")).inAdapterView(withId(R.id.menu_item_grid)).perform(click());
Veer
  • 2,071
  • 19
  • 24
  • this works for me on one screen, but on another screen, Espresso seems to scroll to that item, but the item is not clicked even if perform(click()) is called (tested this with breakpoints)....this is very weird – mbob May 09 '18 at 14:23
0
onData(hasEntry(equalTo(ListViewActivity.ROW_TEXT),is("List item: 25")))
        .onChildView(withId(R.id.rowTextView)).perform(click());

This works best for me with row text data..

Insane Skull
  • 9,220
  • 9
  • 44
  • 63
anuja jain
  • 1,367
  • 13
  • 19