Espresso gebruiken om op weergave in Recycler-item te klikken

Hoe kan ik Espresso gebruiken om op een specifieke weergave in een RecyclerView-item te klikken? Ik weet dat ik op het item op positie 0 kan klikken met:

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

Maar ik moet op een specifieke weergave in dat item klikken en niet op het item zelf.

Bij voorbaat dank.

— bewerken —

Om precies te zijn: ik heb een RecyclerView(R.id.recycler_view) waarvan de items CardView(R.id.card_view). Binnen elke CardViewheb ik vier knoppen (onder andere) en ik wil op een specifieke knop klikken (R.id.bt_deliver).

Ik wil graag de nieuwe functies van Espresso 2.0 gebruiken, maar ik weet niet zeker of dat mogelijk is.

Als dat niet mogelijk is, wil ik zoiets gebruiken (met Thomas Keller-code):

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

maar ik weet niet wat ik op de vraagtekens moet zetten.


Antwoord 1, autoriteit 100%

Je kunt het doen met de actie ‘Weergave aanpassen’.

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

Dan kun je erop klikken met

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

Antwoord 2, autoriteit 47%

Nu met android.support.test.espresso.contrib is het eenvoudiger geworden:

1)Testafhankelijkheid toevoegen

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

*exclusief 3 modules, omdat je deze waarschijnlijk al hebt

2) Doe dan iets als

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

Of

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

Of

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

Antwoord 3, autoriteit 9%

Hier is hoe ik het probleem in kotlin heb opgelost:

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

en dan

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

Antwoord 4, autoriteit 4%

Je kunt klikkenop derde itemvan recyclerViewals volgt:

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

Vergeet nietom het type ViewHolderop te geven, zodat gevolgtrekking niet mislukt.


Antwoord 5, autoriteit 4%

Probeer de volgende aanpak:

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

Antwoord 6

Je kunt deze aanpak zelfs veralgemenen om meer acties te ondersteunen, niet alleen click. Hier is mijn oplossing hiervoor:

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

En dan voor EditTextkun je zoiets als dit doen:

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

Antwoord 7

Alle antwoorden hierboven werkten niet voor mij, dus heb ik een nieuwe methode ontwikkeld die alle weergaven in een cel doorzoekt om de weergave met de gevraagde ID terug te geven. Het vereist twee methoden (kunnen worden gecombineerd tot één):

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

Definitief kun je dit gebruiken in Recycler View door het volgende te doen:

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

Je zou een callback kunnen gebruiken om de weergave terug te sturen als je er iets anders mee wilt doen, of je kunt er 3-4 verschillende versies van maken om andere taken uit te voeren.


Antwoord 8

Ik bleef verschillende methoden uitproberen om erachter te komen waarom het antwoord van @blade niet werkte voor mij, om me alleen maar te realiseren dat ik een OnTouchListener()heb, heb ik de ViewAction dienovereenkomstig aangepast:

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

Antwoord 9

Geef uw knoppen eerst een unieke inhoudBeschrijvingen, d.w.z. “bezorgknoprij 5”.

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

Scroll vervolgens naar rij:

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

Selecteer vervolgens de weergave op basis van contentDescription.

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

Contentbeschrijving is een geweldige manier om Espresso’s onView te gebruiken en uw app toegankelijker te maken.

Other episodes