Эспрессо: Thread.sleep()

Espresso утверждает, что в Thread.sleep() нет необходимости, но мой код не работает, если я его не включу. Я подключаюсь к IP-адресу, и при подключении отображается диалоговое окно прогресса. Мне нужен вызов Thread.sleep(), чтобы дождаться закрытия диалога. Это мой тестовый код, где я его использую:

    IP.enterIP(); // fills out an IP dialog (this is done with espresso)

    //progress dialog is now shown
    Thread.sleep(1500);

    onView(withId(R.id.button).perform(click());

Я пробовал этот код без вызова Thread.sleep(), но он говорит, что R.id.Button не существует. Единственный способ заставить его работать — это вызов Thread.sleep().

Кроме того, я пытался заменить Thread.sleep() такими вещами, как getInstrumentation().waitForIdleSync(), и все равно не повезло.

Это единственный способ сделать это? Или я что-то упускаю?

Заранее спасибо.


person Chad Bingham    schedule 28.01.2014    source источник
comment
Возможно ли, чтобы вы поместили нежелательный цикл While в любом случае, если вы хотите заблокировать вызов.   -  person kedark    schedule 29.01.2014
comment
хорошо .. позвольте мне объяснить. 2 предложения для вас 1) Реализуйте что-то вроде механизма обратного вызова. при установлении соединения вызвать один метод и показать представление. 2) вы хотите создать задержку между IP.enterIP(); и onView(....), чтобы вы могли поместить цикл while, который создаст аналогичную задержку для вызова onview(..)... но я чувствую, что если возможно, пожалуйста, предпочтите вариант № 1. (создание обратного вызова механизм)...   -  person kedark    schedule 29.01.2014
comment
@kedark Да, это вариант, но это решение для эспрессо?   -  person Chad Bingham    schedule 29.01.2014
comment
В вашем вопросе есть комментарии без ответа, не могли бы вы ответить на них?   -  person Bolhoso    schedule 18.02.2014
comment
@Bolhoso, какой вопрос?   -  person Chad Bingham    schedule 18.02.2014
comment
@Binghammer это. Я спросил, делаете ли вы что-то вне потока пользовательского интерфейса или AsyncTasks? Это поможет устранить неполадки в поведении теста.   -  person Bolhoso    schedule 18.02.2014
comment
У меня много потоков и асинхронных задач.   -  person Chad Bingham    schedule 19.02.2014
comment
Я удивлен, что Thread.sleep() работает для вас.... Я получаю эту ошибку, когда сон завершен: NoActivityResumedException: No activities in stage RESUMED. Did you forget to launch the activity. (test.getActivity() or similar)?   -  person Tim Boland    schedule 03.10.2014
comment
Привет, Бингхаммер ... ты все еще используешь выбранный ответ? Или вы пошли другим путем? Асинхронные задачи? Ресурс холостого хода?   -  person Tim Boland    schedule 03.10.2014
comment
Используйте приведенную ниже одну строку кода для любого тестового примера Test Espresso: SystemClock.sleep(1000); // 1 секунда   -  person Nikunjkumar Kapupara    schedule 26.12.2017
comment
Добавление искусственной задержки в таких случаях следует считать плохой практикой. Проверьте этот пост   -  person Catalin Ghita    schedule 10.07.2018


Ответы (13)


На мой взгляд, правильный подход будет:

/** Perform action of waiting for a specific view id. */
public static ViewAction waitId(final int viewId, final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            uiController.loopMainThreadUntilIdle();
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + millis;
            final Matcher<View> viewMatcher = withId(viewId);

            do {
                for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                    // found view with required ID
                    if (viewMatcher.matches(child)) {
                        return;
                    }
                }

                uiController.loopMainThreadForAtLeast(50);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withActionDescription(this.getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new TimeoutException())
                    .build();
        }
    };
}

И тогда схема использования будет:

// wait during 15 seconds for a view
onView(isRoot()).perform(waitId(R.id.dialogEditor, TimeUnit.SECONDS.toMillis(15)));
person Oleksandr Kucherenko    schedule 21.03.2014
comment
Спасибо, Алекс, почему вы выбрали этот вариант, а не IdlingResource или AsyncTasks? - person Tim Boland; 03.10.2014
comment
Это обходной подход, в большинстве случаев Espresso выполняет свою работу без каких-либо проблем и специального «кода ожидания». На самом деле я пробовал несколько разных способов и думаю, что это один из наиболее подходящих архитектуры/дизайна эспрессо. - person Oleksandr Kucherenko; 06.10.2014
comment
@AlexK это сделало меня другом дня! - person dawid gdanski; 12.02.2016
comment
для меня это не работает для API ‹= 19, в строке выбрасывается новое PerformException.Builder() - person Prabin Timsina; 23.06.2016
comment
Он не работает, у меня не работает Matcher импорт. Любая помощь с этим? Я попробовал org.hamcrest.Matcher, но его просто не существует. - person Teo Inke; 28.06.2016
comment
используйте статический импорт для методов Matcher. Код работает нормально, вам просто нужно подтвердить, что все зависимости включены: Hamcrest + Espresso И если он не работает прямо сейчас для вас, дважды проверьте версии зависимостей. - person Oleksandr Kucherenko; 30.06.2016
comment
Надеюсь, вы понимаете, что это образец, который вы можете копировать/вставлять и модифицировать под собственные нужды. Это полностью ваша ответственность за правильное использование его в собственных бизнес-потребностях, а не в моих. - person Oleksandr Kucherenko; 05.07.2016
comment
Idling Resources — это новая вещь. Пост изначально был написан 2 года назад. Итак, для версии библиотеки эспрессо, которая была доступна 2 года назад, это было нормальное решение, основанное на внутренней логике библиотеки эспрессо. - person Oleksandr Kucherenko; 29.09.2016
comment
Должно сработать. Можете ли вы предоставить линию связи? Потенциально он может остаться в бесконечном цикле только в том случае, если во время вызова использовалось неверное значение тайм-аута. Например, минуты вместо миллисекунд... - person Oleksandr Kucherenko; 02.11.2016
comment
Я использую функцию тестирования записи эспрессо, код генерируется сам по себе, но я все еще сталкиваюсь с NoMatchingViewException... кто-нибудь знает, как решить эту проблему? Благодарю. - person Kaveesh Kanwal; 28.11.2016
comment
@AlexK Вы использовали этот код в реальной жизни? Этот фрагмент кода не работает для меня, потому что при выполнении вызывается представление, которое я ищу, еще не находится в иерархии представлений, поэтому он всегда генерирует исключение тайм-аута. Метод Perform не реагирует на новейшие представления на экране. Представление четко отображается, но ваш метод его не видит - person M Tomczyński; 16.02.2017
comment
У меня та же проблема, что и у @MTomczyński. В большинстве случаев это работает нормально, но в некоторых сценариях новые элементы представления или не передаются в параметре представления, поэтому он не может дождаться представления, даже если оно четко видно на устройстве. В остальном пока работает нормально! - person bsautermeister; 08.08.2017
comment
onView(isRoot()) имеет решающее значение для кода. Если вид виден на экране устройства, но не может быть пойман тестом, то весьма вероятно, что в onView(...) вызове был выбран неверный вид. @MTomczyński @bsautermeister - person Oleksandr Kucherenko; 09.08.2017
comment
кроме того, в случае ошибки, если вы хотите получить имя ожидаемого идентификатора, вы можете использовать это: getInstrumentation().getTargetContext().getResources().getResourceName(viewId) - person mbob; 08.11.2017
comment
@AlexK: однако вы ищете элемент в его корне, но если вы хотите найти элемент, который находится в корне, но корень находится на следующем экране (активность), ваш элемент больше не будет найден. Пример: Допустим, у нас есть LoginActivity -> введите учетные данные и нажмите кнопку входа. Я хочу дождаться отображения следующего действия (HomeActivity). В этом случае isRoot в вашем методе будет знать только для LoginActivity. - person mbob; 14.11.2017
comment
Чтобы решить этот случай, когда элемент находится в другом действии, решение в ответе stackoverflow.com/a/44327230/2871861 сработало. для меня. - person guilhermekrz; 19.12.2017

Спасибо AlexK за его потрясающий ответ. Бывают случаи, когда вам нужно сделать некоторую задержку в коде. Это не обязательно ожидание ответа сервера, но может быть ожидание завершения анимации. У меня лично есть проблема с Espresso idolingResources (я думаю, что мы пишем много строк кода для простой вещи), поэтому я изменил способ, которым AlexK делал, на следующий код:

/**
 * Perform action of waiting for a specific time.
 */
public static ViewAction waitFor(final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "Wait for " + millis + " milliseconds.";
        }

        @Override
        public void perform(UiController uiController, final View view) {
            uiController.loopMainThreadForAtLeast(millis);
        }
    };
}

Таким образом, вы можете создать класс Delay и поместить в него этот метод, чтобы легко получить к нему доступ. Вы можете использовать его в своем тестовом классе таким же образом: onView(isRoot()).perform(waitFor(5000));

person Hesam    schedule 10.03.2016
comment
метод выполнения можно даже упростить одной строкой: uiController.loopMainThreadForAtLeast(millis); - person Yair Kukielka; 18.05.2016
comment
Круто, я этого не знал :thumbs_up @YairKukielka - person Hesam; 18.05.2016
comment
Спасибо за напряженное ожидание. - person TWiStErRob; 25.08.2016
comment
Потрясающий. Я искал это целую вечность. +1 за простое решение ожидающих проблем. - person Tobias Reich; 16.11.2016
comment
Гораздо лучший способ добавить задержку вместо использования Thread.sleep() - person Wahib Ul Haq; 05.01.2017
comment
Это хорошо, но у него есть ограничение, если он связан с другим вызовом, например onData(anything()).atPosition(1).perform(click()).perform(waitFor(2000)); : если ваш тест пользовательского интерфейса перемещается на другое действие/экран, то часть waitFor() завершится ошибкой с "androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching ...". Однако, если после этого waitFor() вызывается независимо, кажется, что он работает нормально. - person Mr-IDE; 15.09.2020
comment
Я не понимаю, чем вызов этого ViewAction отличается от вызова SystemClock.sleep(millis). Оба ждут фиксированное количество миллисекунд перед возвратом. Я настоятельно рекомендую определить ваши ViewAction классы для ожидания определенного условия (как показано здесь и здесь), поэтому в большинстве случаев они возвращаются быстрее и ждут только до максимального количества миллисекунд в случае ошибки. - person Adil Hussain; 23.10.2020

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

Хотя приведенное выше решение определенно помогло, я в конце концов нашел отличный пример от chiuki, и теперь я использую этот подход, когда жду действий, происходящих во время простоя приложения. периоды.

Я добавил ElapsedTimeIdlingResource() в мой собственный класс утилит, теперь можно эффективно использовать его в качестве альтернативы эспрессо, и теперь использование приятно и чисто:

// Make sure Espresso does not time out
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);

// Now we wait
IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
Espresso.registerIdlingResources(idlingResource);

// Stop and verify
onView(withId(R.id.toggle_button))
    .check(matches(withText(R.string.stop)))
    .perform(click());
onView(withId(R.id.result))
    .check(matches(withText(success ? R.string.success: R.string.failure)));

// Clean up
Espresso.unregisterIdlingResources(idlingResource);
person MattMatt    schedule 20.07.2015
comment
Я получаю I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceошибку. Есть идеи. Я использую Proguard, но с отключенным обфускацией. - person Anthony; 25.04.2016
comment
Попробуйте добавить оператор -keep для классов, которые не были найдены, чтобы убедиться, что ProGuard не удалит их за ненадобностью. Дополнительная информация здесь: developer.android.com/tools/help/proguard. html#сохранить-код - person MattMatt; 26.04.2016
comment
Я публикую вопрос stackoverflow.com /вопросы/36859528/. Класс находится в seed.txt и mapping.txt - person Anthony; 26.04.2016
comment
Если вам нужно изменить политики бездействия, возможно, вы неправильно реализуете ресурсы бездействия. В долгосрочной перспективе гораздо лучше потратить время на исправление этого. Этот метод в конечном итоге приведет к медленным и ненадежным тестам. Ознакомьтесь с google.github.io/android-testing. -support-library/docs/espresso/ - person Jose Alcérreca; 28.09.2016
comment
Вы совершенно правы. Этому ответу больше года, и с тех пор поведение простаивающих ресурсов улучшилось, так что тот же вариант использования, в котором я использовал приведенный выше код, теперь работает из коробки, правильно обнаруживая фиктивный клиент API - мы больше не используем вышеуказанный ElapsedTimeIdlingResource в наших инструментальных тестах по этой причине. (Вы также можете, конечно, Rx все вещи, что сводит на нет необходимость взлома в период ожидания). Тем не менее, способ работы Google не всегда самый лучший: philosophicalhacker.com/post/. - person MattMatt; 28.09.2016

Я думаю, что проще добавить эту строку:

SystemClock.sleep(1500);

Ожидает заданное количество миллисекунд (uptimeMillis) перед возвратом. Аналогичен sleep(long), но не генерирует InterruptedException; События interrupt() откладываются до следующей прерываемой операции. Не возвращается, пока не истечет указанное количество миллисекунд.

person Cabezas    schedule 06.07.2016
comment
Expresso предназначен для того, чтобы избежать этого жестко запрограммированного сна, который вызывает нестабильные тесты. если это так, я могу использовать инструменты черного ящика, такие как appium - person Emjey; 23.04.2019

Это похоже на этот ответ, но использует тайм-аут вместо попыток и может быть объединено с другими ViewInteractions:

/**
 * Wait for view to be visible
 */
fun ViewInteraction.waitUntilVisible(timeout: Long): ViewInteraction {
    val startTime = System.currentTimeMillis()
    val endTime = startTime + timeout

    do {
        try {
            check(matches(isDisplayed()))
            return this
        } catch (e: NoMatchingViewException) {
            Thread.sleep(50)
        }
    } while (System.currentTimeMillis() < endTime)

    throw TimeoutException()
}

Применение:

onView(withId(R.id.whatever))
    .waitUntilVisible(5000)
    .perform(click())
person Big McLargeHuge    schedule 30.05.2019

Вы можете просто использовать методы бариста:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista — это библиотека, которая обертывает эспрессо, чтобы избежать добавления всего кода, необходимого для принятого ответа. А вот ссылка! https://github.com/SchibstedSpain/Barista

person Roc Boronat    schedule 30.03.2017
comment
Я не понимаю разницы между этим и просто спящим потоком - person Pablo Caviglia; 10.03.2020
comment
Честно говоря, я не помню, в каком видео из Google парень говорил, что мы должны использовать этот способ, чтобы сделать сон вместо того, чтобы делать общий Thread.sleep(). Извиняюсь! Это было в одном из первых видеороликов, которые Google сделал об эспрессо, но я не помню, в каком... это было несколько лет назад. Извиняюсь! :·) Ой! Редактировать! Ссылку на видео я поставил в PR, который открыл три года назад. Проверьте это! github.com/AdevintaSpain/Barista/pull/19 - person Roc Boronat; 10.03.2020

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

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

private static boolean waitForElementUntilDisplayed(ViewInteraction element) {
    int i = 0;
    while (i++ < ATTEMPTS) {
        try {
            element.check(matches(isDisplayed()));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                Thread.sleep(WAITING_TIME);
            } catch (Exception e1) {
                e.printStackTrace();
            }
        }
    }
    return false;
}

Я использую это во всех методах поиска элементов по идентификатору, тексту, родителю и т. д.:

static ViewInteraction findById(int itemId) {
    ViewInteraction element = onView(withId(itemId));
    waitForElementUntilDisplayed(element);
    return element;
}
person anna3101    schedule 02.06.2017
comment
в вашем примере метод findById(int itemId) вернет элемент (который может быть NULL), независимо от того, возвращает ли waitForElementUntilDisplayed(element); значение true или false... так что это не нормально - person mbob; 10.11.2017
comment
Просто хотел вмешаться и сказать, что это лучшее решение, на мой взгляд. IdlingResources мне недостаточно из-за 5-секундной детализации частоты опроса (слишком большой для моего варианта использования). Принятый ответ у меня тоже не работает (объяснение того, почему уже включено в длинную ленту комментариев этого ответа). Спасибо за это! Я взял вашу идею и сделал свое собственное решение, и оно работает как шарм. - person oaskamay; 14.12.2017
comment
Да, это единственное решение, которое сработало и для меня, когда я хотел дождаться элементов, которых нет в текущей активности. - person guilhermekrz; 19.12.2017

Espresso построен так, чтобы избежать вызовов sleep() в тестах. Ваш тест не должен открывать диалоговое окно для ввода IP-адреса, это должно быть ответственностью тестируемого действия.

С другой стороны, ваш UI-тест должен:

  • Подождите, пока появится диалоговое окно IP.
  • Заполните IP-адрес и нажмите Enter
  • Подождите, пока появится ваша кнопка, и нажмите ее.

Тест должен выглядеть примерно так:

// type the IP and press OK
onView (withId (R.id.dialog_ip_edit_text))
  .check (matches(isDisplayed()))
  .perform (typeText("IP-TO-BE-TYPED"));

onView (withText (R.string.dialog_ok_button_title))
  .check (matches(isDisplayed()))
  .perform (click());

// now, wait for the button and click it
onView (withId (R.id.button))
  .check (matches(isDisplayed()))
  .perform (click());

Espresso ждет завершения всего, что происходит как в потоке пользовательского интерфейса, так и в пуле AsyncTask, прежде чем выполнять ваши тесты.

Помните, что ваши тесты не должны делать ничего, за что отвечает ваше приложение. Он должен вести себя как «хорошо информированный пользователь»: пользователь, который нажимает, проверяет, что что-то отображается на экране, но, по сути, знает идентификаторы компонентов.

person Bolhoso    schedule 29.01.2014
comment
Код вашего примера — это, по сути, тот же код, который я написал в своем вопросе. - person Chad Bingham; 29.01.2014
comment
@Binghammer Я имею в виду, что тест должен вести себя так, как ведет себя пользователь. Возможно, мне не хватает того, что делает ваш метод IP.enterIP(). Можете ли вы отредактировать свой вопрос и уточнить это? - person Bolhoso; 29.01.2014
comment
Мои комментарии говорят, что он делает. Это просто метод в эспрессо, который заполняет диалог IP. Это все пользовательский интерфейс. - person Chad Bingham; 29.01.2014
comment
мм... хорошо, так что вы правы, мой тест в основном делает то же самое. Вы делаете что-то вне потока пользовательского интерфейса или AsyncTasks? - person Bolhoso; 29.01.2014
comment
Эспрессо не работает, как следует из кода и текста этого ответа. Вызов проверки в ViewInteraction не будет ждать, пока данный Matcher завершится успешно, а немедленно завершится ошибкой, если условие не будет выполнено. Правильный способ сделать это — либо использовать AsyncTasks, как указано в этом ответе, либо, если это невозможно, реализовать IdlingResource, который будет уведомлять Espresso UiController о том, когда можно приступить к выполнению теста. - person haffax; 04.04.2014
comment
@haffax Espresso так не работает, потому что тестируемое приложение не готово к этому. При правильной реализации простаивающих ресурсов это единственно правильный ответ. Остальное — ярлыки, которые в какой-то момент дадут обратный эффект. - person Jose Alcérreca; 28.09.2016

Вам следует использовать ресурс Espresso Idling, который предлагается по адресу CodeLab

Бездействующий ресурс представляет собой асинхронную операцию, результаты которой влияют на последующие операции в тесте пользовательского интерфейса. Зарегистрировав простаивающие ресурсы в Espresso, вы можете более надежно проверять эти асинхронные операции при тестировании своего приложения.

Пример асинхронного вызова от Presenter

@Override
public void loadNotes(boolean forceUpdate) {
   mNotesView.setProgressIndicator(true);
   if (forceUpdate) {
       mNotesRepository.refreshData();
   }

   // The network request might be handled in a different thread so make sure Espresso knows
   // that the app is busy until the response is handled.
   EspressoIdlingResource.increment(); // App is busy until further notice

   mNotesRepository.getNotes(new NotesRepository.LoadNotesCallback() {
       @Override
       public void onNotesLoaded(List<Note> notes) {
           EspressoIdlingResource.decrement(); // Set app as idle.
           mNotesView.setProgressIndicator(false);
           mNotesView.showNotes(notes);
       }
   });
}

Зависимости

androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'

Для androidx

androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support.test.espresso:espresso-idling-resource:3.0.2'

Официальный репозиторий: https://github.com/googlecodelabs/android-testing< /а>

Пример IdlingResource: https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample

person Gastón Saillén    schedule 26.02.2019

Вы также можете использовать CountDownLatch, чтобы заблокировать поток, пока вы не получите ответ от сервера или тайм-аут.

Защелка обратного отсчета — это простое, но элегантное решение, не требующее внешней библиотеки. Это также помогает вам сосредоточиться на фактической логике, которую нужно протестировать, а не на чрезмерном проектировании асинхронного ожидания или ожидания ответа.

void testServerAPIResponse() {


        Latch latch = new CountDownLatch(1);


        //Do your async job
        Service.doSomething(new Callback() {

            @Override
            public void onResponse(){
                ACTUAL_RESULT = SUCCESS;
                latch.countDown(); // notify the count down latch
                // assertEquals(..
            }

        });

        //Wait for api response async
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        assertEquals(expectedResult, ACTUAL_RESULT);

    }
person Mayuri Khinvasara    schedule 15.10.2020

Хотя я считаю, что для этого лучше всего использовать ресурсы бездействия (https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/), возможно, вы могли бы использовать это как запасной вариант:

/**
 * Contains view interactions, view actions and view assertions which allow to set a timeout
 * for finding a view and performing an action/view assertion on it.
 * To be used instead of {@link Espresso}'s methods.
 * 
 * @author Piotr Zawadzki
 */
public class TimeoutEspresso {

    private static final int SLEEP_IN_A_LOOP_TIME = 50;

    private static final long DEFAULT_TIMEOUT_IN_MILLIS = 10 * 1000L;

    /**
     * Use instead of {@link Espresso#onView(Matcher)}
     * @param timeoutInMillis timeout after which an error is thrown
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(long timeoutInMillis, @NonNull final Matcher<View> viewMatcher) {

        final long startTime = System.currentTimeMillis();
        final long endTime = startTime + timeoutInMillis;

        do {
            try {
                return new TimedViewInteraction(Espresso.onView(viewMatcher));
            } catch (NoMatchingViewException ex) {
                //ignore
            }

            SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
        }
        while (System.currentTimeMillis() < endTime);

        // timeout happens
        throw new PerformException.Builder()
                .withCause(new TimeoutException("Timeout occurred when trying to find: " + viewMatcher.toString()))
                .build();
    }

    /**
     * Use instead of {@link Espresso#onView(Matcher)}.
     * Same as {@link #onViewWithTimeout(long, Matcher)} but with the default timeout {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(@NonNull final Matcher<View> viewMatcher) {
        return onViewWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewMatcher);
    }

    /**
     * A wrapper around {@link ViewInteraction} which allows to set timeouts for view actions and assertions.
     */
    public static class TimedViewInteraction {

        private ViewInteraction wrappedViewInteraction;

        public TimedViewInteraction(ViewInteraction wrappedViewInteraction) {
            this.wrappedViewInteraction = wrappedViewInteraction;
        }

        /**
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction perform(final ViewAction... viewActions) {
            wrappedViewInteraction.perform(viewActions);
            return this;
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(final ViewAction... viewActions) {
            return performWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewActions);
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(long timeoutInMillis, final ViewAction... viewActions) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return perform(viewActions);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to perform view actions: " + viewActions))
                    .build();
        }

        /**
         * @see ViewInteraction#withFailureHandler(FailureHandler)
         */
        public TimedViewInteraction withFailureHandler(FailureHandler failureHandler) {
            wrappedViewInteraction.withFailureHandler(failureHandler);
            return this;
        }

        /**
         * @see ViewInteraction#inRoot(Matcher)
         */
        public TimedViewInteraction inRoot(Matcher<Root> rootMatcher) {
            wrappedViewInteraction.inRoot(rootMatcher);
            return this;
        }

        /**
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction check(final ViewAssertion viewAssert) {
            wrappedViewInteraction.check(viewAssert);
            return this;
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(final ViewAssertion viewAssert) {
            return checkWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewAssert);
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(long timeoutInMillis, final ViewAssertion viewAssert) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return check(viewAssert);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to check: " + viewAssert.toString()))
                    .build();
        }
    }
}

а затем вызовите его в своем коде, например:

onViewWithTimeout(withId(R.id.button).perform(click());

вместо

onView(withId(R.id.button).perform(click());

Это также позволяет вам добавлять тайм-ауты для действий просмотра и утверждений просмотра.

person Piotr Zawadzki    schedule 17.03.2017
comment
Используйте приведенную ниже одну строку кода для любого тестового примера Test Espresso: SystemClock.sleep(1000); // 1 секунда - person Nikunjkumar Kapupara; 26.12.2017
comment
для меня это работает только путем изменения этой строки return new TimedViewInteraction(Espresso.onView(viewMatcher)); на return new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed()))); - person Manuel Schmitzberger; 31.08.2018

Это помощник, который я использую в Kotlin для Android-тестов. В моем случае я использую longOperation для имитации ответа сервера, но вы можете настроить его по своему усмотрению.

@Test
fun ensureItemDetailIsCalledForRowClicked() {
    onView(withId(R.id.input_text))
        .perform(ViewActions.typeText(""), ViewActions.closeSoftKeyboard())
    onView(withId(R.id.search_icon)).perform(ViewActions.click())
    longOperation(
        longOperation = { Thread.sleep(1000) },
        callback = {onView(withId(R.id.result_list)).check(isVisible())})
}

private fun longOperation(
    longOperation: ()-> Unit,
    callback: ()-> Unit
){
    Thread{
        longOperation()
        callback()
    }.start()
}
person Bade    schedule 26.02.2019

Я добавлю свой способ сделать это в микс:

fun suspendUntilSuccess(actionToSucceed: () -> Unit, iteration : Int = 0) {
    try {
        actionToSucceed.invoke()
    } catch (e: Throwable) {
        Thread.sleep(200)
        val incrementedIteration : Int = iteration + 1
        if (incrementedIteration == 25) {
            fail("Failed after waiting for action to succeed for 5 seconds.")
        }
        suspendUntilSuccess(actionToSucceed, incrementedIteration)
    }
}

Вызывается так:

suspendUntilSuccess({
    checkThat.viewIsVisible(R.id.textView)
})

Вы можете добавить такие параметры, как максимальное количество итераций, длина итерации и т. д., в функцию suspendUntilSuccess.

Я по-прежнему предпочитаю использовать ресурсы бездействия, но когда тесты тормозят, например, из-за медленной анимации на устройстве, я использую эту функцию, и она работает хорошо. Конечно, он может зависать до 5 секунд, как и до сбоя, поэтому это может увеличить время выполнения ваших тестов, если действие, направленное на успех, никогда не увенчается успехом.

person Sean Blahovici    schedule 31.08.2019