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

102

Espresso утверждает, что в этом нет необходимости Thread.sleep();, но мой код не работает, если я его не включу. Я подключаюсь к IP. Во время подключения отображается диалоговое окно прогресса. Мне нужно 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();такими вещами, какgetInstrumentation().waitForIdleSync(); и все еще не повезло.

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

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

Чад Бингем
источник
Можно ли в любом случае поставить нежелательный цикл While, если вы хотите заблокировать вызов.
kedark
хорошо .. позвольте мне объяснить. 2 предложения для вас 1. Внедрите что-то вроде механизма обратного вызова. on-connection-install вызвать один метод и показать представление. 2-й) вы хотите создать задержку между IP.enterIP (); и onView (....), чтобы вы могли поместить цикл while, который создаст подобную задержку для вызова onview (..) ... но я чувствую, что если возможно, предпочтите вариант № 1. (создание обратного вызова механизм) ...
kedark
@kedark Да, это вариант, но разве это решение для эспрессо?
Чад Бингэм
В вашем вопросе есть оставшиеся без ответа комментарии, не могли бы вы на них ответить?
Bolhoso
@Bolhoso, какой вопрос?
Чад Бингэм

Ответы:

111

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

/** 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)));
Александр Кучеренко
источник
3
Спасибо, Алекс, почему вы выбрали этот вариант вместо IdlingResource или AsyncTasks?
Тим Боланд
1
Это обходной подход, в большинстве случаев Espresso выполняет работу без каких-либо проблем и специального «кода ожидания». На самом деле я пробую несколько разных способов и считаю, что это один из наиболее подходящих по архитектуре / дизайну Espresso.
Александр Кучеренко
1
@AlexK, это сделало меня приятелем дня!
Давид Гдански
1
для меня это не работает для api <= 19, в строке throw new PerformException.Builder ()
Прабин Тимсина
4
Надеюсь, вы понимаете, что это образец, вы можете копировать / вставлять и изменять для собственных нужд. Вы несете полную ответственность за его правильное использование в личных целях, а не на меня.
Александр Кучеренко
48

Спасибо 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класс и поместить в него этот метод, чтобы легко получить к нему доступ. Вы можете использовать его в своем классе Test таким же образом:onView(isRoot()).perform(waitFor(5000));

Хесам
источник
7
метод выполнения можно даже упростить одной строкой вроде этой: uiController.loopMainThreadForAtLeast (millis);
Яир Кукиелка
Замечательно, я этого не знал: thumbs_up @YairKukielka
Hesam
Ой для занятого ожидания.
TWiStErRob
Потрясающие. Я искал это целую вечность. +1 за простое решение проблем с ожиданием.
Тобиас Райх
Намного лучший способ добавить задержку вместо использованияThread.sleep()
Вахиб Уль Хак
23

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

Хотя вышеприведенное решение определенно помогло, я в конце концов нашел этот отличный пример от 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);
МэттМэтт
источник
Я получаю I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceошибку Любая идея. Я использую Proguard, но с отключенной обфускацией.
Энтони
Попробуйте добавить -keepоператор для классов, которые не обнаруживаются, чтобы убедиться, что ProGuard не удаляет их как ненужные. Подробнее здесь: developer.android.com/tools/help/proguard.html#keep-code
MattMatt,
Я публикую вопрос stackoverflow.com/questions/36859528/… . Класс находится в файлах seed.txt и mapping.txt
Энтони
2
Если вам нужно изменить политики холостого хода, вероятно, вы неправильно реализуете ресурсы холостого хода. В долгосрочной перспективе гораздо лучше потратить время на то, чтобы это исправить. Этот метод в конечном итоге приведет к медленным и нестабильным тестам. Проверьте google.github.io/android-testing-support-library/docs/espresso/…
Хосе Альсеррека,
Вы совершенно правы. Этому ответу больше года, и с тех пор поведение простаивающих ресурсов улучшилось, так что тот же вариант использования, в котором я использовал приведенный выше код, теперь работает из коробки, правильно обнаруживая поддельный клиент API - мы больше не используем вышеуказанный По этой причине в наших инструментальных тестах используется ElapsedTimeIdlingResource. (Вы также можете, конечно, Rx все, что исключает необходимость взлома в период ожидания). Тем не менее, способ Google делать что-то не всегда лучший : philusphicalhacker.com/post/… .
MattMatt
18

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

SystemClock.sleep(1500);

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

Кабесас
источник
Expresso призван избежать этого жестко запрограммированного сна, который вызывает нестабильные тесты. если это так, я могу также использовать инструменты черного ящика, такие как appium
Emjey
6

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

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

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

Рок Боронат
источник
Я не понимаю разницы между этим и просто спящим потоком
Пабло Кавилья,
Честно говоря, я не помню, в каком видео от Google парень сказал, что мы должны использовать этот способ для сна, а не для обычного Thread.sleep(). Сожалею! Это было в некоторых из первых видеороликов, которые Google сделал об эспрессо, но я не помню, в каком ... это было несколько лет назад. Сожалею! :·) Ой! Редактировать! Я выложил ссылку на видео в PR, который открыл три года назад. Проверьте это! github.com/AdevintaSpain/Barista/pull/19
Рок Боронат
5

Это похоже на этот ответ, но использует тайм-аут вместо попыток и может быть связан с другими 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())
Большой МакЛарджОгромный
источник
4

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

Пока я не стану более осведомленным, мне все еще нужно, чтобы мои тесты как-то запускались, поэтому сейчас я использую это грязное решение, которое делает несколько попыток найти элемент, останавливается, если находит его, а если нет, ненадолго засыпает и запускается снова, пока не будет достигнуто максимальное количество попыток (максимальное количество попыток до сих пор было около 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;
}
anna3101
источник
в вашем примере findById(int itemId)метод вернет элемент (который может быть NULL) независимо от того, waitForElementUntilDisplayed(element);возвращает ли он значение true или false .... так что это не нормально
mbob
Просто хотел вмешаться и сказать, что, на мой взгляд, это лучшее решение. IdlingResources недостаточно для меня из-за 5-секундной детализации частоты опроса (слишком большой для моего варианта использования). Принятый ответ для меня тоже не работает (объяснение того, почему уже включено в длинную ленту комментариев этого ответа). Спасибо за это! Я взял вашу идею и сделал собственное решение, и оно работает как шарм.
oaskamay
Да, это единственное решение, которое сработало и для меня, когда я хотел дождаться элементов, которых нет в текущей активности.
guilhermekrz
3

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, прежде чем выполнять ваши тесты.

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

Bolhoso
источник
2
Ваш примерный код - это, по сути, тот же код, который я написал в своем вопросе.
Chad Bingham
@Binghammer я имею в виду, что тест должен вести себя так, как ведет себя пользователь. Возможно, мне не хватает того, что делает ваш метод IP.enterIP (). Вы можете отредактировать свой вопрос и уточнить это?
Bolhoso
Мои комментарии говорят, что он делает. Это просто метод в эспрессо, который заполняет диалоговое окно IP. Это все UI.
Chad Bingham
мм ... хорошо, так что вы правы, мой тест в основном делает то же самое. Вы делаете что-то из потока пользовательского интерфейса или AsyncTasks?
Bolhoso
16
Эспрессо не работает так, как предполагает код и текст этого ответа. Контрольный вызов ViewInteraction не будет ждать, пока данный Matcher завершится успешно, а скорее завершится неудачей, если условие не будет выполнено. Правильный способ сделать это - либо использовать AsyncTasks, как указано в этом ответе, либо, если это невозможно, реализовать IdlingResource, который будет уведомлять Espresso UiController о том, что можно продолжить выполнение теста.
haffax
2

Вы должны использовать ресурс Espresso Idling Resource, это предлагается в этой 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

Gastón Saillén
источник
0

Хотя я считаю, что для этого лучше всего использовать ресурсы ожидания ( 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());

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

Петр Завадски
источник
Используйте эту ниже единственную строку кода для выполнения любого тестового примера Test Espresso: SystemClock.sleep (1000); // 1
секунда
для меня это работает только путем изменения этой строки return new TimedViewInteraction(Espresso.onView(viewMatcher));наreturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger
0

Моя утилита повторяет запускаемое или вызываемое выполнение до тех пор, пока не пройдет без ошибок или не выдаст throwable после тайм-аута. Он отлично подходит для тестов эспрессо!

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

Рекомендуемый подход - заставить ваше приложение отправлять в тест сообщения о состояниях потоков. Иногда мы можем использовать встроенные механизмы, такие как OkHttp3IdlingResource. В других случаях вам следует вставлять фрагменты кода в разные места источников вашего приложения (вы должны знать логику приложения!) Только для поддержки тестирования. Более того, мы должны отключить все ваши анимации (хотя это часть пользовательского интерфейса).

Другой подход - ожидание, например SystemClock.sleep (10000). Но мы не знаем, сколько ждать, и даже большие задержки не могут гарантировать успеха. С другой стороны, ваш тест продлится долго.

Мой подход - добавить временное условие для просмотра взаимодействия. Например, мы проверяем, что новый экран должен появиться в течение 10000 мс (таймаут). Но мы не ждем и не проверяем его так быстро, как хотим (например, каждые 100 мс). Конечно, мы блокируем тестовый поток таким образом, но обычно это как раз то, что нам нужно в таких случаях.

Usage:

long timeout=10000;
long matchDelay=100; //(check every 100 ms)
EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);

ViewInteraction loginButton = onView(withId(R.id.login_btn));
loginButton.perform(click());

myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));

Это источник моего класса:

/**
 * Created by alexshr on 02.05.2017.
 */

package com.skb.goodsapp;

import android.os.SystemClock;
import android.util.Log;

import java.util.Date;
import java.util.concurrent.Callable;

/**
 * The utility repeats runnable or callable executing until it pass without errors or throws throwable after timeout.
 * It works perfectly for Espresso tests.
 * <p>
 * Suppose the last view interaction (button click) activates some background threads (network, database etc.).
 * As the result new screen should appear and we want to check it in our next step,
 * but we don't know when new screen will be ready to be tested.
 * <p>
 * Recommended approach is to force your app to send messages about threads states to your test.
 * Sometimes we can use built-in mechanisms like OkHttp3IdlingResource.
 * In other cases you should insert code pieces in different places of your app sources (you should known app logic!) for testing support only.
 * Moreover, we should turn off all your animations (although it's the part on ui).
 * <p>
 * The other approach is waiting, e.g. SystemClock.sleep(10000). But we don't known how long to wait and even long delays can't guarantee success.
 * On the other hand your test will last long.
 * <p>
 * My approach is to add time condition to view interaction. E.g. we test that new screen should appear during 10000 mc (timeout).
 * But we don't wait and check new screen as quickly as it appears.
 * Of course, we block test thread such way, but usually it's just what we need in such cases.
 * <p>
 * Usage:
 * <p>
 * long timeout=10000;
 * long matchDelay=100; //(check every 100 ms)
 * EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);
 * <p>
 * ViewInteraction loginButton = onView(withId(R.id.login_btn));
 * loginButton.perform(click());
 * <p>
 * myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));
 */
public class EspressoExecutor<T> {

    private static String LOG = EspressoExecutor.class.getSimpleName();

    public static long REPEAT_DELAY_DEFAULT = 100;
    public static long BEFORE_DELAY_DEFAULT = 0;

    private long mRepeatDelay;//delay between attempts
    private long mBeforeDelay;//to start attempts after this initial delay only

    private long mTimeout;//timeout for view interaction

    private T mResult;

    /**
     * @param timeout     timeout for view interaction
     * @param repeatDelay - delay between executing attempts
     * @param beforeDelay - to start executing attempts after this delay only
     */

    public EspressoExecutor(long timeout, long repeatDelay, long beforeDelay) {
        mRepeatDelay = repeatDelay;
        mBeforeDelay = beforeDelay;
        mTimeout = timeout;
        Log.d(LOG, "created timeout=" + timeout + " repeatDelay=" + repeatDelay + " beforeDelay=" + beforeDelay);
    }

    public EspressoExecutor(long timeout, long repeatDelay) {
        this(timeout, repeatDelay, BEFORE_DELAY_DEFAULT);
    }

    public EspressoExecutor(long timeout) {
        this(timeout, REPEAT_DELAY_DEFAULT);
    }


    /**
     * call with result
     *
     * @param callable
     * @return callable result
     * or throws RuntimeException (test failure)
     */
    public T call(Callable<T> callable) {
        call(callable, null);
        return mResult;
    }

    /**
     * call without result
     *
     * @param runnable
     * @return void
     * or throws RuntimeException (test failure)
     */
    public void call(Runnable runnable) {
        call(runnable, null);
    }

    private void call(Object obj, Long initialTime) {
        try {
            if (initialTime == null) {
                initialTime = new Date().getTime();
                Log.d(LOG, "sleep delay= " + mBeforeDelay);
                SystemClock.sleep(mBeforeDelay);
            }

            if (obj instanceof Callable) {
                Log.d(LOG, "call callable");
                mResult = ((Callable<T>) obj).call();
            } else {
                Log.d(LOG, "call runnable");
                ((Runnable) obj).run();
            }
        } catch (Throwable e) {
            long remain = new Date().getTime() - initialTime;
            Log.d(LOG, "remain time= " + remain);
            if (remain > mTimeout) {
                throw new RuntimeException(e);
            } else {
                Log.d(LOG, "sleep delay= " + mRepeatDelay);
                SystemClock.sleep(mRepeatDelay);
                call(obj, initialTime);
            }
        }
    }
}

https://gist.github.com/alexshr/ca90212e49e74eb201fbc976255b47e0

Alexshr
источник
0

Это помощник, который я использую в 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()
}
Bade
источник
0

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

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 секунд, как и перед тем, как выйти из строя, поэтому он может увеличить время выполнения ваших тестов, если успешное действие никогда не увенчается успехом.

Шон Блаховичи
источник