Menggunakan Espresso untuk mengklik tampilan di dalam item RecyclerView

100

Bagaimana saya bisa menggunakan Espresso untuk mengklik tampilan tertentu di dalam item RecyclerView ? Saya tahu saya bisa mengklik item di posisi 0 menggunakan:

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

Tetapi saya perlu mengklik tampilan tertentu di dalam item itu dan bukan pada item itu sendiri.

Terima kasih sebelumnya.

- edit -

Untuk lebih tepatnya: Saya memiliki RecyclerView ( R.id.recycler_view) yang itemnya adalah CardView ( R.id.card_view). Di dalam setiap CardView saya memiliki empat tombol (antara lain) dan saya ingin mengklik tombol tertentu ( R.id.bt_deliver).

Saya ingin menggunakan fitur-fitur baru Espresso 2.0, tetapi saya tidak yakin itu mungkin.

Jika tidak memungkinkan, saya ingin menggunakan sesuatu seperti ini (menggunakan kode Thomas Keller):

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

tapi saya tidak tahu apa yang harus diberi tanda tanya.

Filipe Ramos
sumber
Periksa ini
Skizo-ozᴉʞS
1
Hai, tank untuk jawaban cepatnya! :-) Saya telah melihat pertanyaan itu, tetapi saya tidak dapat menemukan bantuan apa pun tentang cara menggunakan RecyclerViewActions untuk melakukan apa yang saya inginkan. Haruskah saya menggunakan Jawaban Lama ? Terima kasih.
Filipe Ramos
Apakah Anda sudah menemukan solusi untuk masalah Anda?
HowieH
1
@ HowieH: Tidak. Saya menyerah, dan saya sekarang menggunakan Robotium ...
Filipe Ramos

Jawaban:

136

Anda dapat melakukannya dengan menyesuaikan tindakan tampilan.

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

}

Kemudian Anda dapat mengkliknya dengan

onView(withId(R.id.rv_conference_list)).perform(
            RecyclerViewActions.actionOnItemAtPosition(0, MyViewAction.clickChildViewWithId(R.id. bt_deliver)));
Pedang
sumber
2
Saya akan menghapus pemeriksaan null, sehingga jika tampilan anak tidak ditemukan pengecualian akan dilempar daripada tidak melakukan apa pun secara diam-diam.
Alvaro Gutierrez Perez
Terima kasih atas jawabannya. Tapi terkena satu masalah. Dalam fungsi onPerform (), jika saya menggunakan fungsi onView (withId ()), maka fungsi tersebut akan dipukul. Apa alasan perilaku ini?
Penumpang gelap
3
bekerja dengan espresso: espresso-contrib: 2.2.2 tetapi tidak dengan 2.2.1 alasan apa saja? Saya memiliki beberapa ketergantungan karena saya tidak dapat menggunakan 2.2.2.
RosAng
3
Saya mendapatkan NullPointerException dengan ini awalnya, jadi saya harus menerapkan getConstraints () untuk menghindarinya. Yang berikut ini berhasil untuk saya: public Matcher <View> getConstraints () {return isAssignableFrom (View.class); }
dfinn
Solusi di atas tidak berhasil untuk saya. Saya mendapatkan kesalahan berikut: android.support.test.espresso.PerformException: Error melakukan 'android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemAtPositionViewAction@fad9df' on view 'dengan id: adamhurwitz.github.io.doordashlite : id / recyclerView '.
Adam Hurwitz
67

Sekarang dengan android.support.test.espresso.contrib menjadi lebih mudah:

1) Tambahkan ketergantungan uji

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

* kecualikan 3 modul, karena kemungkinan besar Anda sudah memilikinya

2) Kemudian lakukan sesuatu seperti

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

Atau

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

Atau

onView(withId(R.id.recycler_linear))
            .check(matches(hasDescendant(withText("whatever"))));
Andrey
sumber
1
Terima kasih, tidak mengecualikan hasil modul tersebut dalam java.lang.IncompatibleClassChangeError untuk saya.
hboy
8
dan apa yang harus dilakukan untuk mengklik katakanlah di item ke-5 dari daftar tampilan tertentu? dalam contoh yang diberikan, saya hanya melihat cara mengklik item ke-5 atau mengklik dan item dengan tampilan turunan, tetapi tidak keduanya bersamaan, atau apakah saya melewatkan sesuatu?
donfuxx
1
saya harus melakukanexclude group: 'com.android.support' exclude module: 'recyclerview-v7'
Jemshit Iskenderov
1
Tidak ada gunanya jika Anda ingin mengklik kotak centang di dalam RecyclerView.
Farid
2
Di kotlin actionOnItemAtPositionmembutuhkan tipe param. Tidak yakin apa yang harus diletakkan di sana.
ZeroDivide
7

Inilah, bagaimana saya menyelesaikan masalah di 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))
}

lalu

onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(position, clickOnViewChild(R.id.viewToClickInTheRow)))
pengguna2768856
sumber
Saya menemukan E / TestRunner: java.lang.NullPointerException: Mencoba memanggil metode virtual 'void android.view.View.getLocationOnScreen (int [])' pada referensi objek null; tahu kenapa?
sayvortana
6

Coba pendekatan selanjutnya:

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

            }
        };
    }
}
Sergey
sumber
3

Anda dapat klik pada item yang ke-3 dari recyclerViewSeperti ini:

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

Jangan lupa berikan ViewHoldertipe agar inferensi tidak gagal.

erluxman.dll
sumber
1
Saya merindukan <RecyclerView.ViewHolder>terima kasih Pak;)
Skizo-ozᴉʞS
0

Semua jawaban di atas tidak berfungsi untuk saya, jadi saya telah membangun metode baru yang mencari semua tampilan di dalam sel untuk mengembalikan tampilan dengan ID yang diminta. Ini membutuhkan dua metode (bisa digabungkan menjadi satu):

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

Kemudian terakhir Anda dapat menggunakan ini di Recycler View dengan melakukan hal berikut:

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

Anda dapat menggunakan callback untuk mengembalikan tampilan jika Anda ingin melakukan hal lain dengannya, atau cukup buat 3-4 versi berbeda untuk melakukan tugas lain.

paul_f
sumber
0

Saya terus mencoba berbagai metode untuk menemukan mengapa jawaban @ blade tidak berhasil untuk saya, hanya untuk menyadari bahwa saya punya OnTouchListener(), saya memodifikasi ViewAction sesuai:

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!")
                    }
                }
            }
        }
    }
Rimov
sumber
0

Anda bahkan dapat menggeneralisasi pendekatan ini untuk mendukung lebih banyak tindakan, tidak hanya click. Inilah solusi saya untuk ini:

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

Dan kemudian untuk EditTextAnda dapat melakukan sesuatu seperti ini:

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

Pertama, berikan tombol Anda contentDescriptions yang unik, yaitu "baris tombol pengiriman 5".

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

Kemudian gulir ke baris:

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

Kemudian pilih tampilan berdasarkan contentDescription.

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

Deskripsi Konten adalah cara terbaik untuk menggunakan onView Espresso dan membuat aplikasi Anda lebih mudah diakses.

RPGObjects
sumber
5
Deskripsi konten tidak boleh digunakan sedemikian rupa - ini tidak membuat aplikasi Anda lebih mudah diakses oleh tampilan untuk mengeluarkan informasi teknis atau debug bagi pengguna dengan kebutuhan a11y - CD untuk ini mungkin seperti "Pesan <ringkasan item> tombol". withText("Order")akan bekerja juga, dan tidak mengharuskan Anda untuk mengetahui pada saat tes apa yang Anda pesan.
ataulm