Introduction

During Google I/O ‘16 we’ve heard an impressive list of new features to be included in the newest Android Studio 2.2. You can check this cool video here with all the details: What’s new in Android development tools – Google I/O 2016

Android Studio 2.2 introduces many new tools that make developer’s life definitely easier. Layout Editor and Constraint Layout, Firebase plugin integration, Jack compiler, and much more. One of these novelties is also Espresso Test Recorder. This new feature lets us record user interactions on device or emulator, and then creates fully working Espresso tests code. Then you can run this tests on your device, your Continuous Integration or in Firebase Test Lab. Let’s try this!

What is Espresso Test Recorder?

Espresso is a framework for UI testing. It’s the part of Android Testing Support Library for simulating user interactions in the application. To create the test, normally you’ll need to write code on your own. Thanks to Espresso Test Recorder, part of this job can be done automatically.

To run this new feature you need:

Android Studio 2.2 Preview 3

Also, you need to add Gradle dependencies for Espresso:

androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' 1 2 androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'

Espresso in practice

To start with test recording you need to open existing project. In my case, it was an application for making notes, which has one simple screen containing two AutoCompleteTextViews, Button and RecyclerView with notes. In the opened project select Run -> Record Espresso Test

Then you’ll need to select target device for running the app. You can choose the physical device or emulator. Next, you’ll see “Record Your Test” window and the app will run on your device.

In this window, you’ll see every interaction you’ve made on the screen, every taping or text typing. We haven’t done anything yet, so all we see is the message “No events recorded yet”. If I would make some interaction, e.g. I would type some text and tap on the button in the app, I see new events:

These events tell me that I typed texts “Note title” and “Note message” into two TextViews and then tapped on “Add note” button.

If we want to check if we ‘ve added new note correctly, we can make an assertion. You could tap on “Add Assertion” button that creates a clickable screenshot of your current screen. You can select an element and add an assertion to it. For example, I can select some TextView and Espresso Test Recorder tool will suggest me to assert that “id/text_note_title text is Note title”

For now, there’re no many automatic assertions available. You can only choose:

for View: exists/does not exist

for TextView and subclasses: text is/exists/does not exists

If you want some additional assertions, you’ll need to write it in the code on your own.

After making some events, we can finish test by tapping on “Complete Recording”. In the next window, we’ll be asked to choose the name for our test (there’s restriction that one UI test is created in one class). Then if we don’t have Espresso dependencies, we’ll be asked to add them:

If we have, Android Studio will generate test class for us:

MainActivityTest.java package pl.droidsonroids.espressotest; import android.support.test.espresso.ViewInteraction; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.allOf; @LargeTest @RunWith(AndroidJUnit4.class) public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class); @Test public void mainActivityTest() { ViewInteraction appCompatAutoCompleteTextView = onView(allOf(withId(R.id.text_note_title), isDisplayed())); appCompatAutoCompleteTextView.perform(click()); ViewInteraction appCompatAutoCompleteTextView2 = onView(allOf(withId(R.id.text_note_title), isDisplayed())); appCompatAutoCompleteTextView2.perform(replaceText("Note title")); ViewInteraction appCompatAutoCompleteTextView3 = onView(allOf(withId(R.id.text_note_message), isDisplayed())); appCompatAutoCompleteTextView3.perform(replaceText("Note message")); ViewInteraction appCompatButton = onView(allOf(withId(R.id.button_add_note), withText("Add note"), isDisplayed())); appCompatButton.perform(click()); ViewInteraction textView = onView(allOf(withId(R.id.text_note_title), withText("Note title"), childAtPosition(childAtPosition(withId(R.id.recycler_view_notes), 0), 0), isDisplayed())); textView.check(matches(withText("Note title"))); } private static Matcher<View> childAtPosition(final Matcher<View> parentMatcher, final int position) { return new TypeSafeMatcher<View>() { @Override public void describeTo(Description description) { description.appendText("Child at position " + position + " in parent "); parentMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { ViewParent parent = view.getParent(); return parent instanceof ViewGroup && parentMatcher.matches(parent) && view.equals(((ViewGroup) parent).getChildAt(position)); } }; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package pl . droidsonroids . espressotest ; import android . support . test . espresso . ViewInteraction ; import android . support . test . rule . ActivityTestRule ; import android . support . test . runner . AndroidJUnit4 ; import android . test . suitebuilder . annotation . LargeTest ; import android . view . View ; import android . view . ViewGroup ; import android . view . ViewParent ; import org . hamcrest . Description ; import org . hamcrest . Matcher ; import org . hamcrest . TypeSafeMatcher ; import org . junit . Rule ; import org . junit . Test ; import org . junit . runner . RunWith ; import static android . support . test . espresso . Espresso . onView ; import static android . support . test . espresso . action . ViewActions . click ; import static android . support . test . espresso . action . ViewActions . replaceText ; import static android . support . test . espresso . assertion . ViewAssertions . matches ; import static android . support . test . espresso . matcher . ViewMatchers . isDisplayed ; import static android . support . test . espresso . matcher . ViewMatchers . withId ; import static android . support . test . espresso . matcher . ViewMatchers . withText ; import static org . hamcrest . Matchers . allOf ; @LargeTest @RunWith ( AndroidJUnit4 . class ) public class MainActivityTest { @Rule public ActivityTestRule <MainActivity> mActivityTestRule = new ActivityTestRule < > ( MainActivity . class ) ; @Test public void mainActivityTest ( ) { ViewInteraction appCompatAutoCompleteTextView = onView ( allOf ( withId ( R . id . text_note_title ) , isDisplayed ( ) ) ) ; appCompatAutoCompleteTextView . perform ( click ( ) ) ; ViewInteraction appCompatAutoCompleteTextView2 = onView ( allOf ( withId ( R . id . text_note_title ) , isDisplayed ( ) ) ) ; appCompatAutoCompleteTextView2 . perform ( replaceText ( "Note title" ) ) ; ViewInteraction appCompatAutoCompleteTextView3 = onView ( allOf ( withId ( R . id . text_note_message ) , isDisplayed ( ) ) ) ; appCompatAutoCompleteTextView3 . perform ( replaceText ( "Note message" ) ) ; ViewInteraction appCompatButton = onView ( allOf ( withId ( R . id . button_add_note ) , withText ( "Add note" ) , isDisplayed ( ) ) ) ; appCompatButton . perform ( click ( ) ) ; ViewInteraction textView = onView ( allOf ( withId ( R . id . text_note_title ) , withText ( "Note title" ) , childAtPosition ( childAtPosition ( withId ( R . id . recycler_view_notes ) , 0 ) , 0 ) , isDisplayed ( ) ) ) ; textView . check ( matches ( withText ( "Note title" ) ) ) ; } private static Matcher <View> childAtPosition ( final Matcher <View> parentMatcher , final int position ) { return new TypeSafeMatcher <View> ( ) { @Override public void describeTo ( Description description ) { description . appendText ( "Child at position " + position + " in parent " ) ; parentMatcher . describeTo ( description ) ; } @Override public boolean matchesSafely ( View view ) { ViewParent parent = view . getParent ( ) ; return parent instanceof ViewGroup && parentMatcher.matches(parent) && view.equals(((ViewGroup) parent).getChildAt(position)); } } ; } }

As you can see, Android Studio also generated ViewMatcher for finding view at some position, in this case, used for finding the first note in our RecyclerView. It’s very time-saving.

Limitations

Because Espresso Test Recorder is still in experimental phase, it has some issues too. The most crucial in my opinion are:

limited assertions number – in most cases, we’re able to assert only that some UI element exists or doesn’t exist (the only exception is TextView with additional assertion “text is”), so we need to write other assertions in the code

no support for IdlingResources, so if you have some animation on view or some long-running operation, you’ll need to handle it by yourself, because Espresso Test Recorder doesn’t know how long should wait and the test will fail because of unknown view

no support for UI interactions that are outside of your code, so Facebook, Twitter, Google+ and other external sources of UI like WebViews, Google Play dialogs, etc. need to be handled on your own

Summary

Espresso Test Recording is great for someone who hasn’t ever tried UI testing. You don’t need to know how to use Espresso for writing simple tests. You can also check how they should look like (the proper annotations, the naming convention). And in some cases, it’s definitely quicker to record the test rather than write it, mainly if we have lists with many items.

However, Espresso Test Recording has still many limitations that I’ve described above. And it doesn’t save so much time, especially when we need to type a lot of text (which takes really a lot of time in test recording mode).

Despite all these, I encourage you to try this new feature and see if this is helpful for you.