在Espresso中,当多个视图匹配时如何避免AmbigouslyViewMatcherException

问题描述 投票:0回答:11

有 gridView,其中有一些图像。 gridView 的单元格来自相同的预定义布局,具有相同的 id 和 desc。

R.id.item_image == 2131493330

onView(withId(is(R.id.item_image))).perform(click());

由于网格中的所有单元格都有相同的 id,所以它得到了

AmbiguousViewMatcherException
。 如何只选择第一个或其中任何一个? 谢谢!

android.support.test.espresso.AmbigouslyViewMatcherException:“with id: is <2131493330>”匹配层次结构中的多个视图。 问题视图在下面标有“****匹配****”。

+------------->ImageView{id=2131493330, res-name=item_image, desc=图像, 可见性=VISIBLE, 宽度=262, 高度=262, has-focus=false, has-focusable = false,has-window-focus = true,is-clickable = false,is-enabled = true,is-focused = false,is-focusable = false,is-layout-requested = false,is-selected = false,root-is-layout-requested=false,has-input-connection=false,x=0.0,y=0.0} ****匹配****

+------------->ImageView{id=2131493330, res-name=item_image, desc=图像, 可见性=VISIBLE, 宽度=262, 高度=262, has-focus=false, has-focusable = false,has-window-focus = true,is-clickable = false,is-enabled = true,is-focused = false,is-focusable = false,is-layout-requested = false,is-selected = false,root-is-layout-requested=false,has-input-connection=false,x=0.0,y=0.0} ****匹配**** |

android android-espresso
11个回答
120
投票

编辑:有人在评论中提到withParentIndex现在可用,请在使用下面的自定义解决方案之前先尝试一下。

令我惊讶的是,我无法通过简单地提供索引和匹配器(即 withText、withId)来找到解决方案。当您处理 onData 和 ListViews 时,接受的答案只能解决问题。

如果屏幕上有多个具有相同 resId/text/contentDesc 的视图,您可以使用此自定义匹配器选择所需的视图,而不会导致 AmbigouslyViewMatcherException:

public static Matcher<View> withIndex(final Matcher<View> matcher, final int index) {
    return new TypeSafeMatcher<View>() {
        int currentIndex = 0;

        @Override
        public void describeTo(Description description) {
            description.appendText("with index: ");
            description.appendValue(index);
            matcher.describeTo(description);
        }

        @Override
        public boolean matchesSafely(View view) {
            return matcher.matches(view) && currentIndex++ == index;
        }
    };
}

例如:

onView(withIndex(withId(R.id.my_view), 2)).perform(click());

将对 R.id.my_view 的第三个实例执行单击操作。


31
投票

与网格视图情况不完全相关,但您可以使用 hamcrest

allOf
匹配器来组合多个条件:

import static org.hamcrest.CoreMatchers.allOf;

onView(allOf(withId(R.id.login_password), 
             withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
        .check(matches(isCompletelyDisplayed()))
        .check(matches(withHint(R.string.password_placeholder)));

28
投票

您应该使用

onData()
来对
GridView
进行操作:

onData(withId(R.id.item_image))
        .inAdapterView(withId(R.id.grid_adapter_id))
        .atPosition(0)
        .perform(click());

此代码将单击

GridView

中第一项内的图像

19
投票

尝试过@FrostRocket 的答案,看起来最有希望,但需要添加一些自定义:

public static Matcher<View> withIndex(final Matcher<View> matcher, final int index) {
    return new TypeSafeMatcher<View>() {
        int currentIndex;
        int viewObjHash;

        @SuppressLint("DefaultLocale") @Override
        public void describeTo(Description description) {
            description.appendText(String.format("with index: %d ", index));
            matcher.describeTo(description);
        }

        @Override
        public boolean matchesSafely(View view) {
            if (matcher.matches(view) && currentIndex++ == index) {
                viewObjHash = view.hashCode();
            }
            return view.hashCode() == viewObjHash;
        }
    };
}

12
投票

我创建了一个 ViewMatcher 来匹配它找到的第一个视图。 也许这对某人有帮助。 例如。当你没有 AdapterView 来使用 onData() 时。

/**
 * Created by stost on 15.05.14.
 * Matches any view. But only on first match()-call.
 */
public class FirstViewMatcher extends BaseMatcher<View> {


   public static boolean matchedBefore = false;

   public FirstViewMatcher() {
       matchedBefore = false;
   }

   @Override
   public boolean matches(Object o) {
       if (matchedBefore) {
           return false;
       } else {
           matchedBefore = true;
           return true;
       }
   }

   @Override
   public void describeTo(Description description) {
       description.appendText(" is the first view that comes along ");
   }

   @Factory
   public static <T> Matcher<View> firstView() {
       return new FirstViewMatcher();
   }
}

像这样使用它:

 onView(FirstViewMatcher.firstView()).perform(click());

10
投票

案例:

onView( withId( R.id.songListView ) ).perform( RealmRecyclerViewActions.scrollTo( Matchers.first(Matchers.withTextLabeled( "Love Song"))) );
onView( Matchers.first(withText( "Love Song")) ).perform( click() );

在我的 Matchers.class 中

public static Matcher<View> first(Matcher<View> expected ){

    return new TypeSafeMatcher<View>() {
        private boolean first = false;

        @Override
        protected boolean matchesSafely(View item) {

            if( expected.matches(item) && !first ){
                return first = true;
            }

            return false;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("Matcher.first( " + expected.toString() + " )" );
        }
    };
}

10
投票

与网格视图没有特别相关,但我遇到了类似的问题,我的 RecyclerView 和我的 root 布局 上的元素都具有 相同的 id 并且都 显示到屏幕上。帮助我解决这个问题的是检查descendancy

,例如:

onView(allOf(withId(R.id.my_view), not(isDescendantOfA(withId(R.id.recyclerView))))).check(matches(withText("My Text")));


2
投票
最迟 * 运行 -> 记录 Espresso 测试

通过单击不同位置的相同 ID 视图,会为它们生成不同的代码,因此请尝试一下。

它实际上解决了这些问题。


1
投票
您可以简单地制作 NthMatcher,如下所示:

class NthMatcher internal constructor(private val id: Int, private val n: Int) : TypeSafeMatcher<View>(View::class.java) { companion object { var matchCount: Int = 0 } init { var matchCount = 0 } private var resources: Resources? = null override fun describeTo(description: Description) { var idDescription = Integer.toString(id) if (resources != null) { try { idDescription = resources!!.getResourceName(id) } catch (e: Resources.NotFoundException) { // No big deal, will just use the int value. idDescription = String.format("%s (resource name not found)", id) } } description.appendText("with id: $idDescription") } public override fun matchesSafely(view: View): Boolean { resources = view.resources if (id == view.id) { matchCount++ if(matchCount == n) { return true } } return false } }

这样声明:

fun withNthId(resId: Int, n: Int) = CustomMatchers.NthMatcher(resId, n)

并像这样使用:

onView(withNthId(R.id.textview, 1)).perform(click())
    

1
投票
在Kakao查看

https://github.com/agoda-com/Kakao/issues/90:

class InputScreen : Screen<InputScreen>() { fun inputLayout(lambda: KEditText.() -> Unit) = KEditText { withIndex(0, { withId(R.id.input_layout) }) }.invoke(lambda) }
    

0
投票
如果您使用多个“包含”小部件,因此您会陷入错误情况 AmbigouslyViewMatcherException,即重复索引,其中 tv - TextView 和 in - Include

onView(allOf(withId(R.id.tvYourName),isDescendantOfA(withId(R.id.inParentName)))).perform(click())
    
© www.soinside.com 2019 - 2024. All rights reserved.