当许多元素处于层次结构中时,Espresso 匹配找到的第一个元素

Espresso match first element found when many are in hierarchy

我正在尝试编写一个 espresso 函数来匹配 espresso 根据我的函数找到的第一个元素,即使找到多个匹配项也是如此。

例如: 我有一个列表视图,其中包含包含商品价格的单元格。我希望能够将货币转换为加元并验证商品价格是否以加元表示。

我正在使用这个功能:

    onView(anyOf(withId(R.id.product_price), withText(endsWith("CAD"))))
        .check(matches(
                isDisplayed()));

这将引发 AmbiguousViewMatcherException。

在这种情况下,我不关心有多少单元格显示CAD,我只是想验证它是否显示。有没有办法让espresso一遇到满足参数的物体就通过这个测试呢?

您应该能够使用以下代码创建仅匹配第一项的自定义匹配器:

private <T> Matcher<T> first(final Matcher<T> matcher) {
    return new BaseMatcher<T>() {
        boolean isFirst = true;

        @Override
        public boolean matches(final Object item) {
            if (isFirst && matcher.matches(item)) {
                isFirst = false;
                return true;
            }

            return false;
        }

        @Override
        public void describeTo(final Description description) {
            description.appendText("should return first matching item");
        }
    };
}

据我了解,在您的情况下,在您转换货币后所有价格都应以加元为单位。因此,只需抓住第一项并验证它也应该可以解决您的问题:

onData(anything())
        .atPosition(0)
        .onChildView(allOf(withId(R.id.product_price), withText(endsWith("CAD"))))
        .check(matches(isDisplayed()));

我创建了这个匹配器,以防你有许多具有相同特征的元素,比如相同的 id,如果你不仅想要第一个元素,还想要一个 具体元素。希望这会有所帮助:

    private static Matcher<View> getElementFromMatchAtPosition(final Matcher<View> matcher, final int position) {
    return new BaseMatcher<View>() {
        int counter = 0;
        @Override
        public boolean matches(final Object item) {
            if (matcher.matches(item)) {
                if(counter == position) {
                    counter++;
                    return true;
                }
                counter++;
            }
            return false;
        }

        @Override
        public void describeTo(final Description description) {
            description.appendText("Element at hierarchy position "+position);
        }
    };
}

示例:

您有许多按钮具有从您正在使用的库中给定的相同 ID,您想要选择第二个按钮。

  ViewInteraction colorButton = onView(
            allOf(
                    getElementFromMatchAtPosition(allOf(withId(R.id.color)), 2),
                    isDisplayed()));
    colorButton.perform(click());

对于遇到我刚刚遇到的相同问题的任何人:如果您使用 Matcher @appmattus 共享来执行一个 ViewAction,那会很好,但是在执行多个 ViewAction 时,它不会:

onView(first(allOf(matcher1(), matcher2()))
      .perform(viewAction1(), viewAction2())

viewAction1 将被执行,但在执行 vewAction2 之前,匹配器会再次求值,isFirst 将始终为 return false。您将收到一条错误消息,指出没有匹配的视图。

所以,这里有一个使用多个 ViewActions 的版本,并且 return 不仅有第一个,还有第二个或第三个(或...),如果您愿意的话:

class CountViewMatcher(val count: Int) : BaseMatcher<View>() {

    private var matchingCount = 0
    private var matchingViewId: Int? = null

    override fun matches(o: Any): Boolean {
        if (o is View) {
            if (matchingViewId != null) {
                // A view already matched the count
                return matchingViewId == o.id
            } else {
                matchingCount++
                if (count == matchingCount) {
                    matchingViewId = o.id
                    return true
                } else {
                    return false
                }
            }
        } else {
            // o is not a view.
            return false
        }
    }
}