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();
但仍然没有运气。
这是唯一的方法吗?或者我错过了什么?
提前致谢。
答案 0 :(得分:96)
在我看来,正确的做法是:
/** 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)));
答案 1 :(得分:40)
感谢AlexK的精彩回答。有些情况下你需要在代码中做一些延迟。它不一定等待服务器响应,但可能正在等待动画完成。我个人对Espresso idolingResources有问题(我认为我们正在为一个简单的事情编写许多代码行)所以我改变了AlexK对以下代码的处理方式:
fade-in
因此,您可以创建一个opacity
类并将此方法放入其中,以便轻松访问它。
您可以在Test类中使用它:/**
* 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);
}
};
}
答案 2 :(得分:20)
我在寻找类似问题的答案时偶然发现了这个问题,我在等待服务器响应并根据响应更改元素的可见性。
虽然上面的解决方案肯定有帮助,但我最终找到了this excellent example from chiuki,现在每当我等待应用程序闲置期间的操作时,我都会使用这种方法。
我已将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);
答案 3 :(得分:14)
我认为添加此行更容易:
SystemClock.sleep(1500);
在返回之前等待给定的毫秒数(uptimeMillis)。类似于sleep(long),但不会抛出InterruptedException; interrupt()事件被推迟到下一个可中断操作。在至少经过指定的毫秒数后才返回。
答案 4 :(得分:7)
您可以使用Barista方法:
BaristaSleepActions.sleep(2000);
BaristaSleepActions.sleep(2, SECONDS);
Barista是一个包装Espresso的库,以避免添加所接受答案所需的所有代码。这是一个链接! https://github.com/SchibstedSpain/Barista
答案 5 :(得分:3)
Espresso旨在避免测试中的sleep()调用。您的测试不应打开输入IP的对话框,这应该是测试活动的责任。
另一方面,您的UI测试应该:
测试看起来应该是这样的:
// 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会在执行测试之前等待UI线程和AsyncTask池中发生的所有事情。
请记住,您的测试不应该做任何与您的应用程序负责的事情。它应该像一个“信息灵通的用户”:用户点击,验证屏幕上显示的内容,但事实上,知道组件的ID
答案 6 :(得分:3)
我是编码和Espresso的新手,所以虽然我知道好的和合理的解决方案是使用空转,但我还不够智能。
直到我变得更有知识,我仍然需要我的测试以某种方式运行,所以现在我使用这个脏的解决方案,尝试找到一个元素,如果找到它就会停止,如果没有,短暂地睡觉并重新开始直到达到最大尝试次数(到目前为止最多尝试次数为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;
}
答案 7 :(得分:1)
您应该使用Espresso闲置资源,此their docs
建议空闲资源表示异步操作,其结果 影响UI测试中的后续操作。通过注册空闲 使用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'
官方仓库: CodeLab
答案 8 :(得分: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());
这也允许您为视图操作和查看断言添加超时。
答案 9 :(得分:0)
我的实用程序重复执行runnable或callable,直到它没有错误地通过,或者在超时后抛出throwable。 它非常适合Espresso测试!
假设最后一次视图交互(按钮点击)激活一些后台线程(网络,数据库等)。 结果,应该出现一个新屏幕,我们想在下一步检查它, 但是我们不知道新屏幕何时可以进行测试。
建议的方法是强制您的应用程序向测试发送有关线程状态的消息。 有时我们可以使用像OkHttp3IdlingResource这样的内置机制。 在其他情况下,您应该在应用程序源的不同位置插入代码片段(您应该知道应用程序逻辑!)以仅用于测试支持。 此外,我们应该关闭所有动画(尽管它是UI的一部分)。
另一种方法是等待,例如SystemClock.sleep(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
答案 10 :(得分: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()
}
答案 11 :(得分:0)
这类似于this answer,但是使用超时代替尝试,并且可以与其他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())
答案 12 :(得分: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秒钟,因此,如果成功的操作永远不会成功,它可能会增加测试的执行时间。
答案 13 :(得分:0)
您还可以使用CountDownLatch阻止线程,直到收到来自服务器或超时的响应。
倒数锁存器是一种简单而优雅的解决方案,不需要外部库。它还可以帮助您专注于要测试的实际逻辑,而不是过度设计异步等待或等待响应
void testServerAPIResponse() {
Latch latch = new CountDownLatch(1);
//Do your async job
Service.doSomething(new Callback() {
@Override
public void onResponse(){
ACTUAL_RESULT = SUCCESS;
latch.countDown(); // notify the count down latch
// assertEquals(..
}
});
//Wait for api response async
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
assertEquals(expectedResult, ACTUAL_RESULT);
}