Эспрессо: вернуть логическое значение, если представление существует

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

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

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

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

person Chad Bingham    schedule 27.12.2013    source источник
comment
Аналогичный вопрос stackoverflow.com/questions/29250506/   -  person Michael Osofsky    schedule 25.05.2020


Ответы (8)


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

Сказав это, вы все равно можете добиться вышеперечисленного, реализовав свой собственный 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
    }
person ValeraZakharov    schedule 28.12.2013
comment
У меня к вам еще один вопрос, есть ли что-то, что мне нужно знать при переключении между действиями? Когда я нажимаю кнопку с эспрессо, это переходит в другое действие, я не могу больше нажимать кнопки в следующем действии. - person Chad Bingham; 31.12.2013
comment
При переключении между действиями в процессе инструментированного приложения ничего особенного делать не нужно. Если поток управления покидает ваше приложение, вы не можете взаимодействовать с пользовательским интерфейсом (это ограничение инструментов Android). - person ValeraZakharov; 02.01.2014
comment
Точка здесь isDisplayed работает только в том случае, если вид находится внутри экрана. В анимации движения представление должно существовать, но оно может быть за пределами экрана. - person Christopher Francisco; 04.02.2016
comment
Мне не нужно было ловить NoMatchingViewException, но AssertionFailedError - person luckyhandler; 17.09.2016
comment
@ValeraZakharov Условная логика в модульных тестах может быть нежелательной, но предполагается, что среда автоматизации пользовательского интерфейса выполняет функциональные тесты, а не модульные тесты пользовательского интерфейса. Условная логика часто требуется для перехода в известное начальное состояние пользовательского интерфейса, особенно для теста, который находится глубоко внутри как представлений, так и логики приложения. Приложения не являются линейными с точки зрения пользовательского интерфейса. Вы не хотите начинать с шага 1 каждый раз, когда хотите протестировать шаги 200, 201 и 202. Пользователь переустанавливает приложение каждый раз, когда хочет выбрать другое меню и посмотреть, что отображается? Нет. - person jerimiah797; 25.01.2017
comment
Тесты пользовательского интерфейса, написанные разработчиком, могут и должны быть детерминированными (точно так же, как модульные тесты). В прошлом году я выступил с докладом, в котором рассказал, как это можно сделать на Android: slideslive.com/38897360/. Есть место для нескольких end2end тестов пользовательского интерфейса (где вы можете не полностью контролировать среду), но это не было целью Espresso, которую пытается решить (следовательно, API не упрощает) . - person ValeraZakharov; 01.02.2017
comment
Так связывать руки разработчику — это просто плохой дизайн библиотеки. Вы не знаете моих вариантов использования. Библиотеки должны облегчать написание кода, они никогда не должны мешать разработчику писать код, который он хочет написать. Кроме того, почти каждое известное мне использование Espresso предназначено для тестов взаимодействия с пользователем. Никто не использует его для модульного тестирования. Я даже не уверен, что модульное тестирование графического компонента без тестирования взаимодействия с пользователем хоть сколько-нибудь полезно — человек, смотрящий на компонент, намного превосходит автоматизированный тест. - person Gabe Sechan; 24.02.2017
comment
Кроме того, тот факт, что у вас есть DoesNoteExists, но нет DoesExists, усугубляет ситуацию - я даже не могу решить, в каком направлении происходит сбой. Если бы он просто возвращал логическое значение, я мог бы добавить not. Просто ужасный сбой в дизайне библиотеки. - person Gabe Sechan; 24.02.2017

Я думаю, чтобы имитировать 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'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;
    }
}

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

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

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

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

person TWiStErRob    schedule 11.09.2016
comment
Использование IsInstanceOf.any прекрасно работает, но является ли это предполагаемым импортом? Есть 3 возможности. - person Heinzlmaen; 13.02.2020
comment
@Heinzlmaen Возможно, instanceOf(View.class) было бы чище, но мне нравится, как читается override fun getConstraints() = any(View::class.java). В конце концов, все 6 из этих вещей создадут один и тот же класс IsInstanceOf. Я бы использовал Matchers.any (поскольку Matchers имеет большинство методов), я рассматриваю все в подпакетах hamcrest как детали реализации, настолько, что я исключаю их через AS>Настройки>Редактор>Общие>Автоматический импорт>Исключить из . ... - person TWiStErRob; 13.02.2020

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

https://github.com/marcosdiez/espresso_clone

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

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

person user3411862    schedule 12.03.2014
comment
Очень приятно. Большое спасибо. - person Chad Bingham; 12.03.2014
comment
Эспрессо 2.0, 2.1 имеют метод exists()? Я не вижу этого метода в com.android.support.test.espresso:espresso-core:2.1' - person Shivaraj Patil; 20.05.2015
comment
@ShivarajPatil нет, это клон эспрессо. Смотрите ссылку в ответе. - person Chad Bingham; 17.05.2016

Вы также проверяете код ниже. Если отображается вид, он щелкнет, иначе он пройдет.

onView(withText("OK")).withFailureHandler(new FailureHandler() {
        @Override
        public void handle(Throwable error, Matcher<View> viewMatcher){
        }
    }).check(matches(isDisplayed())).perform(customClick());
person Dhiren Mudgil    schedule 01.09.2016
comment
Это также пройдет, если customClick() не удастся, но я думаю, это то, что вы хотите. - person TWiStErRob; 11.09.2016
comment
Это хороший подход, использующий API по назначению. Другой подход заключается в реализации clickIfVisible() ViewAction. - person Jerry101; 02.01.2017
comment
... или более общий фильтр для ViewActions ifVisible(click()). - person Jerry101; 02.01.2017

Основываясь на ответе Дхирена Мадгила, я написал следующий метод:

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 отображается в данный момент.

person trooper    schedule 23.03.2017

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

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

        onView(snackBarMatcher).check(doesNotExist())

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


person Duncan McGregor    schedule 22.05.2019

Прошло некоторое время с тех пор, как эта проблема была заявлена, но, поскольку она является одной из самых популярных в 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
person Morten Løvborg    schedule 30.01.2019

Почему никто не упомянул:

onView(withId(R.id.some_view_id)).check(matches(not(doesNotExist())))

просто добавьте not перед DoesNotExist. Но если вы часто используете эту логику, лучше использовать собственный сопоставитель.

person David    schedule 17.09.2019
comment
Это, безусловно, лучший и самый чистый способ работы с существующими. Я не уверен, почему он не получил голосов, которых заслуживает. Это должен был быть принятый ответ. - person The_Martian; 30.06.2021