Espresso: return boolean if view exists

40,862

Solution 1

Conditional logic in tests is undesirable. With that in mind, Espresso's API was designed to guide the test author away from it (by being explicit with test actions and assertions).

Having said that, you can still achieve the above by implementing your own ViewAction and capturing the isDisplayed check (inside the perform method) into an AtomicBoolean.

Another less elegant option - catch the exception that gets thrown by failed check:

    try {
        onView(withText("my button")).check(matches(isDisplayed()));
        //view is displayed logic
    } catch (NoMatchingViewException e) {
        //view not displayed logic
    }

Kotlin version with an extension function:

    fun ViewInteraction.isDisplayed(): Boolean {
        try {
            check(matches(ViewMatchers.isDisplayed()))
            return true
        } catch (e: NoMatchingViewException) {
            return false
        }
    }

    if(onView(withText("my button")).isDisplayed()) {
        //view is displayed logic
    } else {
        //view not displayed logic
    }

Solution 2

I think to mimic UIAutomator you can do this:
(Though, I suggest rethinking your approach to have no conditions.)

ViewInteraction view = onView(withBlah(...)); // supports .inRoot(...) as well
if (exists(view)) {
     view.perform(...);
}

@CheckResult
public static boolean exists(ViewInteraction interaction) {
    try {
        interaction.perform(new ViewAction() {
            @Override public Matcher<View> getConstraints() {
                return any(View.class);
            }
            @Override public String getDescription() {
                return "check for existence";
            }
            @Override public void perform(UiController uiController, View view) {
                // no op, if this is run, then the execution will continue after .perform(...)
            }
        });
        return true;
    } catch (AmbiguousViewMatcherException ex) {
        // if there's any interaction later with the same matcher, that'll fail anyway
        return true; // we found more than one
    } catch (NoMatchingViewException ex) {
        return false;
    } catch (NoMatchingRootException ex) {
        // optional depending on what you think "exists" means
        return false;
    }
}

Also exists without branching can be implemented really simple:

onView(withBlah()).check(exists()); // the opposite of doesNotExist()

public static ViewAssertion exists() {
    return matches(anything());
}

Though most of the time it's worth checking for matches(isDisplayed()) anyway.

Solution 3

We need that functionality and I ended up implementing it below:

https://github.com/marcosdiez/espresso_clone

if(onView(withText("click OK to Continue")).exists()){ 
    doSomething(); 
} else { 
   doSomethingElse(); 
}

I hope it is useful for you.

Solution 4

You check with the below code also. If view is displayed it will click else it will pass on.

onView(withText("OK")).withFailureHandler(new FailureHandler() {
        @Override
        public void handle(Throwable error, Matcher<View> viewMatcher){
        }
    }).check(matches(isDisplayed())).perform(customClick());

Solution 5

Based on the answer by Dhiren Mudgil, I ended up writing the following method:

public static boolean viewIsDisplayed(int viewId)
{
    final boolean[] isDisplayed = {true};
    onView(withId(viewId)).withFailureHandler(new FailureHandler()
    {
        @Override
        public void handle(Throwable error, Matcher<View> viewMatcher)
        {
            isDisplayed[0] = false;
        }
    }).check(matches(isDisplayed()));
    return isDisplayed[0];
}

I'm using this to help determine which View in a ViewFlipper is currently displayed.

Share:
40,862

Related videos on Youtube

Chad Bingham
Author by

Chad Bingham

Arguing with a software engineer is like wrestling with a pig in the mud; after a while you realize the pig likes it. -Unknown

Updated on July 09, 2022

Comments

  • Chad Bingham
    Chad Bingham almost 2 years

    I am trying to check to see if a view is displayed with Espresso. Here is some pseudo code to show what I am trying:

    if (!Espresso.onView(withId(R.id.someID)).check(doesNotExist()){
       // then do something
     } else {
       // do nothing, or what have you
     }
    

    But my problem is .check(doesNotExist()) does not return boolean. It is just an assertion. With UiAutomator I was able to just do something like so:

     if (UiAutomator.getbyId(SomeId).exists()){
          .....
       }
    
  • Chad Bingham
    Chad Bingham over 10 years
    I have another question for you, is there anything I need to know when switching between activities? When I press a button with espresso, that changes to another activity, I am unable to press any more buttons in the next activity.
  • ValeraZakharov
    ValeraZakharov over 10 years
    There's nothing special to be done when switching between activities within the process of the instrumented application. If control flow leaves your application, you cannot interact with the UI (this is a limitation of Android instrumentation).
  • Chad Bingham
    Chad Bingham about 10 years
    Vey nice. Thank you so much.
  • Shivaraj Patil
    Shivaraj Patil almost 9 years
    Espresso 2.0, 2.1 have exists() method? I dont see this mehod in com.android.support.test.espresso:espresso-core:2.1'
  • Christopher Francisco
    Christopher Francisco about 8 years
    Point here is isDisplayed only work if the view is inside the screen. On tween animations, view should exists but it may be outside of screen.
  • Chad Bingham
    Chad Bingham almost 8 years
    @ShivarajPatil no, this is a clone of espresso. See the link in the answer.
  • TWiStErRob
    TWiStErRob over 7 years
    It'll also pass on if customClick() fails, but I guess that's what you want.
  • luckyhandler
    luckyhandler over 7 years
    I didn't have to catch a "NoMatchingViewException" but an "AssertionFailedError"
  • Jerry101
    Jerry101 over 7 years
    This is a good approach, using the API in its intended way. Another approach would be to implement a clickIfVisible() ViewAction.
  • Jerry101
    Jerry101 over 7 years
    ... or a more general filter for ViewActions ifVisible(click()).
  • jerimiah797
    jerimiah797 over 7 years
    @ValeraZakharov Conditional logic in unit tests may be undesirable, but a UI automation framework is supposed to perform functional tests, not "UI unit tests". Conditional logic is often required to get into a known UI starting state, especially for a test that is deep within both the views and logic of the app. Apps are not linear from a UI perspective. You don't want to start at step 1 every time you want to test step 200, 201, and 202. Does a user reinstall the app every time they want to choose a different menu and see what is displayed? No.
  • ValeraZakharov
    ValeraZakharov about 7 years
    Developer-written UI tests can and should be deterministic (just like unit tests). Last year, I gave this talk that outlines how you can accomplish this on Android: slideslive.com/38897360/…. There is a place for having a few end2end UI tests (where you may not be in full control of the environment), but that was not the focus of what Espresso is trying to solve (hence, the API doesn't make it easy).
  • Gabe Sechan
    Gabe Sechan about 7 years
    Tying a developer's hands like that is just bad library design. You don't know my use cases. Libraries should make it easy to write code, they should never prevent a developer from writing the code he wants to write. Also, almost every use of Espresso I know is for user interaction tests. Nobody uses it for unit testing. I'm not even convinced unit testing of a graphical component without user interactivity testing is remotely useful- a human looking at the component is far superior to an automated test.
  • Gabe Sechan
    Gabe Sechan about 7 years
    Also the entire fact you have a doesNoteExists but no doesExists makes it worse- I can't even decide what direction is a failure. If it just returned a boolean, I could throw a not in. Just a horrible failure of library design.
  • Heinzlmaen
    Heinzlmaen about 4 years
    Using IsInstanceOf.any is working great, but is that the intended import? There's 3 possibilities.
  • TWiStErRob
    TWiStErRob about 4 years
    @Heinzlmaen Maybe instanceOf(View.class) would have been cleaner, but I like how override fun getConstraints() = any(View::class.java) reads. In the end all 6 of these things will end up creating the same IsInstanceOf class. I would use Matchers.any (as Matchers has the most methods), I treat anything in a hamcrest subpackages as implementation details, so much so that I have them excluded via AS > Settings > Editor > General > Auto Import > Exclude from ....
  • The_Martian
    The_Martian almost 3 years
    This is by far the best and cleanest way of dealing with exists. I am not sure why it didn't get the upvotes it deserves. This should have been the accepted answer.
  • Just The Highlights
    Just The Highlights over 2 years
    The question states that they want to execute different code depending on whether a view exists or not. This answer does not help them do that.