Espresso: return boolean, если вид существует

Я пытаюсь проверить, отображается ли представление с помощью Espresso. Вот какой-то псевдо-код, чтобы показать, что я пытаюсь:

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

Но моя проблема .check(doesNotExist()) не возвращает boolean. Это просто утверждение. С помощью UiAutomator я смог сделать что-то вроде этого:

 if (UiAutomator.getbyId(SomeId).exists()){
      .....
   }

Ответ 1

Условная логика в тестах нежелательна. Имея это в виду, Espresso API был разработан для того, чтобы увести автора теста от него (будучи явным с действиями и утверждениями теста).

Сказав это, вы все равно можете достичь вышеупомянутого, реализовав свой собственный ViewAction и записав проверку isDisplayed (внутри метода execute) в AtomicBoolean.

Еще один менее элегантный вариант - поймать исключение, которое выдается при неудачной проверке:

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

Версия Kotlin с функцией расширения:

    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
    }

Ответ 2

Вы также можете проверить код ниже. Если отображается представление, он будет щелкнуть еще раз, когда он пройдет.

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

Ответ 3

Я думаю, что для имитации UIAutomator вы можете это сделать:
(Хотя, я предлагаю переосмыслить ваш подход, чтобы не было никаких условий.)

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

Также exists без разветвления может быть реализовано очень просто:

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

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

Хотя большую часть времени стоит проверить matches(isDisplayed()) в любом случае.

Ответ 4

Нам нужна эта функциональность, и я реализовал ее ниже:

https://github.com/marcosdiez/espresso_clone

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

Я надеюсь, что это полезно для вас.

Ответ 5

На основании ответа Dhiren Mudgil я написал следующий метод:

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];
}

Я использую это, чтобы определить, какой вид в ViewFlipper отображается в настоящее время.

Ответ 6

Прошло некоторое время с тех пор, как эта проблема была заявлена, но, поскольку она является одной из самых популярных в Google при поиске способов убедиться, что представление присутствует, перед выполнением каких-либо действий с ним в Espresso я хотел бы поделиться своими основными способ справиться с этим.

1. Начните с написания расширения для ViewInteraction:

fun ViewInteraction.exists(): Boolean {
val viewExists = AtomicReference<Boolean>()

this.perform(object : ViewAction {
    override fun perform(uiController: UiController?, view: View?) {
        viewExists.set(view != null)
    }

    override fun getConstraints(): Matcher<View>? {
        return Matchers.allOf(ViewMatchers.withEffectiveVisibility(
                ViewMatchers.Visibility.VISIBLE),
                ViewMatchers.isAssignableFrom(View::class.java))
    }

    override fun getDescription(): String {
        return "check if view exists"
    }
})
return viewExists.get()

}

2. Создайте простой метод справки в базовом классе (который будет использоваться во всех тестовых классах):

fun viewExists(id: Int): Boolean {
    return try {
        onView(withId(id)).exists()
    } catch (e: RuntimeException) {
        false
    }
}

При этом вы либо получаете значение true или false из onView(withId(id)).exists(), либо безопасно перехватываете RuntimeException и возвращаете false.

Обычно простой проверки для .exists() было бы достаточно, но в некоторых случаях, например, когда вы удаляете элементы ListView до тех пор, пока не осталось не осталось → когда последний элемент удален, ListView может больше не присутствовать, тогда возникает исключение бросается при попытке проверить, существует ли он.

3: С вышеупомянутой реализацией безопасно проверить, существует ли какое-либо представление, так как RuntimeException хорошо обрабатывается позади сцены:

if(viewExists(R.id.something)) {
    //do something
}
//do something else

Ответ 7

Я думаю, что то, что Espresso хочет, чтобы вы сделали, - это измените свою логику, чтобы использовать doesNotExist()

У меня например

        onView(snackBarMatcher).check(doesNotExist())

        onView(withId(R.id.button)).perform(click())
        onView(snackBarMatcher).check(matches(isDisplayed()))