使用Espresso单击RecyclerView项目内的视图


100

如何使用Espresso单击RecyclerView项目内的特定视图?我知道我可以使用以下方法在位置0处单击该项目:

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

但是我需要单击该项目内的特定视图,而不是该项目本身。

提前致谢。

-编辑-

更准确地说:我有一个RecyclerViewR.id.recycler_view),哪些项目是CardViewR.id.card_view)。在每个CardView中,我都有四个按钮(除其他外),并且我想单击特定的按钮(R.id.bt_deliver)。

我想使用Espresso 2.0的新功能,但是我不确定这是可能的。

如果不可能的话,我想使用这样的东西(使用Thomas Keller代码):

onRecyclerItemView(R.id.card_view, ???, withId(R.id.bt_deliver)).perform(click());

但我不知道该问些什么。



1
嗨,坦克为您快速解答!:-)我已经看到了这个问题,但是在如何使用RecyclerViewActions来完成我想做的事情上找不到任何帮助。我应该使用旧答案吗?谢谢。
Filipe Ramos

您找到解决问题的办法了吗?
HowieH 2015年

1
@HowieH:不,我放弃了,我现在正在使用Robotium ...
Filipe Ramos

Answers:


136

您可以通过自定义视图操作来实现。

public class MyViewAction {

    public static ViewAction clickChildViewWithId(final int id) {
        return new ViewAction() {
            @Override
            public Matcher<View> getConstraints() {
                return null;
            }

            @Override
            public String getDescription() {
                return "Click on a child view with specified id.";
            }

            @Override
            public void perform(UiController uiController, View view) {
                View v = view.findViewById(id);
                v.performClick();
            }
        };
    }

}

然后您可以点击

onView(withId(R.id.rv_conference_list)).perform(
            RecyclerViewActions.actionOnItemAtPosition(0, MyViewAction.clickChildViewWithId(R.id. bt_deliver)));

2
我将删除null检查,以便如果未找到子视图,则引发异常,而不是无声地执行任何操作。
阿尔瓦罗·古铁雷斯·佩雷斯

感谢你的回答。但是遇到了一个问题。在onPerform()函数中,如果我使用了onView(withId())函数,则该函数会被删除。这种行为的原因是什么?
thedarkpassenger

3
适用于espresso:espresso-contrib:2.2.2,但不适用于2.2.1,为什么?我有一些依赖关系,因为我不能使用2.2.2。
RosAng

3
我最初是用此方法获取NullPointerException的,所以我必须实现getConstraints()来避免这种情况。以下对我有用:public Matcher <View> getConstraints(){return isAssignableFrom(View.class); }
dfinn '16

上面的解决方案不适用于我。我收到以下错误:android.support.test.espresso.PerformException:在视图“ ID为:adamhurwitz.github.io.doordashlite”上执行“ android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemAtPositionViewAction@fad9df”时出错:id / recyclerView”。
亚当·赫维兹

67

现在有了android.support.test.espresso.contrib,它变得更容易了:

1)添加测试依赖

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'
}

*不包括3个模块,因为您很可能已经拥有它

2)然后做类似的事情

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

要么

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

要么

onView(withId(R.id.recycler_linear))
            .check(matches(hasDescendant(withText("whatever"))));

1
谢谢,不排除这些模块对我来说会导致java.lang.IncompatibleClassChangeError。
hboy16 2016年

8
并单击该怎么办?让我们在列表的第5项中说一个特定视图?在提供的示例中,我仅看到如何单击第五项或具有后代视图的单击和项,但不能同时看到两者,还是我错过了一些东西?
donfuxx '16

1
我必须做的exclude group: 'com.android.support' exclude module: 'recyclerview-v7'
-Jemshit Iskenderov

1
当您想单击RecyclerView内的复选框时,它是无用的。
Farid

2
在kotlin中actionOnItemAtPosition需要类型参数。不知道该放在哪里。
ZeroDivide

7

这是我如何解决Kotlin中的问题:

fun clickOnViewChild(viewId: Int) = object : ViewAction {
    override fun getConstraints() = null

    override fun getDescription() = "Click on a child view with specified id."

    override fun perform(uiController: UiController, view: View) = click().perform(uiController, view.findViewById<View>(viewId))
}

然后

onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(position, clickOnViewChild(R.id.viewToClickInTheRow)))

我遇到了E / TestRunner:java.lang.NullPointerException:尝试在空对象引用上调用虚拟方法'void android.view.View.getLocationOnScreen(int [])';知道为什么吗?
sayvortana

6

尝试下一种方法:

    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;
                }

            }
        };
    }
}

3

您可以点击第三个项目recyclerView就像这样:

onView(withId(R.id.recyclerView)).perform(
                RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(2,click()))

不要忘记提供ViewHolder类型,以便推理不会失败。


1
我错过了,<RecyclerView.ViewHolder>谢谢您,先生;)
Skizo-ozᴉʞS19年

0

上面的所有答案都对我不起作用,因此我建立了一个新方法,该方法可以搜索单元格内的所有视图以返回带有请求ID的视图。它需要两种方法(可以合并为一种):

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;
}

最后,您可以通过执行以下操作在Recycler View上使用它:

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

如果您想对视图进行其他操作,则可以使用回调函数返回视图,或者只需构建3-4个不同版本的视图即可执行其他任何任务。


0

我一直在尝试各种方法来查找为什么@blade的答案对我不起作用,只是意识到我有一个OnTouchListener(),我相应地修改了ViewAction:

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

您甚至可以泛化此方法,以不仅支持更多操作click。这是我的解决方案:

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()
    }
  }
 
}

然后,EditText您可以执行以下操作:

onView(withId(R.id.yourRecyclerView))
        .perform(
            actionOnItemAtPosition<YourViewHolder>(
                0,
                recyclerChildAction<EditText>(R.id.editTextId) { setText("1000") }
            )
        )

-5

首先为您的按钮提供唯一的contentDescriptions,即“交付按钮第5行”。

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

然后滚动到行:

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

然后基于contentDescription选择视图。

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

内容描述是使用Espresso的onView并使应用更易于访问的好方法。


5
内容描述应该不会以这样的方式被使用-它不会使你的应用程序更容易的意见中喷出的技术或调试信息的用户提供A11Y需求-这样做的CD大概应该是这样的“订单<项目概要>按钮”。withText("Order")也可以使用,并且不需要您在测试时知道要订购什么。
ataulm 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.