70

In my test, after one action, there are two possible views which can appear and both of them are correct. How can I check if one of the view is displayed. For a single view I can check with is Displayed(). But that would fail if other view is visible instead. I want to pass the test if any one of those two views are displayed.

onMyButton.perform(click());

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

After, perform click on MyButton, any one of the view (1 or 2) is expected to appear but not both. It is not fixed that which one would be displayed. How can I check if any one of them is displayed?

eleven
  • 6,779
  • 2
  • 32
  • 52
user846316
  • 6,037
  • 6
  • 31
  • 40
  • According to link https://code.google.com/p/android-test-kit/wiki/EspressoSamples#Asserting_that_a_view_is_not_displayed, "The above approach works if the view is still part of the hierarchy". Search text on "Asserting that a view is not displayed". – The Original Android Mar 25 '15 at 17:22
  • Similar question https://stackoverflow.com/questions/20807131/espresso-return-boolean-if-view-exists – Michael Osofsky May 24 '20 at 22:51

10 Answers10

64

It's possible to catch the exceptions raised by Espresso like this:

If you want to test if a view is in hierarchy:

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

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

This exception will be thrown if the view is not in the hierarchy.

Sometimes the view can be in the hierarchy, but we need to test if it is displayed, so there is another exception for assertions, like this:

try {
    onView(withText("Button")).check(matches(isDisplayed()));
    // View is displayed
} catch (AssertionFailedError e) {
    // View not displayed
}
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Lafayette
  • 891
  • 6
  • 10
  • 4
    you have to wait for a timeout though, and instead of messing with the timeouts it would be nicer to just check "is this thing here, yes or no". Only reason I'm here is that I need to reduce device minutes for cloud device testing. It's not enough to just wait 30 seconds to hit an exception, and contextually managing the timeouts feels wrong – Saik Caskey Apr 20 '17 at 14:41
  • 1
    Expecting exceptions is generally a code smell, with some exceptions I suppose like parsing unknown input - but even that is arguable. – RyPope Dec 20 '17 at 07:28
  • I like this answer, essentially the question is, if there is any API that can return a boolean value (true or false) instead of ViewAssertion to tell the user if the element is visible. This is helpful so automated functional tests can be coded accordingly. – MG Developer Jan 01 '18 at 21:42
63

There are two cases here that you could be trying to cover. The first is if you are checking if the view "is displayed on the screen to the user" in which case you would use isDisplayed()

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

or the negation

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

The other case is if you are checking if the view is visible but not necessarily displayed on the screen (ie. an item in a scrollview). For this you can use withEffectiveVisibility(Visibility)

onView(matcher).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
RyPope
  • 2,645
  • 27
  • 51
25

You can use Matchers.anyOf to check if any of the two views are displayed:

onView(
   anyOf(withId(R.id.view_1), withId(R.id.view_2)) 
).check(matches(isDisplayed()));
Luiz Augusto
  • 827
  • 10
  • 8
14

For the ones looking to check the visibility status for a view; here are some utility functions I use.

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

And can be used as follows

onView(withId(R.id.progressBar)).isVisible()
onView(withId(R.id.progressBar)).isGone()
dgngulcan
  • 3,101
  • 1
  • 24
  • 26
11

I researched Espresso a bit, and I found this @ Espresso Samples.

  1. Search text "Asserting that a view is not displayed". It says "The above approach works if the view is still part of the hierarchy." So I think your code should work but you need to use ViewAssertions also. Using your code, perhaps do this:

    if (ViewAssertions.doesNotExist()) == null) {
       return;
    }
    onMyPageOne.check(matches(isDisplayed()));
    
  2. Another technique is check for UI existence. Search for text "Asserting that a view is not present". Using your code, my best suggestion is:

    onMyPageOne.check(doesNotExist());

Note: This calls doesNotExist method.

Their sample code is: onView(withId(R.id.bottom_left)).check(doesNotExist());

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
The Original Android
  • 6,147
  • 3
  • 26
  • 31
7

Utility class which allows to check if view is visible, gone or 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;
        }
    }
}

And usage could look like this:

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

Another view assertion which could help to check extra visibility properties of a view and its parents: it checks 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
        }
    }
}
eleven
  • 6,779
  • 2
  • 32
  • 52
  • 1
    It's mind blowing how we are supposed to write such utils instead to use them out of the box. Visibility check is such a common and simple scenario! – WindRider Apr 06 '22 at 11:03
4

The problem is that all assertoin() and check() methods return Assertion that stops test flow if failed.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Dmitry Gr
  • 783
  • 6
  • 11
1

One simple way to check for a View or its subclass like a Button is to use method getVisibility from View class. I must caution that visibility attribute is not clearly defined in the GUI world. A view may be considered visible but may be overlapped with another view, for one example, making it hidden.

Another way but more accurate (I have not tried) is to check for the rectangular bounds of the View. Not so simple.

Is that clear enough? cannot give you specific examples since you did not post code.

The Original Android
  • 6,147
  • 3
  • 26
  • 31
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()));
    }
dira
  • 30,304
  • 14
  • 54
  • 69
0

When I face this situation I generally split into multiple tests. One test sets the conditions for view #1 to be displayed and the other test sets the conditions for view #2 to be displayed.

But let's say that you can't really control the conditions. For example, what if it depends on a random number or it depends on a third-party resource such as a calculation on a server? In that case, I usually solve the problem mocking. That way I can control the conditions so I know exactly which view to expect. I use Dependency Injection to set the mock I need for each test.

Michael Osofsky
  • 11,429
  • 16
  • 68
  • 113