The Espresso testing framework, released by Google in October of 2013, is part of the Android Testing Support Library - a set of APIs that allow you to build and run test code for your android applications. This library provides tools such as JUnit 4, UI Automator, and, of course, Espresso.
The Test Recorder
At Google IO 2016, a new tool for Espresso was demonstrated: a recording tool that allows the user to run the app on the device or emulator of their choice and record user interaction directly, and then translating that interaction into editable and reusable test code.
At the time of this blog (early August 2016), this feature is not yet deployed into the stable, release build of Android Studio; it is instead available starting in Android Studio 2.2 Preview 3, which can be downloaded through the Dev or Canary release channel.
As of September 1st, the Android Studio 2.2 RC build was released to the Canary Channel and is a more stable version that includes the Espresso Test Recorder tool. It will be slowly propagated up to the other channels (Dev, Beta, and eventually Stable). You can read the release announcement here.
Table of Contents
Automated UI Testing Made Quick and Easy with Android Studio's Espresso Test Recorder
- About Espresso
- The Test Recorder
- Table of Contents
- How to Use Espresso Test Recorder
- What This Tool Can Do
- What This Tool Can Not Do
- Example Code and References
How to Use Espresso Test Recorder
Once you have Android Studio 2.2 Preview 3 (or higher) installed, it's time to start recording your Espresso Test!
Where to Find the Tool
You can find the tool in the top Application menu - go to:
Run -> Record Espresso Test
A dialog will appear where you will select your target device.
Selecting a Target Device (Emulator vs Physical Device)
According to the documentation, Espresso Test Recorder will support a variety of devices, including physical devices, virtual devices, and even cloud devices! If you need to, go ahead and create a new Emulator, or connect your physical device. Now all you have to do is select it under Connected Devices.
Recording the Test
For my app, I've got 4 simple buttons taking you to different parts of the app. The first, top-left button, called "Colors", takes you to a colors sample page that allows the user to view a set of color palettes, and then copy the hex values to their clipboard.
I'm going to create three tests:
We're going to to tap the "Colors" button (which will take us into the
ColorsActivity, and then tap on the first palette (the blue one). According to my app, the user "selects" the color by tapping on the palette (a CardView containing the color and its lighter and darker accents), which will then pop up a Snackbar message, telling the user that they've selected that color. That's it. Two taps. We're going to add the assertions for this test manually.
The Floating Action Bar, or FAB, at the top of the screen is to copy the selected color to the clipboard by showing a NEW Snackbar with a Copy action. Now we're going to repeat the first two steps of the first test, and then we'll tap the FAB, tap the copy action inside of the Snackbar message that pops up. Now we'll verify the text has, indeed, been added to the clipboard by pasting it in a edit text at the bottom of the screen, and then verifying the text is what we expect.
We know that if we tap the FAB after a palette has been selected that a Snackbar will appear with the color we have selected and an action to copy the hex value of the color. But what if we haven't selected a palette yet? This time we're going to Tap the "Colors" button to go to the
ColorsActivity, but instead of selecting a color, we're going to tap the FAB immediately to make sure our in app error handling is working.
Time to Record
Now it's time to create a test (Test 1). Keeping it simple - all we're going to do is a two tap recording - then we're going to add a custom assertion inside of the code that generated for us after pressing Complete Recording.
Once you have the Record Your Test dialog pop up, it's time to start pressing buttons on your device (or emulator).
In our case, we'll tap the "Colors" button to go to the
ColorsActivity, and then tap the first color-palette CardView - the blue one.
After you press Complete Recording, another dialog titled Pick a test class name for your test will appear. We're going to name this
ColorsActivityTest since it will be testing the functionality of the
ColorsActivity in my app.
The Generated Test Code
The Espresso Test Recorder tool has now created and opened our complete test! Wow! You have a whole Espresso test built with a few clicks and taps! But that's not all. You can edit the test however you'd like, just as if you were writing Espresso tests without the recorder.
We'll be adding a custom assertion next.
Editing Generated Code
Now that we have our first interactions written for us, we want to make sure the Snackbar that pops up after we tap the blue color palette shows the correct text!
You can easily test this with one assertion. Let's take a look:
We've added a new assertion:
onView(allOf(withId(R.id.snackbar_text), withText("Selected Blue with Hex of #ff00a1e0"), isDisplayed())).check(matches(isDisplayed()));
Running Your Tests
Once you've finalized your UI tests, you can run them very easily by right-clicking on the test class inside the Project Tool Window, and select:
You can also run the test by opening the file and using the hotkey (on a Mac):
[control] + [R]
Now you can simply select the deployment target device to run the test on, and hit 'OK'
Once the tests complete, you can see whether or not they've passed. We see that the one simple test we've written has passed - all green! Let's take a look at the other two test and get recording.
Two More Tests
As mentioned previously, we want to make sure of our ability to add text to the clip board and then paste it into our edit text succeeds.
-Spoiler- -- Espresso cannot actually handle pressing the system "Paste" button, since it's a system menu, akin to a standard Android Context Menu. The recorder will (successfully) add the line to test it, but running the test will FAIL. Not covered in this tutorial is another tool called UI Automator that is better suited for this action. In the mean time, we'll comment out the line in the generated code that attempts to invoke the "Paste" button, and leave the line where the recorder conveniently writes code that replaces the text using "replaceText" in order to have the test pass.
Now back to the tutorial.
Before we add assertions, let's record our actions up to the point where we've pasted the text into our EditText:
1) Tap the "Colors" button to get to the
2) Tap the blue color pallet to select it
3) Tap the FAB to show the Snackbar indication our selected color and showing the action to copy the hex value to out Clipboard
4) Tap the "Copy Hex Value" action inside the Snackbar
5) Tap inside of the EditText to request focus
6) Long press inside of the EditText to show the system "Paste" button
7) Tap the "Paste" button
Now we're going to build our first assertion using the recorder. In the recorder, click the Add Assertion button. This will cause the recorder to gather a snapshot of the screen showing on the device (or emulator) and present that snapshot with selectable views for which to create the assertion. You can also select the view from the dropdown that says "Select and element from screenshot".
After clicking on or selecting the EditText view, the recorder gives a list of assertions to apply to the view. Luckily, the one we want is the default - "text is". The value we want to assert is ALSO the default - the blue hex value we pasted.
Click Save Assertion and then Complete Recording, and the test name dialog will once more pop up. Unfortunately, there doesn't seem to be a way to add the test to an existing class, and typing in the same name causes an error complaining that the test already exists - so, for now, we'll call it
Remember to comment out or delete these lines:
ViewInteraction appCompatButton2 = onView( allOf(withText("Paste"), withContentDescription("Paste"), isDisplayed())); appCompatButton2.perform(click());
If you wanted to test this with just Espresso, you could create another button to perform a manual paste into the EditText using the ClipboardManager. Then you could remove the generated code (that replaces the contents of the EditText with our expected results) shown below:
ViewInteraction appCompatEditText2 = onView( allOf(withId(R.id.et_color_edit_text), withParent(withId(R.id.ll_color_cards)), isDisplayed())); appCompatEditText2.perform(replaceText("#ff00a1e0"));
With multiple tests, you have options: you can either cut/paste the new test method called
colorsActivityCopyPastingTest() into our existing
ColorsActivityTest and delete the
ColorsActivityCopyPastingTest class, or you can keep it separate. I personally wanted to keep them in one class since they're all testing the
ColorsActivity. Now the next time you run the test class, all the
@Test methods in the class will run sequentially.
-Note- -- I should mention that the assertion for clicking the action inside of the Snackbar will sometimes not register in the test and the test will fail if you have not turned off animations on your device. Espresso does not play well with animations and Google suggests turning off window animations, transition animations, and animator durations. See more about that here. Normally, if you need a pause (beyond animation), Espresso has methods for handling idling with your own classes. You would not use
Tread.sleep(), but instead implement your own classes using Espresso's IdlingResource class to handle things with delays or asynchronous execution. There is a nice tutorial on automatically disabling animations on test devices using a custom test Rule, permission, and manually granting that permission via Gradle. You can find that tutorial here.
Test 3 is simple - we want to make sure that if no color palette is selected and the user taps the FAB, the Snackbar shows our error handling message instead of the selected color and an option to copy the hex value to the clipboard.
-Spoiler 2- -- once we've created this method, if we keep it in the same class as the other test, this test should run first, since the selected color state remains from taping the blue palette in the other test, which would fail our conditions for this test if it comes after them. You can override some methods with Espresso to set states how you'd like, but for now, I'm going to be lazy and just put it first.
Let's get recording again:
1) Tap the "Colors" button to get to the
2) Tap the FAB button
That's it. Click Complete Recording in the Test Recorder and just like last time, let's call it something unique (I called it "ColorsActivityErrorHandlingTest") and then cut/paste the new
@Test method into our original test class and delete the gutted, newly generated class. Remember to put this test method first.
Also we're going to add in the code for checking the snack bar that we did in the first test:
onView(allOf(withId(R.id.snackbar_text), withText("Please select a color"), isDisplayed())).check(matches(isDisplayed()));
All Together Now
Now you can repeat running the test by right clicking on the
ColorsActivityTest class and clicking Run 'ColorsActivityTest' in the context menu. This will run all three tests and give you the results.
What This Tool Can Do
The Espresso Test Recorder is meant to save time for the developers writing the automated UI tests, and does a lot for you by taking away a large chunk of repeated UI integration steps, and allows introducing alternate flows to do the same things. The examples I gave were very bare bones and were more focused on introducing the tool itself - but there is plenty more you can do with the tool.
The main time saver of the Test Recorder is its ability to grab the UI layout and parse which view you want to add assertions to. Instead of having to determine what the position of child item is in a list or what ID a view has manually, the recorder takes the dump of the UI layout and determines that for you. Later on, if you're not satisfied with how the view is matched in the generated code, you can modify it to be more to your liking.
Types of Assertions
Depending on the type of component you select, there are a variety of assertions available to you, even though they are currently somewhat limited. Here are a few assertions that I discovered while using the tool:
1) exists - this assertion can be used on just about any view. It's converted into the
matches() assertion in the generated code.
2) does not exists - this assertion can be used on just about any view. It's converted into the
doesNotExist() assertion in the generated code.
3) text is - this assertion can be used on about any text-based view - such as an EditText or a TextView, though I noticed it was missing for a Button. It's converted into a
matches(withText("[SOME_TEXT]")) assertion in the generated code.
Types of Interactions
The recorder is capable, as shown, of recording taps on the screen (but not always long presses, *see below), but I can also record several other interactions. So here are some of the interactions I found are recordable:
1) Tapping on components (such as buttons)
2) Long Pressing components (such as on EditText)
3) Typing into an EditText is easily recorded
4) Taping on a Spinner (Dropdown menu) and selecting an item
What This Tool Can Not Do
Presently, the recorder was unable to change interactions once they occurred. If you clicked on the wrong element, or click too many times on a button, there was no way to remove or edit that event from inside the recorder. You would have to either restart, loosing all your steps, or modify the generated code after completing the recording.
Duplicating steps or moving steps are also capabilities missing from the tool. I would bet these basic interactions will become available in the future, but for now they are missing.
Missing Types of Assertions
I could not find any options for other assertions beyond
doesNotExist() assertions. The
withText() method is actually a View Matcher, not an assertion, so the text is option in the recorder is just a matcher/assertion combo.
Other View Assertions, such as
selectedDescendantsMatch(), Layout Assertions, such as
noEllipseizedText(), or Position Assertions, such as
isLeftOf(), do not exist in the tool yet. However, I would assume many will be on the way.
Missing Types of Interactions
The recorder, I noticed, was sometimes unable to determine a long press from a tap. Espresso accepts long presses, so it's easy to change in the generated code. The recorder is capable and sometimes does record the long press - simply be aware while recording your test that the long press may not be recognized.
Swiping through a ViewPager also seems to be ignored in recording. Espresso can actually handle swiping, but the recorder does not pick up on this interaction.
Scrolling is also not currently recordable through the tool - but Espresso itself does allow scrolling through a
The tools is also unable to detect system components, such as the copy or paste buttons. If you want to add an assertion to those components, the tool will not be able to find it in the view hierarchy dump. The gotcha here is that pressing the paste or copy button would actually be recorded, but the generated test would fail when running since Espresso cannot find the view.
The Espresso Test Recorder is a extremely useful tool for writing UI automated testing. Espresso can run on Jenkins and works well with other continuous integration tools, making this a great way to quickly write test coverage for your application's UI. Using this tool also has the expressly important point of approaching your app from a user perspective, writing tests from a flow that the user would take. This alone makes for great test writing.
Other than making test writing faster - it also does so in an easy to read way. The generated code is very easy to understand, even if sometimes the naming conventions are a bit simplistic. Since it's editable, that's easy to fix.
One of main pain points I found using this tool was there was no way to add new tests functions to an existing test class, bringing some unneccessary overhead to the tool. I also found that it didn't work well with standard android UI interactions, such as the Snackbar dialog or the Paste dialog.
Over all - the Espresso Test Recorder is a huge improvement to Android studio, a great feature that I'm glad they showed off at Google IO 2016. Please check out the link to the IO demo below, along with some other great reference material, and, of course, the demo code I used for this tutorial.
Example Code and References
About the Author
Jack is an Android Developer in CapTech's System Integration practice and is based out of their Richmond, VA office. He has a passion for enterprise level Android architecture and integrating micro-services with mobile.