Skip to content

Most visited

Recently visited

navigation

Espresso recipes

This document describes how to set up a variety of common Espresso tests.

Matching a view next to another view

A layout could contain certain views that are not unique by themselves. For example, a repeating call button in a table of contacts could have the same R.id, contain the same text, and have the same properties as other call buttons within the view hierarchy.

For example, in this activity, the view with text "7" repeats across multiple rows:

A list activity showing 3 copies of the same view element
     inside a 3-item list

Often, the non-unique view will be paired with some unique label that’s located next to it, such as a name of the contact next to the call button. In this case, you can use the hasSibling() matcher to narrow down your selection:

onView(allOf(withText("7"), hasSibling(withText("item: 0"))))
    .perform(click());

Matching a view that is inside an action bar

The ActionBarTestActivity has two different action bars: a normal ActionBar and a contextual action bar that is created from a options menu. Both action bars have one item that is always visible and two items that are only visible in overflow menu. When an item is clicked, it changes a TextView to the content of the clicked item.

Matching visible icons on both of the action bars is straightforward, as shown in the following code snippet:

public void testClickActionBarItem() {
    // We make sure the contextual action bar is hidden.
    onView(withId(R.id.hide_contextual_action_bar))
        .perform(click());

    // Click on the icon - we can find it by the r.Id.
    onView(withId(R.id.action_save))
        .perform(click());

    // Verify that we have really clicked on the icon
    // by checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Save")));
}

The save button is on the action bar, at the top of the activity

The code looks identical for the contextual action bar:

public void testClickActionModeItem() {
    // Make sure we show the contextual action bar.
    onView(withId(R.id.show_contextual_action_bar))
        .perform(click());

    // Click on the icon.
    onView((withId(R.id.action_lock)))
        .perform(click());

    // Verify that we have really clicked on the icon
    // by checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Lock")));
}

The lock button is on the action bar, at the top of the activity

Clicking on items in the overflow menu is a bit trickier for the normal action bar as some devices have a hardware overflow menu button, which opens the overflowing items in an options menu, and some devices have a software overflow menu button, which opens a normal overflow menu. Luckily, Espresso handles that for us.

For the normal action bar:

public void testActionBarOverflow() {
    // Make sure we hide the contextual action bar.
    onView(withId(R.id.hide_contextual_action_bar))
        .perform(click());

    // Open the options menu OR open the overflow menu, depending on whether
    // the device has a hardware or software overflow menu button.
    openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());

    // Click the item.
    onView(withText("World"))
        .perform(click());

    // Verify that we have really clicked on the icon by checking
    // the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("World")));
}

The overflow menu button is visible, and a list appears beneath the
          action bar near the top of the screen

This is how this looks on devices with a hardware overflow menu button:

There is no overflow menu button, and a list appears near the bottom
          of the screen

For the contextual action bar it is really easy again:

public void testActionModeOverflow() {
  // Show the contextual action bar.
    onView(withId(R.id.show_contextual_action_bar))
        .perform(click());

    // Open the overflow menu from contextual action mode.
    openContextualActionModeOverflowMenu();

    // Click on the item.
    onView(withText("Key"))
        .perform(click());

    // Verify that we have really clicked on the icon by
    // checking the TextView content.
    onView(withId(R.id.text_action_bar_result))
        .check(matches(withText("Key")));
  }
}

The overflow menu button appears in the action bar, and the list of
          options appear underneath the action bar, near the top of the screen

See the full code for these samples: ActionBarTest.java.

Asserting that a view is not displayed

After performing a series of actions, you will certainly want to assert the state of the UI under test. Sometimes, this may be a negative case, such as when something is not happening. Keep in mind that you can turn any hamcrest view matcher into a ViewAssertion by using ViewAssertions.matches().

In the example below, we take the isDisplayed() matcher and reverse it using the standard not() matcher:

import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isDisplayed;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.Matchers.not;

onView(withId(R.id.bottom_left))
    .check(matches(not(isDisplayed())));

The above approach works if the view is still part of the hierarchy. If it is not, you will get a NoMatchingViewException and you need to use ViewAssertions.doesNotExist().

Asserting that a view is not present

If the view is gone from the view hierarchy—which can happen when an action caused a transition to another activity—you should use ViewAssertions.doesNotExist():

import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.doesNotExist;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;

onView(withId(R.id.bottom_left))
    .check(doesNotExist());

Asserting that a data item is not in an adapter

To prove a particular data item is not within an AdapterView you have to do things a little differently. We have to find the AdapterView we’re interested in and interrogate the data its holding. We don’t need to use onData(). Instead, we use onView() to find the AdapterView and then use another matcher to work on the data inside the view.

First the matcher:

private static Matcher<View> withAdaptedData(final Matcher<Object> dataMatcher) {
    return new TypeSafeMatcher<View>() {

        @Override
        public void describeTo(Description description) {
            description.appendText("with class name: ");
            dataMatcher.describeTo(description);
        }

        @Override
        public boolean matchesSafely(View view) {
            if (!(view instanceof AdapterView)) {
                return false;
            }

            @SuppressWarnings("rawtypes")
            Adapter adapter = ((AdapterView) view).getAdapter();
            for (int i = 0; i < adapter.getCount(); i++) {
                if (dataMatcher.matches(adapter.getItem(i))) {
                    return true;
                }
            }

            return false;
        }
    };
}

Then the all we need is onView() to find the AdapterView:

@SuppressWarnings("unchecked")
public void testDataItemNotInAdapter(){
    onView(withId(R.id.list))
          .check(matches(not(withAdaptedData(withItemContent("item: 168")))));
    }
}

And we have an assertion that will fail if an item that is equal to "item: 168" exists in an adapter view with the ID list.

For the full sample, look at the testDataItemNotInAdapter() method within AdapterViewTest.java.

Using a custom failure handler

Replacing the default FailureHandler in Espresso with a custom one allows for additional or different error handling, such as taking a screenshot or passing along extra debug information.

The CustomFailureHandlerTest example demonstrates how to implement a custom failure handler:

private static class CustomFailureHandler implements FailureHandler {
    private final FailureHandler delegate;

    public CustomFailureHandler(Context targetContext) {
        delegate = new DefaultFailureHandler(targetContext);
    }

    @Override
    public void handle(Throwable error, Matcher<View> viewMatcher) {
        try {
            delegate.handle(error, viewMatcher);
        } catch (NoMatchingViewException e) {
            throw new MySpecialException(e);
        }
    }
}

This failure handler throws a MySpecialException instead of a NoMatchingViewException and delegates all other failures to the DefaultFailureHandler. The CustomFailureHandler can be registered with Espresso in the setUp() method of the test:

@Override
public void setUp() throws Exception {
    super.setUp();
    getActivity();
    setFailureHandler(new CustomFailureHandler(getInstrumentation()
                                              .getTargetContext()));
}

For more information, see the FailureHandler interface and Espresso.setFailureHandler().

Targeting non-default windows

Android supports multiple windows. Normally, this is transparent to the users and the app developer, yet in certain cases multiple windows are visible, such as when an auto-complete window gets drawn over the main application window in the search widget. To simplify things, by default Espresso uses a heuristic to guess which Window you intend to interact with. This heuristic is almost always good enough; however, in rare cases, you’ll need to specify which window an interaction should target. You can do this by providing your own root window matcher, or Root matcher:

onView(withText("South China Sea"))
    .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
    .perform(click());

As is the case with ViewMatchers, we provide a set of pre-provided RootMatchers. Of course, you can always implement your own Matcher object.

Take a look at this MultipleWindowTest sample or the MultiWindowSample sample on GitHub.

Headers and footers are added to ListViews using the addHeaderView() and addFooterView() methods. To ensure Espresso.onData() knows what data object to match, make sure to pass a preset data object value as the second parameter to addHeaderView() and addFooterView(). For example:

public static final String FOOTER = "FOOTER";
...
View footerView = layoutInflater.inflate(R.layout.list_item, listView, false);
((TextView) footerView.findViewById(R.id.item_content)).setText("count:");
((TextView) footerView.findViewById(R.id.item_size)).setText(String.valueOf(data.size()));
listView.addFooterView(footerView, FOOTER, true);

Then, you can write a matcher for the footer:

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;

@SuppressWarnings("unchecked")
public static Matcher<Object> isFooter() {
    return allOf(is(instanceOf(String.class)), is(LongListActivity.FOOTER));
}

And loading the view in a test is trivial:

import static com.google.android.apps.common.testing.ui.espresso.Espresso.onData;
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
import static com.google.android.apps.common.testing.ui.espresso.sample.LongListMatchers.isFooter;

public void testClickFooter() {
    onData(isFooter())
        .perform(click());

    // ...
}

Take a look at the full code sample, found in the testClickFooter() method of AdapterViewTest.java.

This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)