From 34fb906c1aedd11a8a942fa068421153e1dccdc2 Mon Sep 17 00:00:00 2001
From: Eric Schmidt
Testing user interactions
within a single app helps to ensure that users do not
- encounter unexpected results or have a poor experience when interacting with your app.
- You should get into the habit of creating user interface (UI) tests if you need to verify
+ encounter unexpected results or have a poor
+ experience when interacting with your app.
+ You should get into the habit of creating
+ user interface (UI) tests if you need to verify
that the UI of your app is functioning correctly.
The Espresso testing framework, provided by the
Android Testing Support Library,
- provides APIs for writing UI tests to simulate user interactions within a
- single target app. Espresso tests can run on devices running Android 2.2 (API level 8) and
- higher. A key benefit of using Espresso is that it provides automatic synchronization of test
- actions with the UI of the app you are testing. Espresso detects when the main thread is idle,
- so it is able to run your test commands at the appropriate time, improving the reliability of
- your tests. This capability also relieves you from having to adding any timing workarounds,
- such as a sleep period, in your test code.
+ provides APIs for writing UI tests to simulate
+ user interactions within a
+ single target app. Espresso tests can run on
+ devices running Android 2.3.3 (API level 10) and
+ higher. A key benefit of using Espresso is
+ that it provides automatic synchronization of test
+ actions with the UI of the app you are testing.
+ Espresso detects when the main thread is idle,
+ so it is able to run your test commands
+ at the appropriate time, improving the reliability of
+ your tests. This capability also relieves you
+ from having to add any timing workarounds,
+ such as {@link java.lang.Thread#sleep(long) Thread.sleep()}
+ in your test code.
- The Espresso testing framework is an instrumentation-based API and works
- with the
+ The Espresso testing framework is
+ an instrumentation-based API and works with the
{@code
AndroidJUnitRunner} test runner.
Before building your UI test with Espresso, make sure to configure your test source code
-location and project dependencies, as described in
-
-Getting Started with Testing.
+ Before building your UI test with Espresso,
+ make sure to configure your test source code
+ location and project dependencies, as described in
+ Getting Started with Testing.
+ In the {@code build.gradle} file of your Android app module, you must set a dependency
- reference to the Espresso library:
+ In the {@code build.gradle} file of your Android app
+ module, you must set a dependency
+ reference to the Espresso library:
+ Turn off animations on your test device — leaving system animations turned on in the test
-device might cause unexpected results or may lead your test to fail. Turn off animations from
-Settings by opening Developing Options and turning all the following options off:
+
+ Turn off animations on your test device —
+ leaving system animations turned on in the test
+ device might cause unexpected results or may
+ lead your test to fail. Turn off animations from
+ Settings by opening Developer options
+ and turning all the following options off:
+
-
@@ -59,31 +53,40 @@ trainingnavtop=true
class="external-link">Android Testing Codelab
-
+
dependencies {
- ...
- androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
+ // Other dependencies ...
+ androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}
-
-
+
+
If you want to set up your project to use Espresso features other than what the core API +
+ If you want to set up your project to use Espresso + features other than what the core API provides, see this resource.
-- To create an Espresso test, create a Java class that follows this programming model: -
++ To create an Espresso test, create a Java + class that follows this programming model: +
+ +- These steps are covered in more detail in the sections below. -
++ These steps are covered in more detail in the sections below. +
-- The following code snippet shows how your test class might invoke this basic workflow: -
++ The following code snippet shows how your test + class might invoke this basic workflow: +
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
-
-The following section describes how to create a new Espresso test in the JUnit 4 style and use
-
-{@code ActivityTestRule} to reduce the amount of boilerplate code you need to write. By using
-
-{@code ActivityTestRule}, the testing framework launches the activity under test
-before each test method annotated with @Test and before any method annotated with
-@Before. The framework handles shutting down the activity after the test finishes
-and all methods annotated with @After are run.
@Test and before any method annotated with
+ @Before. The framework handles
+ shutting down the activity after the test finishes
+ and all methods annotated with @After are run.
+
package com.example.android.testing.espresso.BasicSample;
@@ -234,103 +271,57 @@ public class ChangeTextBehaviorTest {
}
- The following section describes how to migrate to Espresso if you have existing test classes -subclassed from {@link android.test.ActivityInstrumentationTestCase2} and you don't want to rewrite -them to use JUnit4.
-Note: For new UI tests, we strongly recommend that you write your -test in the JUnit 4 style and use the - -{@code ActivityTestRule} class, instead of -{@link android.test.ActivityInstrumentationTestCase2}.
-- If you are subclassing {@link android.test.ActivityInstrumentationTestCase2} - to create your Espresso test class, you must inject an - {@link android.app.Instrumentation} instance into your test class. This step is required in - order for your Espresso test to run with the - {@code AndroidJUnitRunner} - test runner. -
+ -- To do this, call the - {@link android.test.InstrumentationTestCase#injectInstrumentation(android.app.Instrumentation) injectInstrumentation()} - method and pass in the result of - - {@code InstrumentationRegistry.getInstrumentation()}, as shown in the following code - example: -
- --import android.support.test.InstrumentationRegistry; +- -+ Accessing UI Components +
-public class MyEspressoTest - extends ActivityInstrumentationTestCase2<MyActivity> { - - private MyActivity mActivity; - - public MyEspressoTest() { - super(MyActivity.class); - } - - @Before - public void setUp() throws Exception { - super.setUp(); - injectInstrumentation(InstrumentationRegistry.getInstrumentation()); - mActivity = getActivity(); - } - - ... -} -
Note: Previously, {@link android.test.InstrumentationTestRunner} -would inject the {@link android.app.Instrumentation} instance, but this test runner is being -deprecated.
- -- Before Espresso can interact with the app under test, you must first specify the UI component - or view. Espresso supports the use of -Hamcrest matchers - for specifying views and adapters in your app. -
++ Before Espresso can interact with the app + under test, you must first specify the UI component + or view. Espresso supports the use of + Hamcrest matchers + for specifying views and adapters in your app. +
-- To find the view, call the - {@code onView()} - method and pass in a view matcher that specifies the view that you are targeting. This is - described in more detail in Specifying a View Matcher. - The - {@code onView()} method returns a - - {@code ViewInteraction} - object that allows your test to interact with the view. - However, calling the - {@code onView()} method may not work if you want to locate a view in - an {@link android.widget.AdapterView} layout. In this case, follow the instructions in - Locating a view in an AdapterView instead. -
++ To find the view, call the + {@code onView()} + method and pass in a view matcher that + specifies the view that you are targeting. This is + described in more detail in + Specifying a View Matcher. + The {@code onView()} + method returns a + {@code ViewInteraction} + object that allows your test to interact with the view. + However, calling the + + {@code onView()} + method may not work if you want to locate a view in + an {@link android.support.v7.widget.RecyclerView} layout. + In this case, follow the instructions in + Locating a view in an AdapterView + instead. +
-- Note: The - {@code onView()} method does not check if the view you specified is - valid. Instead, Espresso searches only the current view hierarchy, using the matcher provided. - If no match is found, the method throws a - - {@code NoMatchingViewException}. -
++ Note: + The {@code onView()} + method does not check if the view you specified is + valid. Instead, Espresso searches only the + current view hierarchy, using the matcher provided. + If no match is found, the method throws a + {@code NoMatchingViewException}. +
-- The following code snippet shows how you might write a test that accesses an - {@link android.widget.EditText} field, enters a string of text, closes the virtual keyboard, - and then performs a button click. -
++ The following code snippet shows how you might write a test that accesses an + {@link android.widget.EditText} field, + enters a string of text, closes the virtual keyboard, + and then performs a button click. +
public void testChangeText_sameActivity() {
@@ -344,231 +335,464 @@ public void testChangeText_sameActivity() {
}
- - You can specify a view matcher by using these approaches: -
++ You can specify a view matcher by using these approaches: +
-+
onView(withText("Sign-in"));
-Similarly you can call - -{@code withId()} and providing the resource ID ({@code R.id}) of the view, as shown in the -following example:
++ Similarly you can call + {@code withId()} + and providing the resource ID ({@code R.id}) of the view, + as shown in the + following example: +
onView(withId(R.id.button_signin));
- Android resource IDs are not guaranteed to be unique. If your test attempts to match to a + Android resource IDs are not guaranteed to be unique. + If your test attempts to match to a resource ID used by more than one view, Espresso throws an - - {@code AmbiguousViewMatcherException}. + {@code AmbiguousViewMatcherException}.
-
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
-You can use the {@code not} keyword to filter for views that don't correspond to the matcher, as -shown in the following example:
+ ++ You can use the {@code not} keyword to filter + for views that don't correspond to the matcher, as + shown in the following example: +
+
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
-To use these methods in your test, import the {@code org.hamcrest.Matchers} package. To -learn more about Hamcrest matching, see the -Hamcrest site. -
-- To improve the performance of your Espresso tests, specify the minimum matching information - needed to find your target view. For example, if a view is uniquely identifiable by its - descriptive text, you do not need to specify that it is also assignable from the - {@link android.widget.TextView} instance. + To use these methods in your test, + import the {@code org.hamcrest.Matchers} package. To + learn more about Hamcrest matching, see the + Hamcrest site.
++ To improve the performance of your Espresso tests, + specify the minimum matching information + needed to find your target view. For example, + if a view is uniquely identifiable by its + descriptive text, you do not need to specify + that it is also assignable from the + {@link android.widget.TextView} instance. +
-- In an {@link android.widget.AdapterView} widget, the view is dynamically populated with child - views at runtime. If the target view you want to test is inside an - {@link android.widget.AdapterView} - (such as a {@link android.widget.ListView}, {@link android.widget.GridView}, or - {@link android.widget.Spinner}), the - - {@code onView()} method might not work because only a - subset of the views may be loaded in the current view hierarchy. -
+ -- Instead, call the {@code onData()} - method to obtain a - - {@code DataInteraction} - object to access the target view element. Espresso handles loading the target view element - into the current view hierarchy. Espresso also takes care of scrolling to the target element, - and putting the element into focus. -
++ In an {@link android.widget.AdapterView} widget, + the view is dynamically populated with child + views at runtime. If the target view you want to test is inside an + {@link android.widget.AdapterView} + (such as a {@link android.widget.ListView}, + {@link android.widget.GridView}, or + {@link android.widget.Spinner}), the + {@code onView()} + method might not work because only a + subset of the views may be loaded in the current view hierarchy. +
-- Note: The +
+ Instead, call the {@code onData()} - method does not check if if the item you specified corresponds with a view. Espresso searches - only the current view hierarchy. If no match is found, the method throws a - - {@code NoMatchingViewException}. -
+ method to obtain a + {@code DataInteraction} + object to access the target view element. + Espresso handles loading the target view element + into the current view hierarchy. Espresso + also takes care of scrolling to the target element, + and putting the element into focus. + -- The following code snippet shows how you can use the - {@code onData()} - method together - with Hamcrest matching to search for a specific row in a list that contains a given string. - In this example, the {@code LongListActivity} class contains a list of strings exposed - through a {@link android.widget.SimpleAdapter}. -
++ Note: The + {@code onData()} + method does not check if the item you + specified corresponds with a view. Espresso searches + only the current view hierarchy. If no match is found, the method throws a + {@code NoMatchingViewException}. +
+ ++ The following code snippet shows how you can use the + {@code onData()} + method together + with Hamcrest matching to search for a specific + row in a list that contains a given string. + In this example, the {@code LongListActivity} class + contains a list of strings exposed + through a {@link android.widget.SimpleAdapter}. +
onData(allOf(is(instanceOf(Map.class)),
- hasEntry(equalTo(LongListActivity.ROW_TEXT), is(str))));
+ hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input")));
- - Call the {@code ViewInteraction.perform()} - or - {@code DataInteraction.perform()} - methods to - simulate user interactions on the UI component. You must pass in one or more - {@code ViewAction} - objects as arguments. Espresso fires each action in sequence according to - the given order, and executes them in the main thread. -
+- The - {@code ViewActions} - class provides a list of helper methods for specifying common actions. - You can use these methods as convenient shortcuts instead of creating and configuring - individual {@code ViewAction} - objects. You can specify such actions as: -
++ Call the + {@code ViewInteraction.perform()} + or + {@code DataInteraction.perform()} + methods to + simulate user interactions on the UI component. You must pass in one or more + {@code ViewAction} + objects as arguments. Espresso fires each action in sequence according to + the given order, and executes them in the main thread. +
-+ The + {@code ViewActions} + class provides a list of helper methods for specifying common actions. + You can use these methods as convenient shortcuts + instead of creating and configuring individual + {@code ViewAction} + objects. You can specify such actions as: +
-- If the target view is inside a {@link android.widget.ScrollView}, perform the - {@code ViewActions.scrollTo()} - action first to display the view in the screen before other proceeding - with other actions. The - {@code ViewActions.scrollTo()} - action will have no effect if the view is already displayed. -
++ If the target view is inside a {@link android.widget.ScrollView}, + perform the + {@code ViewActions.scrollTo()} + action first to display the view in the screen before other proceeding + with other actions. The + {@code ViewActions.scrollTo()} + action will have no effect if the view is already displayed. +
-- Call the - {@code ViewInteraction.check()} - or - {@code DataInteraction.check()} - method to assert - that the view in the UI matches some expected state. You must pass in a - - {@code ViewAssertion} object as the argument. If the assertion fails, Espresso throws - an {@link junit.framework.AssertionFailedError}. -
+ -- The +
+ Espresso Intents + enables validation and stubbing of intents sent out by an app. + With Espresso Intents, you can test an app, activity, or service in isolation + by intercepting outgoing intents, stubbing the result, and sending it back to + the component under test. +
+ ++ To begin testing with Espresso Intents, you need + to add the following line to your app's build.gradle file: +
+ +
+dependencies {
+ // Other dependencies ...
+ androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
+}
+
+
++ To test an intent, you need to create an instance of the + IntentsTestRule + class, which is very similar to the + ActivityTestRule + class. + The + IntentsTestRule + class initializes Espresso Intents before each test, + terminates the host activity, + and releases Espresso Intents after each test. +
+ ++ The test class shown in the following codes snippet provides a simple + test for an explicit intent. It tests the activities and intents created in the + Building Your First App + tutorial. +
+ +
+@Large
+@RunWith(AndroidJUnit4.class)
+public class SimpleIntentTest {
+
+ private static final String MESSAGE = "This is a test";
+ private static final String PACKAGE_NAME = "com.example.myfirstapp";
+
+ /* Instantiate an IntentsTestRule object. */
+ @Rule
+ public IntentsTestRule≶MainActivity> mIntentsRule =
+ new IntentsTestRule≶>(MainActivity.class);
+
+ @Test
+ public void verifyMessageSentToMessageActivity() {
+
+ // Types a message into a EditText element.
+ onView(withId(R.id.edit_message))
+ .perform(typeText(MESSAGE), closeSoftKeyboard());
+
+ // Clicks a button to send the message to another
+ // activity through an explicit intent.
+ onView(withId(R.id.send_message)).perform(click());
+
+ // Verifies that the DisplayMessageActivity received an intent
+ // with the correct package name and message.
+ intended(allOf(
+ hasComponent(hasShortClassName(".DisplayMessageActivity")),
+ toPackage(PACKAGE_NAME),
+ hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)));
+
+ }
+}
+
+
++ For more information about Espresso Intents, see the + Espresso Intents + documentation on the Android Testing Support Library site. + You can also download the + IntentsBasicSample and + IntentsAdvancedSample + code samples. +
+ + + ++ Espresso Web allows you to test {@link android.webkit.WebView} components + contained within an activity. It uses the + WebDriver API to inspect and control the + behavior of a {@link android.webkit.WebView}. +
+ ++ To begin testing with Espresso Web, you need + to add the following line to your app's build.gradle file: +
+ +
+dependencies {
+ // Other dependencies ...
+ androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'
+}
+
+
++ When creating a test using Espresso Web, you need to enable + JavaScript on the {@link android.webkit.WebView} when you instantiate the + ActivityTestRule + object to test the activity. In the tests, you can select + HTML elements displayed in the + {@link android.webkit.WebView} and simulate user interactions, like + entering text into a text box and then clicking a button. After the actions + are completed, you can then verify that the results on the + Web page match the results that you expect. +
+ +
+ In the following code snippet, the class tests
+ a {@link android.webkit.WebView} component with the id value 'webview'
+ in the activity being tested.
+ The verifyValidInputYieldsSuccesfulSubmission() test selects an
+ <input> element on the
+ Web page, enters some text, and checks text that appears in
+ another element.
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class WebViewActivityTest {
+
+ private static final String MACCHIATO = "Macchiato";
+ private static final String DOPPIO = "Doppio";
+
+ @Rule
+ public ActivityTestRule mActivityRule =
+ new ActivityTestRule(WebViewActivity.class,
+ false /* Initial touch mode */, false /* launch activity */) {
+
+ @Override
+ protected void afterActivityLaunched() {
+ // Enable JavaScript.
+ onWebView().forceJavascriptEnabled();
+ }
+ }
+
+ @Test
+ public void typeTextInInput_clickButton_SubmitsForm() {
+ // Lazily launch the Activity with a custom start Intent per test
+ mActivityRule.launchActivity(withWebFormIntent());
+
+ // Selects the WebView in your layout.
+ // If you have multiple WebViews you can also use a
+ // matcher to select a given WebView, onWebView(withId(R.id.web_view)).
+ onWebView()
+ // Find the input element by ID
+ .withElement(findElement(Locator.ID, "text_input"))
+ // Clear previous input
+ .perform(clearElement())
+ // Enter text into the input element
+ .perform(DriverAtoms.webKeys(MACCHIATO))
+ // Find the submit button
+ .withElement(findElement(Locator.ID, "submitBtn"))
+ // Simulate a click via JavaScript
+ .perform(webClick())
+ // Find the response element by ID
+ .withElement(findElement(Locator.ID, "response"))
+ // Verify that the response page contains the entered text
+ .check(webMatches(getText(), containsString(MACCHIATO)));
+ }
+}
+
+
++ For more information about Espresso Web, see the + Espresso + Web documentation on the Android Testing Support Library site.. + You can also download this code snippet as part of the + Espresso Web code sample. +
+ + + ++ Call the + {@code ViewInteraction.check()} + or + {@code DataInteraction.check()} + method to assert + that the view in the UI matches some expected state. You must pass in a + {@code ViewAssertion} + object as the argument. If the assertion fails, Espresso throws + an {@link junit.framework.AssertionFailedError}. +
+ ++ The {@code ViewAssertions} - class provides a list of helper methods for specifying common - assertions. The assertions you can use include: -
+ class provides a list of helper methods for specifying common + assertions. The assertions you can use include: + -+ The following code snippet shows how you might + check that the text displayed in the UI has + the same value as the text previously entered in the + {@link android.widget.EditText} field. +
-- The following code snippet shows how you might check that the text displayed in the UI has - the same value as the text previously entered in the - {@link android.widget.EditText} field. -
public void testChangeText_sameActivity() {
// Type text and then press the button.
@@ -580,14 +804,22 @@ public void testChangeText_sameActivity() {
}
--You can run Espresso tests from Android Studio or -from the command-line. Make sure to specify - - {@code AndroidJUnitRunner} as the default instrumentation runner in your project. + You can run Espresso tests from + Android Studio or + from the command-line. Make sure to specify + {@code AndroidJUnitRunner} + as the default instrumentation runner in your project.
+-To run your Espresso test, follow the steps for running instrumented tests -described in -Getting Started with Testing.
+ To run your Espresso test, follow the steps for running instrumented tests + described in + Getting Started with Testing. + -- cgit v1.2.3-59-g8ed1b