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


102

Еспресо стверджує, що в цьому немає потреби 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();і досі не пощастило.

Це єдиний спосіб зробити це? Або я щось пропускаю?

Заздалегідь спасибі.


чи можна вам поставити небажаний Хоча цикл все одно ви хочете блокувати дзвінок.
кедирк

добре .. дозвольте пояснити. 2 пропозиції для вас 1-го) Реалізуйте щось на зразок механізму зворотного дзвінка on-connection - встановіть виклик одного методу та покажіть подання. 2) ви хочете створити затримку між IP.enterIP (); і onView (....), щоб ви могли поставити цикл while, який створить подібний вид затримки для виклику onview (..) ... але я відчуваю, якщо можливо, будь ласка, надайте перевагу варіант № 1. (створення зворотного дзвінка механізм) ...
kedark

@kedark Так, це варіант, але це рішення Еспресо?
Чад Бінгем

У вашому запитанні є коментарі без відповіді, чи можете ви на них відповісти?
Болхосо

@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.
Олександр Кучеренко

1
@AlexK це зробило мого товариша за день!
dawid gdanski

1
для мене це не вдається для api <= 19, на лінії кидають новий PerformException.Builder ()
Прабін Тімсіна,

4
Сподіваюся, ви зрозуміли, що його зразок, ви можете скопіювати / вставити та змінити для власних потреб. Це повністю ваша відповідальність за правильне використання його для власних потреб бізнесу, а не моєї.
Олександр Кучеренко

47

Дякую 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));


7
метод виконання навіть може бути спрощений одним рядком на зразок цього: uiController.loopMainThreadForAtLeast (millis);
Яїр Кукієлка

Чудово, я цього не знав: thumbs_up @YairKukielka
Ісам

Yikes за зайняте очікування.
TWiStErRob

Дивовижно. Я шукав це віками. +1 для простого рішення проблем очікування.
Тобіас Рейх

Набагато кращий спосіб додати затримку замість використанняThread.sleep()
Wahib Ul Haq

23

Я натрапив на цю нитку, шукаючи відповідь на подібну проблему, де я чекав відповіді сервера та зміни видимості елементів на основі відповіді.

Хоча рішення вище, безумовно, допомогло, я врешті знайшов цей чудовий приклад від Чіукі, і тепер використовую такий підхід як мій перехід, коли я чекаю, що дії будуть відбуватися протягом простою програми.

Я додав ElapsedTimeIdlingResource () до свого власного класу утиліт, тепер можу ефективно використовувати це як альтернативу Espresso, а тепер використання є приємним та чистим:

// 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 в наших інструментальних тестах. (Ви, звичайно, також можете виправити всі речі, що заперечує необхідність злому в період очікування). Тим не менше , спосіб Google робити речі не завжди краще: philosophicalhacker.com/post / ... .
MattMatt

18

Я думаю, що простіше додати цей рядок:

SystemClock.sleep(1500);

Зачекає задану кількість мілісекунд (uptimeMillis) перед поверненням. Схожий на сон (тривалий), але не кидає InterruptedException; події interrupt () відкладаються до наступної операції переривання. Не повертається, поки не минуло принаймні вказану кількість мілісекунд.


Експресо полягає в тому, щоб уникнути цього твердо копного сну, який викликає невмілі випробування. якщо це так, я можу також перейти на такі інструменти для чорної панелі, як appium
Emjey

6

Можна просто використовувати методи Бариста:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Баріста - це бібліотека, яка обгортає Еспресо, щоб уникнути додавання всього коду, необхідного для прийнятої відповіді. І ось посилання! https://github.com/SchibstedSpain/Barista


Я не розумію різниці між цим і просто сплячим потоком
Пабло Кавілья

Чесно кажучи, я не пам’ятаю, в якому відео з Google хлопець сказав, що ми повинні використовувати цей спосіб для сну, а не загального Thread.sleep(). Вибачте! Це було в деяких з перших відео, зроблених Google про Еспресо, але я не пам'ятаю, яке саме було ... це було кілька років тому. Вибачте! : ·) О! Редагувати! Я поклав посилання на відео в PR, який я відкрив три роки тому. Перевір! github.com/AdevintaSpain/Barista/pull/19
Roc Boronat

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

у вашому прикладі findById(int itemId)метод поверне елемент (який міг би бути NULL), чи waitForElementUntilDisplayed(element);повертає справжнє чи помилкове .... так, це не нормально
mbob

Просто хотів задзвонити і сказати, що це найкраще рішення на мою думку. IdlingResources мені недостатньо через детальність 5-секундного рівня опитування (занадто великий для мого випадку використання). Прийнята відповідь також не працює для мене (пояснення, чому вона вже включена в довгу подачу коментарів цієї відповіді). Дякую за це! Я взяв вашу ідею і прийняв власне рішення, і це працює як шарм.
oaskamay

Так, це єдине рішення, яке працювало і для мене, коли хотіли чекати елементів, яких немає в поточній діяльності.
guilhermekrz

3

Еспресо створено, щоб уникнути дзвінків у режимі сну (). Ваш тест не повинен відкривати діалогове вікно для введення IP-адреси, яке повинно бути відповідальністю перевіреної діяльності.

З іншого боку, ваш тест на інтерфейс повинен:

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

Пам'ятайте, що ваші тести не повинні робити нічого, що відповідає вашій програмі. Він повинен вести себе як "добре інформований користувач": користувач, який натискає, перевіряє, що щось відображається на екрані, але, по суті, знає ідентифікатори компонентів


2
Ваш приклад код - це по суті той самий код, який я написав у своєму запитанні.
Чад Бінгем

@ Binghammer, що я маю на увазі, тест повинен вести себе так, як поводиться користувач. Можливо, мені не вистачає того, що робить ваш метод IP.enterIP (). Чи можете ви відредагувати своє запитання та уточнити це?
Bolhoso

Мої коментарі кажуть, що це робить. Це лише метод еспресо, який заповнює діалогове вікно IP. Це все інтерфейс користувача.
Чад Бінгем

мм ... добре, значить, ти маєш рацію, мій тест в основному робить те саме. Ви робите щось із потоку інтерфейсу або AsyncTasks?
Bolhoso

16
Еспресо працює не так, як код і текст цієї відповіді, мабуть, мається на увазі. Перевірка дзвінка на ViewInteraction не буде чекати, поки даний Матчер вдасться, а швидше припинить негайно, якщо умова не буде виконана. Правильний спосіб зробити це - або використовувати AsyncTasks, як це було зазначено у цій відповіді, або, якщо якимось чином це не можливо, застосувати IdlingResource, який сповістить UiController Espresso, коли буде гаразд, щоб продовжити виконання тесту.
haffax

2

Ви повинні використовувати Espresso Idling Resource, що пропонується в цьому CodeLab

Ресурс простою - це асинхронна операція, результати якої впливають на наступні операції в тесті користувальницького інтерфейсу. Реєструючи ресурси простою в Espresso, ви можете надійніше перевірити ці асинхронні операції під час тестування програми.

Приклад асинхронного виклику презентатора

@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'

Для андроїд

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


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())));
Мануель Шмітцбергер,

0

Моя утиліта повторює виконання, яке можна виконати або зателефонувати, доки воно не пройде без помилок чи кидків, що підлягають перезавантаженню після таймауту. Він прекрасно працює для тестів Еспресо!

Припустимо, взаємодія останнього перегляду (натискання кнопки) активує деякі фонові потоки (мережа, база даних тощо). В результаті повинен з’явитися новий екран, і ми хочемо перевірити його на наступному кроці, але ми не знаємо, коли новий екран буде готовий до тестування.

Рекомендований підхід полягає в тому, щоб змусити вашу програму надсилати повідомлення про стан потоків у ваш тест. Іноді ми можемо використовувати вбудовані механізми, такі як 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


0

Це помічник, який я використовую в Котліні для Android Tests. У моєму випадку я використовую 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()
}

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 секунд, як і до того, як не вдалося, так що це може збільшити час виконання ваших тестів, якщо дія для успіху ніколи не вдається.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.