简体   繁体   English

如何在 Espresso 中单击 RecyclerView 中的项目

[英]How to click on an item inside a RecyclerView in Espresso

I have a RecyclerView (R.id.recyclerView) where each row has an image (R.id.row_image) and a TextView.我有一个 RecyclerView (R.id.recyclerView),其中每一行都有一个图像 (R.id.row_image) 和一个 TextView。 I want to click on the image in the first row.我想单击第一行中的图像。
I've tried to use onData(..) but it doesn't seem to work.我试过使用 onData(..) 但它似乎不起作用。

Use RecyclerViewActions使用RecyclerViewActions

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

Include this in your gradle script:将此包含在您的 gradle 脚本中:

dependencies {
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
    androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.0'
}

Just to add to Gabor's answer (which is the correct and complete answer since Espresso 2.0).只是为了添加 Gabor 的答案(这是自 Espresso 2.0 以来正确且完整的答案)。

You may encounter problems at the moment when using espresso-contrib and RecyclerView s (seeandroid-test-kit ticket ).您目前在使用espresso-contribRecyclerView时可能会遇到问题(请参阅android-test-kit 票证)。

A workaround is to add this exclusion in the espresso-contrib dependency Gabor mentioned above:解决方法是在上面提到的espresso-contrib依赖项 Gabor 中添加此排除项:

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

(This is an answer instead of a comment to Gabor's answer because I don't have the right to post comments yet) (这是一个答案而不是对 Gabor 答案的评论,因为我还没有发表评论的权利)

Edit:编辑:

Espresso 2.0 has been released, the changelog includes the following: Espresso 2.0 已经发布,更新日志包括以下内容:

New Features新功能

  • espresso-contrib浓缩咖啡贡献
    • RecyclerViewActions: handles interactions with RecyclerViews RecyclerViewActions:处理与 RecyclerViews 的交互

Old Answer旧答案

I haven't yet tested this myself, but Thomas Keller posted this on G+ with a short explanation and a link to a Gist with the necessary view matchers.我自己还没有测试过这个,但是 Thomas Keller 在 G+ 上发布了这个,并附有一个简短的解释和一个指向带有必要视图匹配器的 Gist 的链接。

Since the new RecyclerView API inherits from ViewGroup and not from AdapterView , you cannot use Espresso's onData() to test layouts using this component.由于新的RecyclerView API 继承自ViewGroup而不是AdapterView ,因此您不能使用 Espresso 的onData()来测试使用此组件的布局。

Link to Gist .链接到要点

I'll attach the code, just for completeness sake (note: not mine! All credit goes to Thomas Keller)我会附上代码,只是为了完整起见(注意:不是我的!所有功劳都归功于 Thomas Keller)

ViewMatcher:视图匹配器:

public class ViewMatchers {
    @SuppressWarnings("unchecked")
    public static Matcher<View> withRecyclerView(@IdRes int viewId) {
        return allOf(isAssignableFrom(RecyclerView.class), withId(viewId));
    }

    @SuppressWarnings("unchecked")
    public static ViewInteraction onRecyclerItemView(@IdRes int identifyingView, Matcher<View> identifyingMatcher, Matcher<View> childMatcher) {
        Matcher<View> itemView = allOf(withParent(withRecyclerView(R.id.start_grid)),
                withChild(allOf(withId(identifyingView), identifyingMatcher)));
        return Espresso.onView(allOf(isDescendantOfA(itemView), childMatcher));
    }
}

And sample usage:和示例用法:

onRecyclerItemView(R.id.item_title, withText("Test"),  withId(R.id.item_content))
    .matches(check(withText("Test Content")));

You should use a Custom ViewAction:您应该使用自定义 ViewAction:

public void clickOnImageViewAtRow(int position) {
    onView(withId(R.id.recycler_view)).perform(RecyclerViewActions.actionOnItemAtPosition(position, new ClickOnImageView()));
}

public class ClickOnImageView implements ViewAction{
    ViewAction click = click();

    @Override
    public Matcher<View> getConstraints() {
        return click.getConstraints();
    }

    @Override
    public String getDescription() {
        return " click on custom image view";
    }

    @Override
    public void perform(UiController uiController, View view) {
        click.perform(uiController, view.findViewById(R.id.imageView));
    }
}

I have found two ways:我找到了两种方法:

  1. Assuming you have a textview with id "R.id.description" for each item in the RecyclerView.假设您在 RecyclerView 中的每个项目都有一个 ID 为“R.id.description”的文本视图。 You can do this to match a specific child:您可以这样做以匹配特定的孩子:

onView(allOf(withId(R.id.place_description),withText("what"))).perform(click());

  1. A tutorial from Android Testing Codelab https://codelabs.developers.google.com/codelabs/android-testing/#0来自 Android 测试 Codelab 的教程https://codelabs.developers.google.com/codelabs/android-testing/#0

` `

public Matcher<View> withItemText(final String itemText) {
        checkArgument(!TextUtils.isEmpty(itemText),"cannot be null");
        return new TypeSafeMatcher<View>() {
            @Override
            protected boolean matchesSafely(View item) {
                return allOf(isDescendantOfA(isAssignableFrom(RecyclerView.class)),withText(itemText)).matches(item);
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("is descendant of a RecyclerView with text" + itemText);
            }
        };
    }

` `

And then, do this:然后,这样做:

onView(withItemText("what")).perform(click());

Same answer like above but when some small modification since you have to provide the Viewholder type与上面相同的答案,但是由于您必须提供 Viewholder 类型,因此进行了一些小的修改

With using RecyclerViewActions使用 RecyclerViewActions

fun tapOnRecyclerView(@IdRes resId: Int , position: Int) = onView(withId(resId))
        .perform(actionOnItemAtPosition<RecyclerView.ViewHolder>(position, click()));

and to include this libraries in your project , use this并将此库包含在您的项目中,请使用此

androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"

Prefer to use the latest espresso version from this link更喜欢使用此链接中的最新浓缩咖啡版本

You needn't to add "testing-support-lib", neither "espresso:espresso-core".您不需要添加“testing-support-lib”,也不需要添加“espresso:espresso-core”。 They are transitive added when "espresso:espresso-contrib" is added.当添加“espresso:espresso-contrib”时,它们是可传递的。

build.grade建造等级

dependencies {
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile 'com.android.support.test:rules:0.3'
    androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2'
}

Usage :用法

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

I followed @Gabor's answer but when I included the libraries, I hit the dex limit!我遵循了@Gabor 的回答,但是当我包含这些库时,我达到了 dex 限制!

So, I removed the libraries, added this getInstrumentation().waitForIdleSync();所以,我删除了库,添加了这个getInstrumentation().waitForIdleSync(); and then just called onView(withId...))...然后只是调用onView(withId...))...

Works perfectly.完美运行。

In your case you will have multiple image views with the same ID so you will have to figure out something on how you can select the particular list item.在您的情况下,您将拥有多个具有相同 ID 的图像视图,因此您必须弄清楚如何选择特定列表项。

As I posted here you can implement your custom RecyclerView matcher.正如我在这里发布的那样您可以实现您的自定义RecyclerView匹配器。 Let's assume you have RecyclerView where each element has subject you wonna match:假设您有RecyclerView ,其中每个元素都有您要匹配的主题:

public static Matcher<RecyclerView.ViewHolder> withItemSubject(final String subject) {
    Checks.checkNotNull(subject);
    return new BoundedMatcher<RecyclerView.ViewHolder, MyCustomViewHolder>(
            MyCustomViewHolder.class) {

        @Override
        protected boolean matchesSafely(MyCustomViewHolder viewHolder) {
            TextView subjectTextView = (TextView)viewHolder.itemView.findViewById(R.id.subject_text_view_id);

            return ((subject.equals(subjectTextView.getText().toString())
                    && (subjectTextView.getVisibility() == View.VISIBLE)));
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("item with subject: " + subject);
        }
    };
}

And usage:和用法:

onView(withId(R.id.my_recycler_view_id)
    .perform(RecyclerViewActions.actionOnHolderItem(withItemSubject("My subject"), click()));

Basically you can match anything you want.基本上你可以匹配任何你想要的。 In this example we used subject TextView but it can be any element inside the RecyclerView item.在这个例子中,我们使用了主题TextView但它可以是RecyclerView项目中的任何元素。

One more thing to clarify is check for visibility (subjectTextView.getVisibility() == View.VISIBLE) .需要澄清的另一件事是检查可见性(subjectTextView.getVisibility() == View.VISIBLE) We need to have it because sometimes other views inside RecyclerView can have the same subject but it would be with View.GONE .我们需要它,因为有时RecyclerView其他视图可以具有相同的主题,但它会与View.GONE This way we avoid multiple matches of our custom matcher and target only item that actually displays our subject.通过这种方式,我们避免了自定义匹配器的多次匹配,并且只针对实际显示我们主题的项目。

With this code, you can scroll the recycler view to find your item withText and perform click or other action on it.使用此代码,您可以滚动回收站视图以找到您的项目 withText 并对其执行单击或其他操作。

dependencies依赖

androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'

Code代码

@Test
public void scrollRecyclerViewAndClick() {
    onView(withId(R.id.recycler_view)).
            perform(RecyclerViewActions.
            actionOnItem(withText("specific string"), click()));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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