В Espresso, как избежать AmbiguousViewMatcherException, когда несколько представлений совпадают
Наличие gridView с несколькими изображениями. Ячейка gridView выходит из того же предопределенного макета, который имеет одинаковый идентификатор и desc.
R.id.item_image == 2131493330
onView(withId(is(R.id.item_image))).perform(click());
Поскольку все ячейки в сетке имеют одинаковый идентификатор, он получил AmbiguousViewMatcherException
.
Как просто выбрать первый или любой из них?
Спасибо!
android.support.test.espresso.AmbiguousViewMatcherException: 'с id: is < 2131493330 > ' соответствует нескольким представлениям в иерархии. Представления задач отмечены "**** MATCHES ****" ниже.
+ ------------- > ImageView {id = 2131493330, res-name = item_image, desc = Изображение, видимость = ВИДИМОСТЬ, width = 262, height = 262, has-focus = false, has-focusable = false, has-window-focus = true, is-clickable = false, is-enabled = true, is-focus = false, is-focusable = false, is-layout-request = false, is- selected = false, root-is-layout-request = false, has-input-connection = false, x = 0.0, y = 0.0} **** MATCHES ****
+ ------------- > ImageView {id = 2131493330, res-name = item_image, desc = Изображение, видимость = ВИДИМОСТЬ, width = 262, height = 262, has-focus = false, has-focusable = false, has-window-focus = true, is-clickable = false, is-enabled = true, is-focus = false, is-focusable = false, is-layout-request = false, is- selected = false, root-is-layout-request = false, has-input-connection = false, x = 0.0, y = 0.0} **** MATCHES **** |
Ответы
Ответ 1
Вы должны использовать onData()
для работы с GridView
:
onData(withId(R.id.item_image))
.inAdapterView(withId(R.id.grid_adapter_id))
.atPosition(0)
.perform(click());
Этот код нажимает на изображение внутри первого элемента в GridView
Ответ 2
Я был поражен тем, что не смог найти решение, просто указав индекс вместе с помощником (т.е. withText, withId). Принятый ответ решает проблему, когда вы имеете дело с onData и ListViews.
Если у вас несколько видов на экране с тем же resId/text/contentDesc, вы можете выбрать, какой из них вы хотите, не вызывая исключение AmbiguousViewMatcherException, используя этот настраиваемый соединитель:
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());
выполнит действие click для третьего экземпляра R.id.my_view.
Ответ 3
Не полностью связано с ситуацией с сеткой, но вы можете использовать сочетания 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)));
Ответ 4
Я создал ViewMatcher, который соответствует первому виду, которое он находит.
Может быть, это полезно для кого-то.
Например. когда у вас нет адаптера для использования 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());
Ответ 5
Попробовал ответить @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;
}
};
}
Ответ 6
Случаи:
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() + " )" );
}
};
}
Ответ 7
Также не относится конкретно к представлению сетки, но в случае, если это может быть полезно кому-то еще, у меня была похожая проблема, когда и элемент в моем RecyclerView, и в моем корневом макете имели один и тот же идентификатор и оба былипоказаны на экране. Что помогло мне решить, так это проверить descendancy
, например:
onView(allOf(withId(R.id.my_view), not(isDescendantOfA(withId(R.id.recyclerView))))).check(matches(withText("My Text")));
![enter image description here]()
Ответ 8
Вы можете просто сделать 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())
Ответ 9
Не позднее * Run → Запись эспрессо-теста
Нажав на тот же самый ID ID с другой позицией, создайте для них другой код, поэтому попробуйте.
На самом деле решить эти проблемы.