Espresso 不会等到显示对话框并失败
Espresso does not wait until dialog is shown and fail
我正在使用 Espresso 进行一些简单的测试。其中之一是关于单击视图并检查是否显示对话框。
我的问题是有时有效,有时无效。只有当我在检查对话框之前放置 sleep 时,它才会始终有效。任何不使用 sleep?
的解决方案
这是我的代码(非常简单):
onView(withId(R.id.forgot_password)).perform(click());
// only works if use Thread.sleep(ms) here
onView(withText(R.string.reset_password)).check(matches(isDisplayed()));
编辑:
我正在展示与静态助手的对话,但简化是这样的。而且我没有在中间执行任何后台任务。
final TextInputDialog textInputDialog = new
TextInputDialog.Builder(context)
.setTitle(titleId)
.setInputType(inputType)
.setHint(hintId)
.setPreFilledText(preFilledText)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(positiveButtonId, onTextSubmittedListener)
.create();
textInputDialog.show(textInputDialog);
谢谢!
最后看来问题出在动画上。为了使 Espresso 正常工作,需要在开发者选项菜单中禁用动画。
这样的话,问题就解决了。但是在其他情况下,问题可能是后台任务,就像对我的问题的评论所暗示的那样。所以我建议看看 IdlingResource https://medium.com/azimolabs/wait-for-it-idlingresource-and-conditionwatcher-602055f32356#.pw55uipfj or this Espresso: Thread.sleep( );
在某些情况下禁用动画是不可能的,例如:
- 当 运行 在云设备上测试时。 Firebase 测试实验室不允许更改设备配置
- 当您等待某个后台线程更新用户界面时,例如 http 响应。
在这些情况下,最简单的方法就是等待元素显示。方法如下:
简单的帮手:
class WaifForUIUpdate {
public static void waifForWithId(@IdRes int stringId) {
ViewInteraction element;
do {
waitFor(500);
//simple example using withText Matcher.
element = onView(withText(stringId));
} while (!MatcherExtension.exists(element));
}
static void waitFor(int ms) {
final CountDownLatch signal = new CountDownLatch(1);
try {
signal.await(ms, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Assert.fail(e.getMessage());
}
}
}
Matcher 来自 twisterrob
public class MatcherExtension {
@CheckResult
public static boolean exists(ViewInteraction interaction) {
try {
interaction.perform(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return any(View.class);
}
@Override
public String getDescription() {
return "check for existence";
}
@Override
public void perform(UiController uiController, View view) {
// no op, if this is run, then the execution will continue after .perform(...)
}
});
return true;
} catch (AmbiguousViewMatcherException ex) {
// if there's any interaction later with the same matcher, that'll fail anyway
return true; // we found more than one
} catch (NoMatchingViewException ex) {
return false;
} catch (NoMatchingRootException ex) {
// optional depending on what you think "exists" means
return false;
}
}
}
用法:
WaifForUIUpdate.waifForWithId(R.string.some_string);
//now do your validations
我最终使用了另一种不同于我的第一个答案的方法,它也很有效,但它更容易适应任何东西,通过使用 CountdownLatch
。
这是代码:
public static boolean viewExists(final Matcher<View> viewMatcher, final long millis) throws InterruptedException {
final Boolean[] found = new Boolean[1];
final CountDownLatch latch = new CountDownLatch(1);
ViewAction action = new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}
@Override
public String getDescription() {
return "wait for a specific view with id <" + viewMatcher.toString() + "> 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;
do {
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
if (viewMatcher.matches(child)) {
Log.d(TAG, "perform: found match");
found[0] = true;
latch.countDown();
return;
}
}
uiController.loopMainThreadForAtLeast(50);
}
while (System.currentTimeMillis() < endTime);
found[0] = false;
latch.countDown();
}
};
onView(isRoot()).perform(action);
latch.await();
return found[0];
}
Google方法是使用Idling resource类,但是需要在生产apk中插入测试代码,或者使用flavors和依赖注入模式来避免它。
我正在使用 Espresso 进行一些简单的测试。其中之一是关于单击视图并检查是否显示对话框。
我的问题是有时有效,有时无效。只有当我在检查对话框之前放置 sleep 时,它才会始终有效。任何不使用 sleep?
的解决方案这是我的代码(非常简单):
onView(withId(R.id.forgot_password)).perform(click());
// only works if use Thread.sleep(ms) here
onView(withText(R.string.reset_password)).check(matches(isDisplayed()));
编辑:
我正在展示与静态助手的对话,但简化是这样的。而且我没有在中间执行任何后台任务。
final TextInputDialog textInputDialog = new
TextInputDialog.Builder(context)
.setTitle(titleId)
.setInputType(inputType)
.setHint(hintId)
.setPreFilledText(preFilledText)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(positiveButtonId, onTextSubmittedListener)
.create();
textInputDialog.show(textInputDialog);
谢谢!
最后看来问题出在动画上。为了使 Espresso 正常工作,需要在开发者选项菜单中禁用动画。
这样的话,问题就解决了。但是在其他情况下,问题可能是后台任务,就像对我的问题的评论所暗示的那样。所以我建议看看 IdlingResource https://medium.com/azimolabs/wait-for-it-idlingresource-and-conditionwatcher-602055f32356#.pw55uipfj or this Espresso: Thread.sleep( );
在某些情况下禁用动画是不可能的,例如:
- 当 运行 在云设备上测试时。 Firebase 测试实验室不允许更改设备配置
- 当您等待某个后台线程更新用户界面时,例如 http 响应。
在这些情况下,最简单的方法就是等待元素显示。方法如下:
简单的帮手:
class WaifForUIUpdate {
public static void waifForWithId(@IdRes int stringId) {
ViewInteraction element;
do {
waitFor(500);
//simple example using withText Matcher.
element = onView(withText(stringId));
} while (!MatcherExtension.exists(element));
}
static void waitFor(int ms) {
final CountDownLatch signal = new CountDownLatch(1);
try {
signal.await(ms, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Assert.fail(e.getMessage());
}
}
}
Matcher 来自 twisterrob
public class MatcherExtension {
@CheckResult
public static boolean exists(ViewInteraction interaction) {
try {
interaction.perform(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return any(View.class);
}
@Override
public String getDescription() {
return "check for existence";
}
@Override
public void perform(UiController uiController, View view) {
// no op, if this is run, then the execution will continue after .perform(...)
}
});
return true;
} catch (AmbiguousViewMatcherException ex) {
// if there's any interaction later with the same matcher, that'll fail anyway
return true; // we found more than one
} catch (NoMatchingViewException ex) {
return false;
} catch (NoMatchingRootException ex) {
// optional depending on what you think "exists" means
return false;
}
}
}
用法:
WaifForUIUpdate.waifForWithId(R.string.some_string);
//now do your validations
我最终使用了另一种不同于我的第一个答案的方法,它也很有效,但它更容易适应任何东西,通过使用 CountdownLatch
。
这是代码:
public static boolean viewExists(final Matcher<View> viewMatcher, final long millis) throws InterruptedException {
final Boolean[] found = new Boolean[1];
final CountDownLatch latch = new CountDownLatch(1);
ViewAction action = new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}
@Override
public String getDescription() {
return "wait for a specific view with id <" + viewMatcher.toString() + "> 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;
do {
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
if (viewMatcher.matches(child)) {
Log.d(TAG, "perform: found match");
found[0] = true;
latch.countDown();
return;
}
}
uiController.loopMainThreadForAtLeast(50);
}
while (System.currentTimeMillis() < endTime);
found[0] = false;
latch.countDown();
}
};
onView(isRoot()).perform(action);
latch.await();
return found[0];
}
Google方法是使用Idling resource类,但是需要在生产apk中插入测试代码,或者使用flavors和依赖注入模式来避免它。