RecyclerView öğesinin içindeki görünümü tıklamak için Espresso kullanma


100

RecyclerView öğesinin içindeki belirli bir görünümü tıklamak için Espresso'yu nasıl kullanabilirim ? Şunu kullanarak 0 konumundaki öğeyi tıklayabileceğimi biliyorum:

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

Ama o öğenin içindeki belirli bir görünüme tıklamam gerekiyor, öğenin kendisine değil.

Şimdiden teşekkürler.

-- Düzenle --

Daha kesin olmak gerekirse: Bir RecyclerView ( R.id.recycler_view) var, hangi öğeler CardView ( R.id.card_view). Her bir CardView içinde dört düğme var (diğer şeylerin yanı sıra) ve belirli bir düğmeye ( R.id.bt_deliver) tıklamak istiyorum .

Espresso 2.0'ın yeni özelliklerini kullanmak isterdim ama bunun mümkün olduğundan emin değilim.

Mümkün değilse, buna benzer bir şey kullanmak istiyorum (Thomas Keller kodunu kullanarak):

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

ama soru işaretlerine ne yazacağımı bilmiyorum.



1
Merhaba, hızlı cevap için tanklar! :-) Bu soruyu gördüm, ancak RecyclerViewActions'ı istediğim şeyi yapmak için nasıl kullanacağım konusunda herhangi bir yardım bulamıyorum . Eski Cevabı kullanmalı mıyım ? Teşekkürler.
Filipe Ramos

Sorununuza herhangi bir çözüm buldunuz mu?
HowieH

1
@HowieH: Hayır. Vazgeçtim ve şimdi Robotium kullanıyorum ...
Filipe Ramos

Yanıtlar:


136

Bunu özel görünüm eylemi ile yapabilirsiniz.

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

}

Sonra da tıklayabilirsiniz

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

2
Boş denetimi kaldırırdım, böylece çocuk görünümü bulunmazsa sessizce hiçbir şey yapmamak yerine bir istisna atılır.
Alvaro Gutierrez Perez

Cevap için teşekkürler. Ama tek bir sorunla karşılaştı. OnPerform () fonksiyonunda onView (withId ()) fonksiyonunu kullandıysam fonksiyon vurulur. Bu davranışın sebebi nedir?
thedarkpassenger

3
espresso ile çalışır: espresso-katkı: 2.2.2 ama 2.2.1 ile değil herhangi bir neden var mı? 2.2.2'yi kullanamadığım için bazı bağımlılıklarım var.
RosAng

3
Başlangıçta bununla bir NullPointerException alıyordum, bu yüzden bundan kaçınmak için getConstraints () uygulamalıydım. Aşağıdakiler benim için çalıştı: public Matcher <View> getConstraints () {return isAssignableFrom (View.class); }
dfinn

Yukarıdaki çözüm benim için çalışmıyor. Şu hatayı alıyorum: android.support.test.espresso.PerformException: 'android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemAtPositionViewAction@fad9df' görünümde 'kimliğiyle: adamhurwitz.github.io.doordashlite gerçekleştirilirken hata oluştu : id / recyclerView '.
Adam Hurwitz

67

Şimdi android.support.test.espresso.contrib ile daha kolay hale geldi:

1) Test bağımlılığı ekleyin

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 modülü hariç tutun, çünkü büyük olasılıkla zaten ona sahipsiniz

2) O zaman şöyle bir şey yapın:

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

Veya

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

Veya

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

1
Teşekkürler, bu modülleri hariç tutmak benim için bir java.lang.IncompatibleClassChangeError ile sonuçlanır.
hboy

8
ve listenin 5. maddesine belirli bir görünümde tıklamak için ne yapmalı? verilen örneklerde sadece 5. öğeye veya alttan görünümde tıklamaya ve öğeye nasıl tıklayacağımı görüyorum, ancak ikisini birden değil mi yoksa bir şeyi mi kaçırdım?
donfuxx

1
yapmam gerekiyorduexclude group: 'com.android.support' exclude module: 'recyclerview-v7'
Jemshit Iskenderov

1
RecyclerView içindeki onay kutusuna tıklamak istediğinizde işe yaramaz.
Farid

2
Kotlin'de actionOnItemAtPositionbir tür parametresi gerektirir. Oraya ne koyacağımı bilmiyorum.
ZeroDivide

7

İşte kotlin'deki sorunu nasıl çözdüm:

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

ve sonra

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

E / TestRunner ile karşılaştım: java.lang.NullPointerException: Boş bir nesne başvurusu üzerinde 'void android.view.View.getLocationOnScreen (int [])' sanal yöntemini çağırma girişimi; herhangi bir fikriniz neden?
sayvortana

6

Bir sonraki yaklaşımı deneyin:

    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

Sen edebilirsiniz tıklayın üzerindeki 3 öğe arasında recyclerViewbeğendiniz bu:

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

Do unutmayın sağlamak için ViewHolderböyle bir sonuca varılmasını başarısız değil bu yüzden türü.


1
Eksik <RecyclerView.ViewHolder>teşekkür ederim efendim;)
Skizo-ozᴉʞS

0

Yukarıdaki yanıtların tümü benim için işe yaramadı, bu yüzden görünümü istenen kimlikle döndürmek için bir hücredeki tüm görünümleri arayan yeni bir yöntem geliştirdim. İki yöntem gerektirir (bir yöntemde birleştirilebilir):

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

Daha sonra, aşağıdakileri yaparak bunu Recycler View'da kullanabilirsiniz:

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

Onunla başka bir şey yapmak istiyorsanız, görünümü geri döndürmek için bir geri arama kullanabilirsiniz veya başka herhangi bir görevi yapmak için bunun 3-4 farklı versiyonunu oluşturabilirsiniz.


0

@ Blade'in cevabının neden benim için işe yaramadığını bulmak için çeşitli yöntemler denemeye devam ettim, yalnızca bir OnTouchListener()anım olduğunu fark etmek için ViewAction'ı buna göre değiştirdim:

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

Sadece daha fazla eylemi desteklemek için bu yaklaşımı genelleştirebilirsiniz click. İşte bunun için çözümüm:

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

Ve sonra EditTextbunun gibi bir şey yapabilirsiniz:

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

-5

Önce düğmelerinize benzersiz contentDescription, yani "dağıtım düğmesi satırı 5" verin.

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

Ardından satıra kaydırın:

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

Ardından, contentDescription temel alınarak görünümü seçin.

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

İçerik Açıklaması, Espresso'nun onView özelliğini kullanmanın ve uygulamanızı daha erişilebilir hale getirmenin harika bir yoludur.


5
İçerik açıklaması gerektiğini değil şekilde kullanılabilir - bu görüşleri a11y ihtiyaçları olan kullanıcılar için teknik veya ayıklama bilgi püskürttüğünde için daha erişilebilir uygulamanızı yapmaz - bunun için CD muhtemelen "gibi bir şey olmalı Sipariş <öğesi özeti> buton". withText("Order")da işe yarardı ve test sırasında ne sipariş ettiğinizi bilmenizi gerektirmez.
2015
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.