Skip to main content

Discovering Espresso for Android: wading through the hierarchical thicket

The Android Lollipop update brought a hard nut to crack for the testers who use Espresso, represented by RecyclerView.

So far the app I'm testing contains this element in couple of places. While I was writing tests for them I found out that it was not so easy and obvious. Thanks to Espresso authors, version 2.0 has the basic RecyclerView actions support which is honestly not enough and is not convenient sometimes.

The example of RecyclerView action:
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
.
.
    @Test
    public void testSomething() {
        onView(withId(R.id.recycler_view_id)).perform(actionOnItemAtPosition(1, click()));
    }
The activity under test has the following structure - it contains the RecyclerView element which is populated with some data. Let's call it the Feed. Each element in Feed is the Feed Post represented by FrameLayout. Of course every FrameLayout has the same layout elements inside as it's neighbors. It contains header with text and footer with like ImageView and TextView used to show number of likes.

What I just described represented by below image:



Now let's try to implement this test case:
  1. click the like ImageView in 3rd RecyclerView item (FameLayout 3), 
  2. check that number of likes changed after the click. 

In my tests I don't want to rely on the Post position in the RecyclerView, because I run multiple tests in parallel and some of them can create new Posts and push down the target one. I'd like to rely on some unique element of the Post instead.

Now I'll focus on the layout. Target ImageView and TextView are marked in red color.


Actually I can't click the like image (ImageView with id=like_image) directly, based on it's id, since this will lead us to multiple matches issue.

But what if we climb in the FrameLayout hierarchy up (from like ImageView) until we found any element with the unique value. In our case the unique is the text in Post header (TextView with id=header_text).

Now we'll find the way how to tie like image with header text. We'll go up in the hierarchy from like image using withParent() method until we reach footer (LinearLayout with id=footer)  which is the sibling of header (LinearLayout with id=header), which contains child with unique text (follow arrows on the image). And the code is:
private void clickOnLikeIconOnPostWithText(String postText){
    onView(allOf(withParent(
            allOf(withParent(
                    allOf(hasSibling(
                            allOf(withId(R.id.header), withChild(withText(postText)))),
                        withId(R.id.footer))),
                withId(R.id.footer_like))),
        withId(R.id.like_image))).perform(click());
}
And the similar code for checking the like text value (TextView with id=like_text):
private void checkLikesText(Matcher<View> likeTextMatcher, String postText){
    onView(allOf(withParent(
            allOf(withParent(
                    allOf(hasSibling(
                            allOf(withId(R.id.header), withChild(withText(postText)))),
                        withId(R.id.footer))),
                withId(R.id.footer_like))),
        withId(R.id.like_text))).check(matches(likeTextMatcher));
}
Finally the usage is:
@Test
public void testPostLike() {
    //like - 1 like count
    clickOnLikeIconOnPostWithText("UNIQUE_TEXT_3");
    checkLikesText(withText("1"),"UNIQUE_TEXT_3");
}
Looks a bit unusual and complicated but it works.

Popular posts from this blog

Espresso & UIAutomator - the perfect tandem

Espresso for Android is perfect and fast test automation framework, but it has one important limitation - you are allowed to operate only inside your app under test context.

That means that it is not possible to automate tests for such app features like:

application push notificationscontact synchronizationnavigating from another app to your app under test,
since you have to deal with other apps from the mobile device - Notification Bar, Contacts or People app, etc. 
In fact it wasn't possible until the release of UIAutomator 2.0. As stated in Android Developers blog post - "...Most importantly, UI Automator is now based on Android Instrumentation...".  And because of that we can run UIAutomator tests as well as Espresso tests using Instrumentation test runner.

In addition to that we can combine UIAutomator tests together with Espresso tests and this gives us the real power and control over the phone and application under test.

In the below example I'll explain  how …

Discovering Espresso for Android: matching and asserting view with text.

After more than a month of using great test tool from Google - Espresso for Android, I'd like to share with you some of my experience. I assume that you've already added espresso jar into your project, spent some time playing with Espresso samples and have basic understanding how this tool works.

In this post I'll show how to match particular view with text or assert that it contains (or not) specified Strings. Before we start, you have to take a look at Hamcrest matchers - Hamcrest tutorial and API Reference Documentation, which are used together with Espresso's ViewAssertions and ViewMatchers and included into Espresso standalone library. Pay more attention to Matcher<java.lang.String> matchers.

So, here we go. For simplicity following String "XXYYZZ" will be used as a expected text pattern.
Espresso ViewMatchers class implements two String matcher methods withText() and withContentDescription() which will match a view which text is equal to specified…

Discovering Espresso for Android: swiping.

Hi, for today I have some tips about swiping ViewActions in Espresso for Android.

As you may know the latest Espresso release contains new swipeLeft and swipeRight ViewActions. They both are really useful when you'd like to swipe between activity fragments, tab layouts or any other UI elements.

You can use it as any other view action:
onView(withId(R.id.viewId)).perform(swipeRight());
But be aware that doing this you will operate on a view, in our case R.id.viewId, but not on the screen size. That means that to swipe right or left, for example between fragments you have to deal with some parent layout or maybe list view.

If you take a look inside Espresso's ViewActions.java class you will see below code for swipeRight() method:
  public static ViewAction swipeRight() {     return new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,         GeneralLocation.CENTER_RIGHT, Press.FINGER);   }
As you may guess GeneralLocation.CENTER_LEFT and GeneralLocation.CENTER_RIGHT…