简体   繁体   中英

Android Espresso: click on a specific view inside a RecyclerView's item

I have the following RecyclerView list:

在此输入图像描述

I only want my test to click on the checkbox on the right, not the whole item. Here is what I tried:

onView(withId(R.id.recycler_view))
    .perform(actionOnViewHolder<ViewHolder>(matcher = { vh ->
        if (vh == null) return@actionOnViewHolder false

        if (vh.position == no) {
            // v1
            (vh.itemView as ViewGroup).findViewById<View>(R.id.checkbox)?.performClick()
            // v2
            (vh.itemView as ViewGroup).getChildAt(2)?.performClick()
        }
        return@actionOnViewHolder false
    }))

And some helper methods/classes:

inline fun <reified VH : RecyclerView.ViewHolder> actionOnViewHolder(
    noinline matcher: (VH?) -> Boolean): RecyclerViewActions.PositionableRecyclerViewAction {
    return RecyclerViewActions.actionOnHolderItem(
        RecyclerViewViewHolderMatcher(VH::class.java, matcher), ViewActions.click())
}

class RecyclerViewViewHolderMatcher<VH : RecyclerView.ViewHolder>(
    clazz: Class<VH>,
    private val matcher: (VH?) -> Boolean) : BoundedMatcher<RecyclerView.ViewHolder, VH>(clazz) {

    override fun describeTo(description: Description?) { }

    override fun matchesSafely(item: VH): Boolean = matcher(item)
}

V1: (vh.itemView as ViewGroup).findViewById<View>(R.id.checkbox) returns null, even though in debugger I can see that the view that 3 childs, the last one having the id: checkbox .

V2: (vh.itemView as ViewGroup).getChildAt(2) returns the view, but a click happens way later.

Also, I always return false from the actionOnViewHolder because I don't want to click() or anything else on the whole ViewHolder.

Is there any way to do this better?

Add new class with helper method:

object EspressoMatchers { 
    fun atPositionOnView(mRecyclerViewId: Int, position: Int, targetViewId: Int): Matcher<View> {

    return object : TypeSafeMatcher<View>() {
        var resources: Resources? = null
        var childView: View? = null

        override fun describeTo(description: Description) {
            val id = targetViewId
            var idDescription = Integer.toString(id)
            if (this.resources != null) {
                try {
                    idDescription = this.resources!!.getResourceName(id)
                } catch (var4: Resources.NotFoundException) {
                    idDescription = String.format("%s (resource name not found)", id)
                }
            }
            description.appendText("with id: $idDescription")
        }

        public override fun matchesSafely(view: View): Boolean {
            this.resources = view.resources
            if (childView == null) {
                val recyclerView: RecyclerView = if (view.rootView.findViewById<View>(mRecyclerViewId) is RecyclerView) {
                    view.rootView.findViewById<View>(mRecyclerViewId) as RecyclerView
                } else {
                    //todo need only if you want acces to recyclerView in custom views
                }
                childView = recyclerView.findViewHolderForAdapterPosition(position)!!.itemView
            }
            val targetView = childView!!.findViewById<View>(targetViewId)
            return view === targetView
        }
    }
}}

Create cell wrapper

open class BaseCell(val listId: Int, val position: Int) {

public fun tap(id: Int) {
    onView(atPositionOnView(listId, position, id)).perform(click())
}
}

And then we can use it in test

val cell = BaseCell(R.id.recyclerViewId, 0)
cell.tap(R.id.checkBoxId)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM