Sử dụng Espresso để nhấp vào xem bên trong mục RecyclerView


100

Làm cách nào tôi có thể sử dụng Espresso để nhấp vào một chế độ xem cụ thể bên trong một mục RecyclerView ? Tôi biết tôi có thể nhấp vào mục ở vị trí 0 bằng cách sử dụng:

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

Nhưng tôi cần phải nhấp vào một cái nhìn cụ thể bên trong mục đó chứ không phải trên chính mục đó.

Cảm ơn trước.

-- biên tập --

Nói chính xác hơn: Tôi có RecyclerView ( R.id.recycler_view) mà các mục là CardView ( R.id.card_view). Bên trong mỗi CardView, tôi có bốn nút (trong số những thứ khác) và tôi muốn nhấp vào một nút cụ thể ( R.id.bt_deliver).

Tôi muốn sử dụng các tính năng mới của Espresso 2.0, nhưng tôi không chắc là có thể.

Nếu không thể, tôi muốn sử dụng một cái gì đó như thế này (sử dụng mã Thomas Keller):

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

nhưng tôi không biết phải đặt những gì vào dấu hỏi.



1
Xin chào, xe tăng cho câu trả lời nhanh chóng! :-) Tôi đã thấy câu hỏi đó, nhưng tôi không thể tìm thấy bất kỳ trợ giúp nào về cách sử dụng RecyclerViewActions để làm những gì tôi muốn. Tôi có nên sử dụng Câu trả lời cũ không? Cảm ơn.
Filipe Ramos

Bạn đã tìm thấy giải pháp nào cho vấn đề của mình chưa?
HowieH

1
@HowieH: Số tôi đã từ bỏ, và bây giờ tôi đang sử dụng Robotium ...
Filipe Ramos

Câu trả lời:


135

Bạn có thể làm điều đó với tùy chỉnh chế độ xem.

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

}

Sau đó, bạn có thể nhấp vào nó bằng

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

2
Tôi sẽ loại bỏ kiểm tra null, để nếu chế độ xem con không được tìm thấy, một ngoại lệ sẽ được ném ra thay vì im lặng không làm gì cả.
Alvaro Gutierrez Perez

Cảm ơn vì câu trả lời. Nhưng đã xảy ra một vấn đề. Trong hàm onPerform (), nếu tôi sử dụng hàm onView (withId ()), thì hàm này bị đánh. Lý do cho hành vi này là gì?
thedarkpassenger

3
hoạt động với espresso: espresso-Contrib: 2.2.2 nhưng không hoạt động với 2.2.1 bất kỳ lý do tại sao? Tôi có một số phụ thuộc do tôi không thể sử dụng 2.2.2.
RosAng

3
Tôi đã nhận được một NullPointerException với điều này ban đầu, vì vậy tôi phải triển khai getConstraints () để tránh điều đó. Những điều sau đây phù hợp với tôi: public Matcher <View> getConstraints () {return isAssignableFrom (View.class); }
dfinn

Giải pháp trên không hoạt động đối với tôi. Tôi gặp lỗi sau: android.support.test.espresso.PerformException: Lỗi khi thực hiện 'android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemAtPositionViewAction@fad9df' trên chế độ xem 'với id: adamhurwitz.github.io.doordashlite : id / RecyclerView '.
Adam Hurwitz

66

Giờ đây với android.support.test.espresso.contrib, việc này đã trở nên dễ dàng hơn:

1) Thêm phụ thuộc thử nghiệm

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

* loại trừ 3 mô-đun, vì rất có thể bạn đã có nó

2) Sau đó, làm một cái gì đó như

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

Hoặc là

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

Hoặc là

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

1
Cảm ơn, không loại trừ các mô-đun đó dẫn đến lỗi java.lang.Incomp Tương thíchClassChangeError cho tôi.
hboy 17/02/16

8
và phải làm gì để nhấp vào hãy nói trong mục thứ 5 của danh sách một chế độ xem cụ thể? trong các ví dụ được cung cấp, tôi chỉ thấy cách nhấp vào mục thứ 5 hoặc nhấp và mục có chế độ xem con cháu, nhưng không thấy cả hai cùng nhau, hoặc tôi đã bỏ lỡ điều gì đó?
donfuxx

1
tôi phải làmexclude group: 'com.android.support' exclude module: 'recyclerview-v7'
Jemshit Iskenderov

1
Sẽ vô ích khi bạn muốn nhấp vào hộp kiểm bên trong RecyclerView.
Farid

2
Trong kotlin actionOnItemAtPositionyêu cầu một tham số kiểu. Không chắc chắn những gì để đặt ở đó.
ZeroDivide

7

Đây là cách tôi giải quyết vấn đề trong 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))
}

và sau đó

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

Tôi gặp phải E / TestRunner: java.lang.NullPointerException: Cố gắng gọi phương thức ảo 'void android.view.View.getLocationOnScreen (int [])' trên tham chiếu đối tượng rỗng; bất kỳ ý tưởng tại sao?
sayvortana

6

Hãy thử cách tiếp cận tiếp theo:

    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

Bạn có thể nhấp vào mục thứ 3 của recyclerViewLike this:

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

Đừng quên cung cấp ViewHolderloại để suy luận không bị lỗi.


1
Tôi đã thiếu <RecyclerView.ViewHolder>cảm ơn bạn;)
Skizo-ozᴉʞS

0

Tất cả các câu trả lời ở trên không phù hợp với tôi vì vậy tôi đã xây dựng một phương pháp mới tìm kiếm tất cả các chế độ xem bên trong một ô để trả về chế độ xem có ID được yêu cầu. Nó yêu cầu hai phương pháp (có thể được kết hợp thành một):

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

Sau đó, cuối cùng bạn có thể sử dụng điều này trên Recycler View bằng cách làm như sau:

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

Bạn có thể sử dụng một lệnh gọi lại để trả về chế độ xem nếu bạn muốn làm việc gì đó khác với nó hoặc chỉ cần tạo ra 3-4 phiên bản khác nhau của chế độ này để thực hiện bất kỳ tác vụ nào khác.


0

Tôi tiếp tục thử các phương pháp khác nhau để tìm lý do tại sao câu trả lời của @ blade không phù hợp với tôi, chỉ để nhận ra rằng tôi có một OnTouchListener(), tôi đã sửa đổi ViewAction cho phù hợp:

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

Bạn thậm chí có thể tổng quát hóa cách tiếp cận này để hỗ trợ nhiều hành động hơn không chỉ click. Đây là giải pháp của tôi cho điều này:

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

Và sau đó EditTextbạn có thể làm điều gì đó như sau:

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

-5

Trước tiên, hãy cung cấp cho các nút của bạn nội dung Độc đáo, tức là "hàng nút phân phối 5".

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

Sau đó cuộn đến hàng:

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

Sau đó chọn chế độ xem dựa trên contentDescription.

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

Nội dung Mô tả là một cách tuyệt vời để sử dụng onView của Espresso và giúp ứng dụng của bạn dễ truy cập hơn.


5
Mô tả nội dung không nên được sử dụng theo cách như vậy - nó không làm cho ứng dụng của bạn dễ tiếp cận hơn đối với các lượt xem để cung cấp thông tin kỹ thuật hoặc gỡ lỗi cho người dùng có nhu cầu a11y - CD cho điều này có lẽ phải là một cái gì đó giống như "Đặt hàng <tóm tắt mục> cái nút". withText("Order")cũng sẽ hoạt động và sẽ không yêu cầu bạn biết những gì bạn đang đặt hàng tại thời điểm kiểm tra.
ataulm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.