Espresso: Thread.sleep();
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();
и до сих пор не повезло.
Это единственный способ сделать это? Или я чего-то не хватает?
Спасибо заранее.
Ответы
Ответ 1
На мой взгляд, правильный подход будет:
/** 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)));
Ответ 2
Спасибо 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));
Ответ 3
Я наткнулся на этот поток, когда искал ответ на аналогичную проблему, где я ждал ответа сервера и изменял видимость элементов на основе ответа.
Несмотря на то, что решение, безусловно, помогло, я в конце концов нашел этот отличный пример из чиуки и теперь использую этот подход как мой вариант, m, ожидая действий, которые будут выполняться во время периодов простоя приложения.
Я добавил 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);
Ответ 4
Я думаю, что добавить эту строку проще:
SystemClock.sleep(1500);
Ожидает заданное количество миллисекунд (от uptimeMillis) перед возвратом. Подобно sleep (long), но не бросает InterruptedException; interrupt() откладываются до следующей прерывистой операции. Не возвращается до тех пор, пока не истечет указанное количество миллисекунд.
Ответ 5
Вы можете просто использовать методы Бариста:
BaristaSleepActions.sleep(2000);
BaristaSleepActions.sleep(2, SECONDS);
Barista - это библиотека, которая упаковывает Espresso, чтобы избежать добавления всего кода, необходимого для принятого ответа. И здесь ссылка! https://github.com/SchibstedSpain/Barista
Ответ 6
Я новичок в кодировании и эспрессо, поэтому, хотя я знаю, что хорошим и разумным решением является использование холостого хода, я пока недостаточно умен, чтобы это сделать.
Пока я не становлюсь более осведомленным, мне все равно нужны мои тесты, чтобы хоть как-то бежать, поэтому пока я использую это грязное решение, которое предпринимает ряд попыток найти элемент, останавливается, если оно находит его, а если нет, кратко спит и запускается снова, пока не достигнет максимального количества попыток (наибольшее количество попыток до сих пор было около 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;
}
Ответ 7
Эспрессо построено для того, чтобы избежать вызовов sleep() в тестах. В вашем тесте не должно открываться диалоговое окно для ввода IP-адреса, которое должно быть проверкой активности.
С другой стороны, ваш тест пользовательского интерфейса должен:
- Подождите, пока появится диалоговое окно IP
- Заполните IP-адрес и нажмите кнопку
- Подождите, пока появится ваша кнопка, и щелкните по ней.
Тест должен выглядеть примерно так:
// 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, чтобы завершить работу перед выполнением ваших тестов.
Помните, что ваши тесты не должны делать ничего, что отвечает вашим приложениям. Он должен вести себя как "хорошо информированный пользователь": пользователь, который нажимает, проверяет, что что-то отображается на экране, но, по сути, знает идентификаторы компонентов
Ответ 8
Вы должны использовать 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'
для 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
Ответ 9
Это похоже на этот ответ, но использует время ожидания вместо попыток и может быть связано с другими 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())
Ответ 10
В то время как я считаю, что лучше использовать ресурсы для холостого хода (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} 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());
Это также позволяет вам добавлять таймауты для просмотра действий и просмотра утверждений.
Ответ 11
Моя утилита повторяет выполняемые или вызываемые функции, пока не пройдет без ошибок или не сгенерирует 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 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 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
Ответ 12
Это помощник, который я использую в 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()
}
Ответ 13
Я добавлю свой способ сделать это в микс:
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 секунд, как и до сбоя, поэтому он может увеличить время выполнения ваших тестов, если успешное действие никогда не будет успешным.