Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Gaurav Singh
Gaurav Singh

Posted on • Originally published atautomationhacks.io on

Hello, espresso! Part 2 Working with lists

Espresso logo and the title Hello, espresso! Part 2 Working with lists
Photo by Pro Android Dev

In the previous post, we read an introduction to espresso, and understood how to write a basic test flow in espresso. We also saw the structure of an espresso test

In this post, we’ll dive into how to automate tests and work with list like components using espresso. Examples of such components areAdapterView,RecyclerView etc

Let’s go! 🏃🏃‍♀️

Working with lists in general

Using espresso, we can scroll to or perform an action on a given item in a list

This is needed since the view you are interested in may not be present on the screen since Android lists created withRecyclerView orAdapterView have only a small no of child elements created and they are recycled as you scroll. For these use casesscrollTo method won’t work as that needs an existing view

Let’s see an example and walk through how we could use espresso to scroll and act on elements for these types of components

Working withAdapterView

We’ll useDataAdapterSample for this post. You can find the app and test code atthispath in my forked Github repo

Understanding the app under test

Once you load the project in android studio and let gradle build it, you can run the app and see it load up on the emulator

Running an app in android studio

Let’s understand the flow we want to automate a bit better

  • we have aListView component where each row has aTextView and aToggleButton
  • You can scroll in this list
  • Once you tap on a row, there is aLinearLayout at the top that’s set with the row no

Data Adapter sample app

Assume that we want to automate this flow

Using Layout Inspector to figure out the app structure

You don’t need to dive into the source code to figure out how everything would render on the UI when the app is built

Android studio provides a convenient tool calledLayout Inspector , that allows us to inspect theComponent Tree. (Kind of similar toAppium Inspector) You can open it by going toTools > Layout Inspector , You’ll also need to select the process you want to inspect (In this case you can selectDataAdapterSample)

Open layout inspector

Open layout inspector

Understanding the UI

Understanding layout inspector

The layout inspector has 3 main sections:

  1. Component Tree: Here we can see the tree like structure that makes up our current screen, we can observe theListView withTextView andButtonand the staticLinearLayout on top
  2. Live App: This section is refreshed as you interact with your app in the emulator, you can select any particular row and it would highlight the same in Component Tree and also show the attributes
  3. Attributes: Here we can see all the attributes or properties of the given element and use these while automating our tests in espresso

Writing our test for AdapterView

Let’s write our tests to perform the actions mentioned above

We’ll use a series of learning tests to explore how to test different aspects of this app and test and also learn espresso’s API a bit better, in a real test you may just write one test to perform the workflow you intend to test.

You can see the complete test fileherewith some helpful comments explaining what each test is supposed to do

Test control to be scrolled to is not visible

To start we want to create the usual structure i.e. Write our classDataAdapterPractice with@RunWith annotation and useActivityScenarioRuleto start ourLongListActivity activity

@RunWith(AndroidJUnit4.class)@LargeTestpublic class DataAdapterPractice {    @Rule    public ActivityScenarioRule<LongListActivity> rule = new ActivityScenarioRule<>(LongListActivity.class);    ...
Enter fullscreen modeExit fullscreen mode

Before we try scrolling, it may be helpful to verify that the item that we want to scroll to (say a TextView with text:item: 99) does not exist, we can do that with below line:

onView(withText("item: 99")).check(doesNotExist());
Enter fullscreen modeExit fullscreen mode

Here is the complete test:

@RunWith(AndroidJUnit4.class)@LargeTestpublic class DataAdapterPractice {    @Rule    public ActivityScenarioRule<LongListActivity> rule = new ActivityScenarioRule<>(LongListActivity.class);    @Test    public void whenAppOpens_ThenLastItemIsNotDisplayed() {        onView(withText("item: 99")).check(doesNotExist());    }}
Enter fullscreen modeExit fullscreen mode

Test we are able to scroll to last item

Next, we want to be able to scroll to the last item with textitem: 99, we can figure out this text by actually scrolling in the app and then seeing the value oftext attribute in the layout inspector

If we see the code forLongListActivitywe can see that theListView gets its data from an adapterLongListAdapterthat has a hash map keys likeROW_TEXT andROW_ENABLED

We can use this understanding to write our matcher to find this row

  • To scroll to such an element we useonData instead ofonView (since the element is not displayed on the view)
  • In our test, We want to find the element whoseROW_TEXT isitem:99 and we can do so usinghasEntry Hamcrest matcher that is able to match elements in a hash map
onData(hasEntry(equalTo(LongListActivity.ROW_TEXT), is("item: 99")))
Enter fullscreen modeExit fullscreen mode
  • Espresso would take care of automatically scrolling to this element for us
  • We can then close the flow by checking that such a row is visible by using belowViewAssertion
.check(matches(isCompletelyDisplayed());
Enter fullscreen modeExit fullscreen mode

Below is how the complete test looks like:

@RunWith(AndroidJUnit4.class)@LargeTestpublic class DataAdapterPractice {    @Rule    public ActivityScenarioRule<LongListActivity> rule = new ActivityScenarioRule<>(LongListActivity.class);    @Test    public void whenScrollToLastItem_ThenLastItemIsDisplayed() {        // We use onData since we want to scroll to an item in the list view        // we use hasEntry matcher that takes two args, first the item check        // and second the value        onData(hasEntry(equalTo(LongListActivity.ROW_TEXT), is("item: 99")))                // Then we check that this entry is displayed                .check(matches(isCompletelyDisplayed()));    }}
Enter fullscreen modeExit fullscreen mode

Click on a row and verify the LinearLayout has expected test

If the user taps on a particular element in the ListView then the app updates the row no in a separate TextView with id:selection_row_value

We can repeat similar steps to scroll to the element with text value 30, tap on it and then check if the TextView at the top is updated with the correct value

To click on a child element inside a ListView we can useonChildView() method like below:

.onChildView(withId(R.id.rowContentTextView)).perform(click());
Enter fullscreen modeExit fullscreen mode

Below is how the complete test looks like:

@RunWith(AndroidJUnit4.class)@LargeTestpublic class DataAdapterPractice {    @Rule    public ActivityScenarioRule<LongListActivity> rule = new ActivityScenarioRule<>(LongListActivity.class);    @Test    public void whenClickOnRow_ThenTheTextIsUpdated() {        String itemToClickOn = "item: 30";        onData(hasEntry(equalTo(LongListActivity.ROW_TEXT), is(itemToClickOn)))                // To click on an element in the list use `onChildView`                .onChildView(withId(R.id.rowContentTextView)).perform(click());        // Now that we are on desired item, we can verify using onView method        String expectedValueAfterClick = "30";        onView(withId(R.id.selection_row_value)).check(matches(withText(expectedValueAfterClick)));    }}
Enter fullscreen modeExit fullscreen mode

Working withRecyclerView

RecyclerView is a different type of list which ensures when the user scrolls off a screen, it recycles elements in an efficient manner. We do not useonData in this case. To understand howRecyclerView works you could readthis post on Android developers

To work withRecyclerView, we can useespresso-contrib package in our app’s gradle dependencies

androidTestImplementation 'androidx.test.espresso:espresso-contrib:' + rootProject.espressoVersion;
Enter fullscreen modeExit fullscreen mode

The dependency supports below actions:

Scrolling in aRecyclerView

  • scrollTo() - Scroll to matched view
  • scrollToHolder() - Scroll to matched view holder
  • scrollToPosition() - Scroll to specific position

Performing action on element

  • actionOnItem() - Perform view action on matched view
  • actionOnHolderItem() - Perform view action on a matched View holder
  • actionOnItemAtPosition() - Perform a view action on a view at a specific position

App under test

For these tests, we’ll useRecyclerViewSampletest app

RecyclerView

In this test app, we have aRecyclerView where each row is aTextView having text likeThis is element #42, as the user scrolls the same elements are recycled and reused by Android framework

Learning tests

Let’s write some tests for this app

Test that a given element is not present in the list and espresso throws an exception

We’ll start with a negative test, what if we try to scroll to an element that does not exist, we should expect espresso framework to throw an exception in this case, this test also is a good way to demo thescrollTo method inRecyclerViewActions

  • We useonView to find our RecyclerView (getting its id via layout inspector)
onView(withId(R.id.recyclerView))
Enter fullscreen modeExit fullscreen mode
  • We then usescrollTo method inRecyclerViewActions and usehasDescendantViewMatcher to check that a hypothetical element with textnot on the listis present. Naturally this throws an exception and we handle that by adding@Test(expected = PerformException.class) in the JUnit test assertion
.perform(RecyclerViewActions.scrollTo(hasDescendant(withText("not on the list"))));
Enter fullscreen modeExit fullscreen mode

Below is th how the complete test looks like:

@RunWith(AndroidJUnit4.class)public class RecyclerViewSamplePracticeTest {  @Rule  public ActivityScenarioRule activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class);  /**   * This is a negative test that tries to scroll to a descendant that does not exist in the app We   * use Junit @Test annotation to verify that this test throws a PerformException   */  @Test(expected = PerformException.class)  public void whenAppOpens_ThenItemWithTextIsNotVisible() {    onView(withId(R.id.recyclerView))        // Here scrollTo will fail if there are no items that match expected descriptions        .perform(RecyclerViewActions.scrollTo(hasDescendant(withText("not on the list"))));  }}
Enter fullscreen modeExit fullscreen mode

Test we can scroll to a fixed position and click on it and check its displayed

Let’s say we want to scroll into our list to the 40th item and click on it, we useactionOnItemAtPosition method to specify the position and also add theclick() method to click that

onView(withId(R.id.recyclerView))        .perform(RecyclerViewActions.actionOnItemAtPosition(itemBelowFold, click()));
Enter fullscreen modeExit fullscreen mode

We can then check if the item with expected text is displayed using below:

String expectedText = String.format("This is element #%d", itemBelowFold);onView(withText(expectedText)).check(matches(isDisplayed()));
Enter fullscreen modeExit fullscreen mode

Below is how the complete test looks like:

@RunWith(AndroidJUnit4.class)public class RecyclerViewSamplePracticeTest {  @Rule  public ActivityScenarioRule activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class);  /**   * Test to scroll in a recycler view to an item at a fixed position And verify that the element   * with expected text is displayed   */  @Test  public void whenScrollToItemAtAPosition_ThenItemIsDisplayed() {    int itemBelowFold = 40;    onView(withId(R.id.recyclerView))        .perform(RecyclerViewActions.actionOnItemAtPosition(itemBelowFold, click()));    String expectedText = String.format("This is element #%d", itemBelowFold);    onView(withText(expectedText)).check(matches(isDisplayed()));  }}
Enter fullscreen modeExit fullscreen mode

Test we can scroll to the middle using a custom matcher

Our app has a special row in the middle with a text:This is the middle!, let’s say we want to scroll to this elements holder and then verify its displayed

RecyclerView with the middle

We can usescrollToHolder and pass it a custom matcher to verify if it has reached the middle

onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()));
Enter fullscreen modeExit fullscreen mode

Let’s see howisInTheMiddle() method is implemented

We create a newTypeSafeMatcher of typeCustomAdapter.ViewHolder which overridden implementation ofmatchesSafely that returnsgetIsInTheMiddle()methods output. Note: The support for this method is added in apps source code

private static TypeSafeMatcher<CustomAdapter.ViewHolder> isInTheMiddle() {    return new TypeSafeMatcher<CustomAdapter.ViewHolder>() {      @Override      public void describeTo(Description description) {        description.appendText("item in the middle");      }      @Override      protected boolean matchesSafely(CustomAdapter.ViewHolder viewHolder) {        return viewHolder.getIsInTheMiddle();      }    };  }
Enter fullscreen modeExit fullscreen mode

To see the app impl supporting this, seeCustomAdapterclass that has a methodonBindViewHolder which sets this flag inviewHolder.setIsInTheMiddle(true); if theposition == mDataSet.size() / 2

Finally, we can check if the view is displayed using:

String expectedText = String.format("This is element #%d", itemBelowFold);onView(withText(expectedText)).check(matches(isDisplayed()));
Enter fullscreen modeExit fullscreen mode

Please see the complete test below:

@RunWith(AndroidJUnit4.class)public class RecyclerViewSamplePracticeTest {  @Rule  public ActivityScenarioRule activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class);  /**   * This test scrolls in recycler view till it finds an element with text: "This is the middle!" It   * uses a field already set in the View holder implementation to determine it has reached the   * point And a custom hamcrest matcher   */  @Test  public void whenScrollToItemInTheMiddle_ThenCheckItemWithSpecialTextIsDisplayed() {    onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()));    onView(withText("This is the middle!")).check(matches(isDisplayed()));  }  private static TypeSafeMatcher<CustomAdapter.ViewHolder> isInTheMiddle() {    return new TypeSafeMatcher<CustomAdapter.ViewHolder>() {      @Override      public void describeTo(Description description) {        description.appendText("item in the middle");      }      @Override      protected boolean matchesSafely(CustomAdapter.ViewHolder viewHolder) {        return viewHolder.getIsInTheMiddle();      }    };  }}
Enter fullscreen modeExit fullscreen mode

Further reads

You can readEspresso lists on Android developers

Conclusion

Hopefully this post gives you an idea on how to work with list like components in espresso. Stay tuned for next post where we’ll dive into how to automate and work withintents with espresso

As always, Do share this with your friends or colleagues and if you have thoughts or feedback, I’d be more than happy to chat over at twitter or comments. Until next time. Happy Testing and coding.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Principal SDET @ CRED, ex Meta, Gojek | 13+ years of hands-on solving deep testing problems and leading engineering teams
  • Location
    India
  • Work
    Software Engineer
  • Joined

More fromGaurav Singh

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp