Espresso:Thread.sleep();

Espresso声称不需要Thread.sleep(); ,但我的代码不工作,除非我包括它。 我连接到一个IP。 连接时显示进度对话框。 我需要sleep ,等待对话解散。 这是我使用它的testing片段:

  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不存在。 我唯一能做的就是睡觉。

另外,我已经尝试replaceThread.sleep();getInstrumentation().waitForIdleSync(); 仍然没有运气。

这是唯一的方法来做到这一点? 还是我错过了什么?

提前致谢。

在我看来,正确的做法是:

 /** 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))); 

感谢AlexK的真棒回答。 有些情况下,您需要延迟代码。 它不一定等待服务器响应,但可能正在等待animation完成。 我个人对于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); } }; } 

所以你可以创build一个Delay类,并在其中放置这个方法以便轻松访问它。 你可以在你的Test类中使用它: onView(isRoot()).perform(waitFor(5000));

当我在等待服务器响应并根据响应改变元素的可见性时寻找类似问题的答案时,我偶然发现了这个线程。

虽然上面的解决scheme肯定有帮助,但是我最终从chiuki中find了这个优秀的例子 ,现在当我在等待应用程序闲置期间发生的行为时,将这种方法用作我的去向。

我已经添加了ElapsedTimeIdlingResource()到我自己的公用事业类,现在可以有效地使用它作为一个Espresso适当的select,现在使用是很好,干净:

 // 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; 中断()事件被推迟到下一个可中断操作。 直到至less达到指定的毫秒数才会返回。

build立Espresso是为了避免在testing中的sleep()调用。 你的testing不应该打开一个对话框input一个IP,这应该是testing活动的责任。

另一方面,你的UItesting应该:

  • 等待IP对话框出现
  • 填写IP地址并点击进入
  • 等待你的button出现,然后点击它

testing应该是这样的:

 // 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()); 

在执行testing之前,Espresso会等待UI线程和AsyncTask池中发生的所有事情完成。

请记住,你的testing不应该做任何你的应用程序的责任。 它的行为应该像一个“消息灵通的用户”:一个用户点击,validation屏幕上显示的东西,但事实上,知道组件的ID

您可以使用Barista方法:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista是一个包装Espresso的库,以避免添加接受的答案所需的所有代码。 这里有一个链接! https://github.com/SchibstedSpain/Barista

我的实用程序重复运行或可调用的执行,直到它传递没有错误或超时后抛出。 它完美的咖啡testing!

假设最后的视图交互(button点击)激活一些后台线程(networking,数据库等)。 因此,新的屏幕应该出现,我们希望在下一步中检查它,但是我们不知道什么时候可以testing新的屏幕。

推荐的方法是强制你的应用发送关于线程状态的消息给你的testing。 有时我们可以使用像OkHttp3IdlingResource这样的内置机制。 在其他情况下,您应该将代码片段插入到应用程序源代码的不同位置(您应该知道应用程序逻辑!)以仅用于testing支持。 此外,我们应该closures所有的animation(虽然这是关于用户界面的一部分)。

另一种方法是等待,例如SystemClock.sleep(10000)。 但是我们不知道等待多久,甚至拖延也不能保证成功。 另一方面,你的testing将持续很长时间。

我的方法是添加时间条件来查看交互。 例如,我们testing在10000微秒(超时)期间应该出现新的屏幕。 但是,我们不会等待,看看新的屏幕。 当然,我们以这种方式阻止testing线程,但通常这就是我们在这种情况下所需要的。

 Usage: long timeout=10000; long matchDelay=100; //(check every 100 ms) EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(10000, 100); ViewInteraction loginButton = onView(withId(R.id.login_btn)); loginButton.perform(click()); myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed()))); 

这是我的class级来源:

 /** * 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, eg 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. Eg 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>(10000, 100); * <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/02142880442299563b174d8b349cadd4

我对编程和Espresso很陌生,所以虽然我知道合理的解决scheme是使用空闲,但我还不够聪明。

直到我变得更有知识,我仍然需要我的testing以某种方式运行,所以现在我使用这个肮脏的解决scheme,它做了一些尝试find一个元素,停止,如果它发现,如果没有,暂时睡觉,并开始再次达到最大尝试次数(到目前为止尝试的最高次数已经在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; } 

虽然我认为最好是使用Idling资源( 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()); 

这也允许您为查看操作和查看断言添加超时。