Эспрессо: Нить.спать();
Эспрессо утверждает, что нет необходимости 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());
Я пробовал этот код С и без the Thread.sleep(); но он говорит R.id.Button не существует. Единственный способ заставить его работать - это спать.
кроме того, я попытался заменить Thread.sleep(); С getInstrumentation().waitForIdleSync(); и все равно не повезло.
это единственный способ сделать это? Или я что-то упустил?
спасибо заранее.
9 ответов:
на мой взгляд правильный подход будет:
/** 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)));
спасибо Алексу за его удивительный ответ. Есть случаи, когда вам нужно сделать некоторую задержку в коде. Это не обязательно ждет ответа сервера, но может ждать анимации, чтобы сделать. У меня лично есть проблема с 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));
я наткнулся на этот поток при поиске ответа на аналогичную проблему, где я ждал ответа сервера и изменения видимости элементов на основе ответа.
хотя решение выше определенно помогло, я в конце концов нашел это отличный пример от чиуки и теперь используйте этот подход в качестве моего go-to всякий раз, когда я жду действий во время периодов простоя приложения.
Я добавил 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);
Я думаю, что это более легко, чтобы добавить эту строку:
SystemClock.sleep(1500);ожидает заданное количество миллисекунд (uptimeMillis) перед возвращением. Аналогично sleep (long), но не вызывает InterruptedException; события interrupt () откладываются до следующей прерываемой операции. Не возвращается, пока не пройдет по крайней мере указанное количество миллисекунд.
Вы можете просто использовать методы бариста:
BaristaSleepActions.sleep(2000);
BaristaSleepActions.sleep(2, SECONDS);Barista-это библиотека, которая обертывает эспрессо, чтобы избежать добавления всего кода, необходимого для принятого ответа. А вот и ссылка! https://github.com/SchibstedSpain/Barista
Эспрессо построен, чтобы избежать вызовов sleep() в тестах. Ваш тест не должен открывать диалоговое окно для ввода 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, перед выполнением тестов.
помните, что ваши тесты не должны делать то, что ваша ответственность приложение. Он должен вести себя как "хорошо информированный пользователь": пользователь, который нажимает, проверяет, что что-то отображается на экране, но, по сути, знает идентификаторы компонентов
Я новичок в кодировании и эспрессо, поэтому, хотя я знаю, что хорошим и разумным решением является использование холостого хода, я еще недостаточно умен, чтобы это сделать.
пока я не стану более осведомленным, мне все еще нужно, чтобы мои тесты каким-то образом запускались, поэтому сейчас я использую это грязное решение, которое делает несколько попыток найти элемент, останавливается, если он его находит, а если нет, ненадолго спит и начинает снова, пока не достигнет максимального nr попыток (наибольшее количество попыток до сих пор было около 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; }Я использую это во всех методах, которые находят элементы по ID, текст, родитель и т. д.:
static ViewInteraction findById(int itemId) { ViewInteraction element = onView(withId(itemId)); waitForElementUntilDisplayed(element); return element; }
В то время как я думаю, что лучше всего использовать холостой ход ресурсов для этого (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());Это также позволяет добавлять тайм-ауты для просмотра действий и просмотра утверждений.
моя утилита повторяет запускаемое или вызываемое выполнение, пока оно не пройдет без ошибок или не бросит throwable после таймаута. Он отлично работает для тестов Эспрессо!
Предположим, что последнее взаимодействие с видом (нажатие кнопки) активирует некоторые фоновые потоки (сеть, база данных и т. д.). В результате должен появиться новый экран, и мы хотим проверить его на следующем шаге, но мы не знаем, когда новый экран будет готова к тестированию.
Рекомендуемый подход заключается в том, чтобы заставить ваше приложение для отправки сообщений о состоянии потоков в тест. Иногда мы можем использовать встроенные механизмы, такие как OkHttp3IdlingResource. В других случаях вы должны вставлять фрагменты кода в разные места источников вашего приложения (вы должны знать логику приложения!) только для поддержки тестирования. Кроме того, мы должны отключить все ваши анимации (хотя это часть пользовательского интерфейса).
Другой подход ждет, например, SystemClock.сон(10000). Но мы не знаем, как долго ждать и даже длительные задержки не могут гарантировать успех. С другой стороны, ваш тест будет длиться долго.
Мой подход-добавить условие времени для просмотра взаимодействия. Например, мы проверяем, что новый экран должен появиться во время 10000 mc (тайм-аут). Но мы не ждем и проверяем его так быстро, как мы хотим (например, каждые 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
Comments