Espresso를 사용하여 RecyclerView 항목 내부보기 클릭


100

Espresso를 사용하여 RecyclerView 항목 내의 특정보기를 클릭하려면 어떻게 해야합니까? 다음을 사용하여 위치 0에서 항목을 클릭 할 수 있다는 것을 알고 있습니다.

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

하지만 항목 자체가 아닌 해당 항목 내부의 특정보기를 클릭해야합니다.

미리 감사드립니다.

-- 편집하다 --

더 정확하게 말하자면, CardView ( ) 항목 인 RecyclerView ( R.id.recycler_view)가 있습니다 . 각 CardView 안에는 4 개의 버튼 (다른 것들 중에서)이 있고 특정 버튼 ( ) 을 클릭하고 싶습니다 .R.id.card_viewR.id.bt_deliver

Espresso 2.0의 새로운 기능을 사용하고 싶지만 이것이 가능한지 잘 모르겠습니다.

가능하지 않다면 다음과 같이 사용하고 싶습니다 (Thomas Keller 코드 사용).

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

하지만 나는 물음표에 무엇을 써야할지 모르겠습니다.



1
안녕하세요, 빠른 답변을위한 탱크! :-) 그 질문을 보았지만 RecyclerViewActions 를 사용 하여 원하는 작업을 수행 하는 방법에 대한 도움말을 찾을 수 없습니다 . 이전 답변을 사용해야 합니까? 감사.
Filipe Ramos

문제에 대한 해결책을 찾았습니까?
HowieH

1
@HowieH : 아뇨 포기하고, 지금은 ... Robotium을 사용하고 있습니다
필리페 라모스

답변:


136

보기 작업을 사용자 지정하여 수행 할 수 있습니다.

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

}

그런 다음 클릭 할 수 있습니다.

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

2
null 검사를 제거하여 자식보기가 발견되지 않으면 자동으로 아무것도하지 않고 예외가 발생합니다.
Alvaro Gutierrez Perez

답변 해주셔서 감사합니다. 그러나 한 가지 문제에 부딪 혔습니다. onPerform () 함수에서 onView (withId ()) 함수를 사용하면 함수가 발생합니다. 이 행동의 이유는 무엇입니까?
thedarkpassenger 2016-09-15

3
espresso : espresso-contrib : 2.2.2에서는 작동하지만 2.2.1에서는 작동하지 않습니다. 이유가 무엇입니까? 2.2.2를 사용할 수 없기 때문에 몇 가지 종속성이 있습니다.
RosAng

3
원래 이것으로 NullPointerException이 발생했기 때문에이를 피하기 위해 getConstraints ()를 구현해야했습니다. 다음은 나를 위해 일했습니다. public Matcher <View> getConstraints () {return isAssignableFrom (View.class); }
dfinn

위의 해결책은 저에게 효과가 없습니다. 다음 오류가 발생합니다. android.support.test.espresso.PerformException : Error perform 'android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemAtPositionViewAction@fad9df'on view 'with id : adamhurwitz.github.io.doordashlite : id / recyclerView '.
Adam Hurwitz

67

이제 android.support.test.espresso.contrib를 사용하면 더 쉬워졌습니다.

1) 테스트 종속성 추가

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 개 모듈 제외

2) 그런 다음 다음과 같이하십시오.

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

또는

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

또는

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

1
감사합니다. 해당 모듈을 제외하지 않으면 java.lang.IncompatibleClassChangeError가 발생합니다.
hboy

8
클릭하려면 어떻게해야합니까? 목록의 5 번째 항목에서 특정보기를 가정 해 보겠습니다. 제공된 예제에서 다섯 번째 항목을 클릭하거나 하위보기가있는 항목과 항목을 클릭하는 방법 만 볼 수 있습니다. 둘 다 함께 볼 수는 없습니까? 아니면 뭔가 놓친 것이 있습니까?
donfuxx

1
내가해야 할 일을했을exclude group: 'com.android.support' exclude module: 'recyclerview-v7'
Jemshit Iskenderov에게

1
RecyclerView 내부의 확인란을 클릭하고 싶을 때 쓸모가 없습니다.
Farid

2
kotlin actionOnItemAtPosition에서는 유형 매개 변수가 필요합니다. 거기에 무엇을 넣어야할지 모르겠습니다.
ZeroDivide

7

다음은 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))
}

그리고

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

E / TestRunner가 발생했습니다. java.lang.NullPointerException : null 개체 참조에서 가상 메서드 'void android.view.View.getLocationOnScreen (int [])'을 호출하려고했습니다. 왜 그런지 아세요?
sayvortana

6

다음 접근 방식을 시도하십시오.

    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

당신은 할 수 클릭3 항목recyclerView처럼이 :

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

추론이 실패하지 않도록 유형 을 제공하는 것을 잊지 마십시오 ViewHolder.


1
내가 실종됐다가 <RecyclerView.ViewHolder>당신에게 선생님 감사합니다)
Skizo-ozᴉʞS을

0

위의 모든 답변이 나를 위해 작동하지 않았으므로 요청 된 ID로 뷰를 반환하기 위해 셀 내부의 모든 뷰를 검색하는 새로운 방법을 구축했습니다. 두 가지 방법이 필요합니다 (하나로 결합 될 수 있음).

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

마지막으로 다음을 수행하여 Recycler View에서이를 사용할 수 있습니다.

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

다른 작업을 수행하려는 경우 콜백을 사용하여 뷰를 반환하거나 다른 작업을 수행하기 위해 3-4 개의 다른 버전을 빌드 할 수 있습니다.


0

나는 @blade의 대답이 나를 위해 작동하지 않는 이유를 찾기 위해 다양한 방법을 계속 시도했고, 내가 가지고 있음을 깨닫기 위해 OnTouchListener()그에 따라 ViewAction을 수정했습니다.

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

뿐만 아니라 더 많은 작업을 지원하기 위해이 접근 방식을 일반화 할 수도 있습니다 click. 이에 대한 내 해결책은 다음과 같습니다.

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

그리고 다음 EditText과 같이 할 수 있습니다.

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

-5

먼저 버튼에 고유 한 contentDescriptions (예 : "배달 버튼 행 5")를 제공합니다.

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

그런 다음 행으로 스크롤합니다.

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

그런 다음 contentDescription을 기반으로보기를 선택하십시오.

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

콘텐츠 설명은 Espresso의 onView를 사용하고 앱의 접근성을 높이는 좋은 방법입니다.


5
콘텐츠 설명은 그러한 방식으로 사용 되어서는 안됩니다. 보기가보기에 더 쉽게 접근 할 수 있도록하여 사용자에게 필요한 기술 또는 디버그 정보를 제공하지는 않습니다. 이에 대한 CD는 아마도 "Order <item summary>와 같을 것입니다. 단추". withText("Order")또한 작동하며 테스트 시간에 무엇을 주문하는지 알 필요가 없습니다.
ataulm 2015
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.