android - Espresso 检查是否显示 toast (一个在另一个上面)

标签 android testing toast android-espresso

我在检查是否使用 espresso 显示 toast 时遇到问题。我正在使用类(class):

       import android.os.IBinder;
    import android.support.test.espresso.Root;
    import android.view.WindowManager;
    import org.hamcrest.Description;
    import org.hamcrest.TypeSafeMatcher;

    public class ToastMatcher extends TypeSafeMatcher<Root> {

    @Override
    public void describeTo(Description description) {
        description.appendText("is toast");
    }

    @Override
    public boolean matchesSafely(Root root) {
        int type = root.getWindowLayoutParams().get().type;
        if ((type == WindowManager.LayoutParams.TYPE_TOAST)) {
            IBinder windowToken = root.getDecorView().getWindowToken();
            IBinder appToken = root.getDecorView().getApplicationWindowToken();
            if (windowToken == appToken) {
                // windowToken == appToken means this window isn't contained by any other windows.
                // if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
                return true;
            }
        }
        return false;
    }

}

并通过以下方式检查 Toast:

onView(withText(R.string.unauthorized)).inRoot(new ToastMatcher())
            .check(matches(isDisplayed()));

一切正常,直到我尝试检查同一类中的另一个 toast ,例如:

@Test
public void messageOnBack() throws Exception{
pressBack();
onView(withText(R.string.exit_on_back)).inRoot(new ToastMatcher())
            .check(matches(isDisplayed()));

然后第一个通过但第二个出错:

    android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with string from resource id: <2131165323>[unauthorized] value: Wrong login or password.

View Hierarchy:
+>LinearLayout{id=-1, visibility=VISIBLE, width=660, height=116, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=1}
|
+->AppCompatTextView{id=16908299, res-name=message, visibility=VISIBLE, width=528, height=58, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=66.0, y=29.0, text=Please click BACK again to exit., input-type=0, ime-target=false, has-links=false}
|
at dalvik.system.VMStack.getThreadStackTrace(Native Method)

奇怪的是,当我注释掉其中一项测试时,第二项测试工作正常,没有任何更改。当一个 toast 显示在另一个 toast 之上时,Espresso 似乎变得愚蠢。有什么解决办法吗?

最佳答案

您看到的 NoMatchingViewException 意味着您的 ToastMatcher 确实找到了具有 TYPE_TOAST 的 Root View ,但无法在该根中找到请求的 View (否则,您将得到一个 NoMatchingRootException)。

我想原因是 Android 没有显示 toasts 一个接一个,而是一个接一个。因此,它在 toast-root 中找到的唯一 View 可能是您的第一个 toast(您的第二个 toast 尚未显示)。因此,在检查第二个 toast 之前,您将不得不以某种方式等到您的第一个 toast 消失。不幸的是,这不是微不足道的(见下文),我相信您无法绕过更改生产代码。

https://stackoverflow.com/a/32023568/1059766 中给出了可能的解决方案.基本思想是在显示 toast 之前OnAttachStateChangedListener 附加到 toast 的 View ,并使用该监听器跟踪 View 何时附加到 View 层次结构和从 View 层次结构分离。然后可以使用它来实现自定义 IdlingResource,它可以等待 toast 消失。

espresso 等待事物的方式是通过IdlingResources。我目前看不到如何在不更改生产代码的情况下创建自定义空闲资源来等待 toast 。因此,与上述答案类似的东西是我能想到的最好的,即使对生产代码所需的更改不是很有吸引力。

也就是说,请注意您的 ToastMatcher 解决方案(通常在 stackoverflow 和博客上推荐)也不是测试 toasts 的真正可靠方法。它适用于大多数情况,但并非总是如此。考虑例如以下 fragment :

new AsyncTask<Void, Void, Void>() {
    public void doInBackground(...) {
        // start background work for 10s (or just Thread.sleep(10000))
    }
}.execute()
Toast.make(context, R.string.mytoast, Toast.LENGTH_SHORT).show()

由于 espresso 总是等待 UI 线程和所有异步任务空闲,因此在上面的示例中它将等待(大约)10 秒,直到执行 isDisplayed() 检查。但此时 toast 将消失,因此检查失败。我希望这足以说明这种方法的固有问题。 Valera Zakharov 在 https://groups.google.com/d/msg/android-test-kit-discuss/uaHdXuVm-Bw/cuQASd3PdpgJ 中的以下声明似乎证实没有简单的解决方案来测试 Espresso toast :

Short answer: Unfortunately, there is no thread-safe way of doing this in Android, so we don't provide this capability in Espresso.
Details: The way Toasts are implemented makes it possible to detect a toast has been displayed. However there is no way to see if a Toast has been requested, thru a call to show()) or to block between the period of time between show() and when the toast has become visible. This is opens up unresolvable timing issues (that you can only address thru sleep & hope) [...].

Zakharov 然后还建议在生产代码中添加一些钩子(Hook)(据我所知)。因此,我想添加一个基于一些生产代码 Hook 的 IdlingResource 确实是你能做的最好的(这也可能使你的 toast 测试总体上更稳定,因为你可以然后测试你的 toasts 概述扎哈罗夫)。

关于android - Espresso 检查是否显示 toast (一个在另一个上面),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38329114/

相关文章:

java - 使用 Thread.sleep 了解循环中的 toast

android - Apptentive从1.7.3版本升级到2.1.1版本时出现NoClassDefFoundError

ruby-on-rails - Git:在推送到本地或远程主机之前强制测试

Tizen TV 上的 XmlHttpRequest 退出应用程序

fonts - 编程字体和语法高亮测试/示例文本

perl - Test::Class 启动/设置继承问题

android - snackbar 与 toast

java - 基于 child 的Android ViewAnimator高度变化

android - 如何使动画过渡android

javascript - Javascript Web 中的共享元素转换