Espresso - 如何检查一个视图是否已显示

71

在我的测试中,执行一个动作后可能会出现两个正确的视图。我该如何检查其中一个视图是否被显示?对于单个视图,我可以使用isDisplayed()进行检查,但如果另一个视图可见,则会失败。如果这两个视图中的任何一个被显示,我想要通过测试。

onMyButton.perform(click());

onMyPageOne.check(matches(isDisplayed())); //view 1
or
onMyPageTwo.check(matches(isDisplayed())); //view 2

在点击"MyButton"按钮后,预计会出现其中一个视图(1或2),但不会同时出现两个。不能确定哪个视图会被显示。 如何检查它们中的任何一个是否被显示?

After, 点击"MyButton"按钮后,预期只会出现其中一个视图(1或2),而不是两个同时出现。无法确定哪个视图会被显示。 如何检查它们中的任何一个是否已显示?


根据链接https://code.google.com/p/android-test-kit/wiki/EspressoSamples#Asserting_that_a_view_is_not_displayed,“如果视图仍然是层次结构的一部分,则上述方法有效”。搜索文本“断言视图未显示”。 - The Original Android
类似问题 https://dev59.com/HWIi5IYBdhLWcg3w8AFy - Michael Osofsky
10个回答

64

您可以像这样捕获Espresso引发的异常:

如果您想测试视图是否在层次结构中:

try {
    onView(withText("Button")).perform(click());
    // View is in hierarchy

} catch (NoMatchingViewException e) {
    // View is not in hierarchy
}

如果视图不在层次结构中,则会抛出此异常。

有时候视图可能在层次结构中,但我们需要测试它是否被显示,因此还有另一个用于断言的异常,就像这样:

try {
    onView(withText("Button")).check(matches(isDisplayed()));
    // View is displayed
} catch (AssertionFailedError e) {
    // View not displayed
}

4
你需要等待一个超时时间,而不是调整超时时间,最好的方法是检查“这个东西在这里吗,是或否”。我在这里的唯一原因是需要减少云设备测试的设备时间。仅等待30秒并不能满足需求,管理超时时间也感觉不太对。 - Saik Caskey
1
期望异常通常是代码异味,我想有一些例外,比如解析未知输入——但即使这也是可以争论的。 - RyPope
我喜欢这个答案,本质上问题是,是否有任何API可以返回布尔值(true或false),而不是ViewAssertion来告诉用户元素是否可见。这对于编写相应的自动化功能测试非常有帮助。 - MG Developer

63

这里有两种情况可能需要考虑。第一种是如果你正在检查视图是否“显示在屏幕上呈现给用户”,那么你应该使用isDisplayed()

onView(matcher).check(matches(isDisplayed()));

或否定

onView(matcher).check(matches(not(isDisplayed())));

另一种情况是如果您要检查视图是否可见,但不一定在屏幕上显示(例如,在滚动视图中的项)。为此,您可以使用withEffectiveVisibility(Visibility)

onView(matcher).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));

帮助我完成了我的任务 继续帮忙 - Sagar Bandamwar

25

您可以使用Matchers.anyOf来检查是否显示了任何两个视图:

onView(
   anyOf(withId(R.id.view_1), withId(R.id.view_2)) 
).check(matches(isDisplayed()));

要检查视图1或视图2是否显示,您可以在viewMatcher上使用anyOf。 - Luiz Augusto
2
这段代码不能确保只有两个视图中的一个被显示,你确定它能实现吗? - dira

14

如果您想要检查视图的可见性状态,这里提供了一些我使用的实用函数。

fun ViewInteraction.isGone() = getViewAssertion(ViewMatchers.Visibility.GONE)

fun ViewInteraction.isVisible() = getViewAssertion(ViewMatchers.Visibility.VISIBLE)

fun ViewInteraction.isInvisible() = getViewAssertion(ViewMatchers.Visibility.INVISIBLE)

private fun getViewAssertion(visibility: ViewMatchers.Visibility): ViewAssertion? {
    return ViewAssertions.matches(ViewMatchers.withEffectiveVisibility(visibility))
}

并可按如下方式使用

onView(withId(R.id.progressBar)).isVisible()
onView(withId(R.id.progressBar)).isGone()

11

我研究了一下Espresso,发现这里有一个@Espresso Samples

  1. 搜索文本“断言视图未显示”。它说:“如果该视图仍然是层次结构的一部分,则上述方法有效。”所以我认为您的代码应该可以工作,但您需要使用 ViewAssertions。使用您的代码,可能要这样做:

    if (ViewAssertions.doesNotExist()) == null) {
       return;
    }
    onMyPageOne.check(matches(isDisplayed()));
    
  2. 另一种技术是检查界面是否存在。搜索文本“断言视图不存在”。 根据您的代码,我最好的建议是:

    onMyPageOne.check(doesNotExist());

注意:这会调用doesNotExist方法。

他们的示例代码是:onView(withId(R.id.bottom_left)).check(doesNotExist());


3
这是要用的。我之前写的是 not( isDisplayed()) :( - Juan Mendez

7

这是一个实用类,可以检查视图是否为visible(可见),gone(隐藏)或invisible(不可见):

public class ExtraAssertions {
    public static ViewAssertion isVisible() {
        return new ViewAssertion() {
            public void check(View view, NoMatchingViewException noView) {
                assertThat(view, new VisibilityMatcher(View.VISIBLE));
            }
        };
    }

    public static ViewAssertion isGone() {
        return new ViewAssertion() {
            public void check(View view, NoMatchingViewException noView) {
                assertThat(view, new VisibilityMatcher(View.GONE));
            }
        };
    }

    public static ViewAssertion isInvisible() {
        return new ViewAssertion() {
            public void check(View view, NoMatchingViewException noView) {
                assertThat(view, new VisibilityMatcher(View.INVISIBLE));
            }
        };
    }

    private static class VisibilityMatcher extends BaseMatcher<View> {

        private int visibility;

        public VisibilityMatcher(int visibility) {
            this.visibility = visibility;
        }

        @Override public void describeTo(Description description) {
            String visibilityName;
            if (visibility == View.GONE) visibilityName = "GONE";
            else if (visibility == View.VISIBLE) visibilityName = "VISIBLE";
            else visibilityName = "INVISIBLE";
            description.appendText("View visibility must has equals " + visibilityName);
        }

        @Override public boolean matches(Object o) {

            if (o == null) {
                if (visibility == View.GONE || visibility == View.INVISIBLE) return true;
                else if (visibility == View.VISIBLE) return false;
            }

            if (!(o instanceof View))
                throw new IllegalArgumentException("Object must be instance of View. Object is instance of " + o);
            return ((View) o).getVisibility() == visibility;
        }
    }
}

而使用可能如下所示:

onView(withId(R.id.text_message)).check(isVisible());

另一个视图断言可以帮助检查视图及其父视图的其他可见性属性: 它检查visibility, isAttachedToWindow, alpha.
class IsVisible : ViewAssertion {
    override fun check(view: View, noViewFoundException: NoMatchingViewException?) {
        ViewMatchers.assertThat(
                "View is not visible. " +
                        "visibility: ${view.visibility}, " +
                        "isAttachedToWindow: ${view.isAttachedToWindow}, " +
                        "alpha: ${view.alpha}",
                true, `is`(isViewTreeVisible(view)))
    }

    private fun isViewTreeVisible(view: View?): Boolean {
        return if (view != null) {
            val viewVisible = view.isAttachedToWindow && view.visibility == View.VISIBLE && view.alpha == 1.0f

            if (view.parent !is View) viewVisible
            else viewVisible && isViewTreeVisible(view.parent as View)
        } else {
            true
        }
    }
}

1
真让人惊叹,我们竟然需要编写这样的工具而不是直接使用它们。可见性检查是如此常见和简单的场景! - WindRider

4
问题在于所有的 assertoin()check() 方法返回失败时会停止测试流程的 Assertion

确实,这就是问题所在!我不知道是否有任何方法可以做到这一点。有没有咖啡专家可以帮忙? - user846316
2
这应该是一个注释吧 - Willi Mentzel

1

检查View或其子类(如Button)的一种简单方法是使用View类中的getVisibility方法。我必须警告,可见性属性在GUI世界中没有明确定义。例如,视图可能被认为是可见的,但可能与另一个视图重叠,使其隐藏。

另一种更准确的方法(我尚未尝试)是检查View的矩形边界。这不是很简单。

清楚了吗?由于您没有发布代码,因此无法给出具体示例。


感谢您的代码更新。我不知道Espresso是一个测试库。无论如何,我发布了另一个答案。至少我知道Java和Android。 - The Original Android

0

当我面对这种情况时,通常会将其拆分为多个测试。一个测试设置了显示视图#1的条件,另一个测试设置了显示视图#2的条件。

但是假设您无法真正控制条件。例如,如果它取决于随机数或第三方资源(例如服务器上的计算),该怎么办?在这种情况下,我通常通过模拟解决问题。这样我就可以控制条件,以便我知道应该期望哪个视图。我使用依赖注入来设置每个测试所需的模拟。


0
final AtomicBoolean view1Displayed = new AtomicBoolean(true);
Espresso.onView(ViewMatchers.withId(viewId1)).inRoot(RootMatchers.withDecorView(Matchers.is(intentsTestRule.getActivity().getWindow().getDecorView()))).withFailureHandler(new FailureHandler() {
        @Override
        public void handle(Throwable error, Matcher<View> viewMatcher) {
            view1Displayed.set(false);
        }
    }).check(ViewAssertions.matches(ViewMatchers.isDisplayed()));

if (view1Displayed.get()) {
        try {
            Espresso.onView(ViewMatchers.withId(viewId2)).inRoot(RootMatchers.withDecorView(Matchers.is(intentsTestRule.getActivity().getWindow().getDecorView()))).check(ViewAssertions.matches(Matchers.not(ViewMatchers.isDisplayed())));
        } catch (NoMatchingViewException ignore) {
        }
    } else {
        Espresso.onView(ViewMatchers.withId(viewId2)).inRoot(RootMatchers.withDecorView(Matchers.is(intentsTestRule.getActivity().getWindow().getDecorView()))).check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接