0

I have a nested Recycler lists which don't have ~~scrolling~~ & nestedScrollingEnabled=false. I'm attempting to swipe up and click on the recycler item by it's text. Having issues with determining when to swipe and how far.

UPDATE: This may have scrolling, I may need to specify the ViewHolder of the Item with text instead of the view with text... Experimenting...

  • parent_recycler_list
    • recycler_list
      • List item A
      • List item B
    • recycler_list
      • List item A
      • List item B

So far I am able to find the item and try to click on it:

Espresso.onView(
            CoreMatchers.allOf(
                ViewMatchers.withId(R.id.recycler_list),
                ViewMatchers.hasDescendant(recyclerViewItemWithText(text))
            )
        ).perform(
            RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
                recyclerViewItemWithText(text),
                ViewActions.click()
            )
        )
fun recyclerViewItemWithText(text: String) = object : BoundedMatcher<View, View>(View::class.java) {
    override fun describeTo(description: Description?) {
        description?.appendText("Searching for text with: $text")
    }

    override fun matchesSafely(item: View?): Boolean {
        val views = ArrayList<View>()
        item?.findViewsWithText(views, text, View.FIND_VIEWS_WITH_TEXT)

        return when (views.size) {
            1 -> true
            else -> false
        }
    }
}

This only works by it self when the list item is displayed. I have tried to swipe until the view item is displayed:

Espresso.onView(ViewMatchers.withId(R.id.parent_recycler_list)).perform(
            ViewActions.repeatedlyUntil(
                ViewActions.swipeUp(),
                Matchers.allOf(
                    ViewMatchers.hasDescendant(ViewMatchers.withText(text)),
                    isCompletelyDisplayed()
                ), 10
            )
        )

This will always swipe at least once... and can swipe past the view item I'm looking for.

Is there a way I can be more precise in when and how far to swipe?

I'm a bit of a novice still and don't know much about custom swipe actions on view holders. Thanks

When trying to use nestedScrollTo()

java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints: (view has effective visibility=VISIBLE and is descendant of a: (is assignable from class: class androidx.core.widget.NestedScrollView))

2 Answers2

0

You can simply use ViewActions.scrollTo() if your nested recycler views do not have nested scrolling enabled, but you'll need to tweak the action first because it does not support NestedScrollView:

fun nestedScrollTo(): ViewAction = object : ViewAction {

    private val scrollTo = ViewActions.scrollTo()

    override fun getConstraints(): Matcher<View> {
        return Matchers.allOf(
            ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
            ViewMatchers.isDescendantOfA(Matchers.anyOf(ViewMatchers.isAssignableFrom(NestedScrollView::class.java))))
    }

    override fun getDescription(): String = scrollTo.description

    override fun perform(uiController: UiController, view: View) = scrollTo.perform(uiController, view)
}

Then use the new custom action to scroll, for example:

onView(withText("query")).perform(nestedScrollTo(), click())

Avoid using swipe in this use case if possible, they can be unreliable at times.

Aaron
  • 3,764
  • 2
  • 8
  • 25
  • Thanks for the `nestedScrollTo()` Cleaner than the other scripts I tried from [espresso-testing-nestedscrollview-error-performing-scroll-to-on-view-with](https://stackoverflow.com/questions/39642631/espresso-testing-nestedscrollview-error-performing-scroll-to-on-view-with) All failed which is why I resorted to using swiping... – Adam Schultz Sep 09 '21 at 19:27
  • If the child recycler views do not have nested scrolling enabled, alternatively you could also try `onView(parent_recycler_view).perform(RecyclerViewActions.scrollTo(child_recycler_view_2))`, then `onView(child_recycler_view_2).perform(RecyclerViewActions.scrollTo(...))`, hope this one works too! The above answer only works if your recycler views are placed in nested scroll view. – Aaron Sep 10 '21 at 02:36
0

6 months later I figured out a bit more about Recycler lists and that I was trying to use them wrong, or at least figured out which Recycler actions work (some don't seem to work at all). This had nothing to do with Nested scrolling even though I have nested recycler lists.

Needed to use a swipe up action as a back up for when the list doesn't exist in the hierarchy.

Also there is a Potential infinite loop.

fun tapRecyclerItem(titleText: String) {

    val parentList by lazy { onView(withId(R.id.parent_recycler_list)) }

    try {
        //Try to scroll to the item in the child list
        onView(allOf(
            withId(R.id.recycler_list), 
            hasDescendantWithText(titleText)
        )).perform(
            RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
                hasDescendantWithText(titleText),
                ViewActions.scrollTo()
            )
        )
        // Tap the title
        onView(allOf(withId(R.id.title), withText(titleText))).tap()
    } catch (ex: NoMatchingViewException) {
        // Swipe up and try again
        parentList.perform(swipeCenterUp())
        tapRecyclerItem(titleText)
    }
}

fun hasDescendantWithText(text: String): Matcher<View> {
    return Matchers.allOf(
        hasDescendant(withText(text)),
        withEffectiveVisibility(VISIBLE)
    )
}

fun swipeCenterUp(): ViewAction? {
    return ViewActions.actionWithAssertions(
        GeneralSwipeAction(
            Swipe.FAST,
            GeneralLocation.CENTER,
            GeneralLocation.TOP_CENTER,
            Press.FINGER
        )
    )
}