Using Espresso to click view inside RecyclerView item

后端 未结 9 1857
醉酒成梦
醉酒成梦 2020-12-02 05:36

How can I use Espresso to click a specific view inside a RecyclerView item? I know I can click the item at position 0 using:

onView(withId(R.i

相关标签:
9条回答
  • 2020-12-02 06:06

    You can even generalize this approach to support more actions not only click. Here is my solution for this:

    fun <T : View> recyclerChildAction(@IdRes id: Int, block: T.() -> Unit): ViewAction {
      return object : ViewAction {
        override fun getConstraints(): Matcher<View> {
          return any(View::class.java)
        }
    
        override fun getDescription(): String {
          return "Performing action on RecyclerView child item"
        }
    
        override fun perform(
            uiController: UiController,
            view: View
        ) {
          view.findViewById<T>(id).block()
        }
      }
     
    }
    

    And then for EditText you can do something like this:

    onView(withId(R.id.yourRecyclerView))
            .perform(
                actionOnItemAtPosition<YourViewHolder>(
                    0,
                    recyclerChildAction<EditText>(R.id.editTextId) { setText("1000") }
                )
            )
    
    0 讨论(0)
  • 2020-12-02 06:09

    I kept trying out various methods to find why @blade's answer was not working for me, to only realize that I have an OnTouchListener(), I modified the ViewAction accordingly:

    fun clickTopicToWeb(id: Int): ViewAction {
    
            return object : ViewAction {
                override fun getDescription(): String {...}
    
                override fun getConstraints(): Matcher<View> {...}
    
                override fun perform(uiController: UiController?, view: View?) {
    
                    view?.findViewById<View>(id)?.apply {
    
                        //Generalized for OnClickListeners as well
                        if(isEnabled && isClickable && !performClick()) {
                            //Define click event
                            val event: MotionEvent = MotionEvent.obtain(
                              SystemClock.uptimeMillis(),
                              SystemClock.uptimeMillis(),
                              MotionEvent.ACTION_UP,
                              view.x,
                              view.y,
                              0)
    
                            if(!dispatchTouchEvent(event))
                                throw Exception("Not clicking!")
                        }
                    }
                }
            }
        }
    
    0 讨论(0)
  • 2020-12-02 06:10

    Try next approach:

        onView(withRecyclerView(R.id.recyclerView)
                        .atPositionOnView(position, R.id.bt_deliver))
                        .perform(click());
    
        public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
                return new RecyclerViewMatcher(recyclerViewId);
        }
    
    public class RecyclerViewMatcher {
        final int mRecyclerViewId;
    
        public RecyclerViewMatcher(int recyclerViewId) {
            this.mRecyclerViewId = recyclerViewId;
        }
    
        public Matcher<View> atPosition(final int position) {
            return atPositionOnView(position, -1);
        }
    
        public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
    
            return new TypeSafeMatcher<View>() {
                Resources resources = null;
                View childView;
    
                public void describeTo(Description description) {
                    int id = targetViewId == -1 ? mRecyclerViewId : targetViewId;
                    String idDescription = Integer.toString(id);
                    if (this.resources != null) {
                        try {
                            idDescription = this.resources.getResourceName(id);
                        } catch (Resources.NotFoundException var4) {
                            idDescription = String.format("%s (resource name not found)", id);
                        }
                    }
    
                    description.appendText("with id: " + idDescription);
                }
    
                public boolean matchesSafely(View view) {
    
                    this.resources = view.getResources();
    
                    if (childView == null) {
                        RecyclerView recyclerView =
                                (RecyclerView) view.getRootView().findViewById(mRecyclerViewId);
                        if (recyclerView != null) {
    
                            childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
                        }
                        else {
                            return false;
                        }
                    }
    
                    if (targetViewId == -1) {
                        return view == childView;
                    } else {
                        View targetView = childView.findViewById(targetViewId);
                        return view == targetView;
                    }
    
                }
            };
        }
    }
    
    0 讨论(0)
  • 2020-12-02 06:11

    Now with android.support.test.espresso.contrib it has become easier:

    1)Add test dependency

    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.0') {
        exclude group: 'com.android.support', module: 'appcompat'
        exclude group: 'com.android.support', module: 'support-v4'
        exclude module: 'recyclerview-v7'
    }
    

    *exclude 3 modules, because very likely you already have it

    2) Then do something like

    onView(withId(R.id.recycler_grid))
                .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
    

    Or

    onView(withId(R.id.recyclerView))
      .perform(RecyclerViewActions.actionOnItem(
                hasDescendant(withText("whatever")), click()));
    

    Or

    onView(withId(R.id.recycler_linear))
                .check(matches(hasDescendant(withText("whatever"))));
    
    0 讨论(0)
  • 2020-12-02 06:15

    All of the answers above didn't work for me so I have built a new method that searches all of the views inside a cell to return the view with the ID requested. It requires two methods (could be combined into one):

    fun performClickOnViewInCell(viewID: Int) = object : ViewAction {
        override fun getConstraints(): org.hamcrest.Matcher<View> = click().constraints
        override fun getDescription() = "Click on a child view with specified id."
        override fun perform(uiController: UiController, view: View) {
            val allChildViews = getAllChildrenBFS(view)
            for (child in allChildViews) {
                if (child.id == viewID) {
                    child.callOnClick()
                }
            }
        }
    }
    
    
    private fun  getAllChildrenBFS(v: View): List<View> {
        val visited = ArrayList<View>();
        val unvisited = ArrayList<View>();
        unvisited.add(v);
    
        while (!unvisited.isEmpty()) {
            val child = unvisited.removeAt(0);
            visited.add(child);
            if (!(child is ViewGroup)) continue;
            val group = child
            val childCount = group.getChildCount();
            for (i in 0 until childCount) { unvisited.add(group.getChildAt(i)) }
        }
    
        return visited;
    }
    

    Then final you can use this on Recycler View by doing the following:

    onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition<RecyclerView.ViewHolder>(0, getViewFromCell(R.id.cellInnerView) {
                val requestedView = it
    }))
    

    You could use a callback to return the view if you want to do something else with it, or just build out 3-4 different versions of this to do any other tasks.

    0 讨论(0)
  • 2020-12-02 06:15

    First give your buttons unique contentDescriptions, i.e. "delivery button row 5".

    <button android:contentDescription=".." />

    Then scroll to row:

    onView(withId(R.id.recycler_view)).perform(RecyclerViewActions.scrollToPosition(5));

    Then select the view based on contentDescription.

    onView(withContentDescription("delivery button row 5")).perform(click());

    Content Description is a great way to use Espresso's onView and make your app more accessible.

    0 讨论(0)
提交回复
热议问题