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和依赖注入模式来避免它。