Espresso 检查是否显示吐司(一个在另一个之上)
Espresso checking if toasts are displayed (one on top of another)
我在检查是否使用浓缩咖啡显示吐司时遇到问题。我正在使用 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()));
一切正常,直到我尝试检查同一个 class 中的另一个吐司,例如:
@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)
奇怪的是,当我注释掉其中一项测试时,第二项测试工作正常,没有任何更改。当一个吐司显示在另一个吐司之上时,Espresso 似乎变得愚蠢。有什么解决办法吗?
您看到的 NoMatchingViewException
意味着您的 ToastMatcher
确实找到了具有 TYPE_TOAST
的根视图,但无法在该根中找到请求的视图(否则,您会得到一个NoMatchingRootException
).
我想原因是 Android 没有显示敬酒 一个接一个 ,而是 一个接一个 .因此,它在 toast-root 中找到的唯一视图可能是您的第一个 toast(您的第二个 toast 尚未显示)。因此,在检查第二个吐司之前,您将不得不以某种方式等到您的第一个吐司消失。不幸的是,这不是微不足道的(见下文),我相信您无法绕过更改生产代码。
中给出了可能的解决方案。基本思想是在 显示 toast 之前将 OnAttachStateChangedListener
附加到 toast 的视图 ,并使用该侦听器跟踪视图何时附加到视图层次结构和从视图层次结构中分离。然后可以使用它来实现可以等待吐司消失的自定义 IdlingResource
。
espresso 等待事物的方式是通过 IdlingResource
s。我目前看不到如何在不更改生产代码的情况下创建自定义空闲资源来等待吐司。因此,与上述答案类似的东西是我能想到的最好的,即使所需的生产代码更改不是很有吸引力。
也就是说,请注意您的 ToastMatcher
解决方案(通常在 Whosebug 和博客上推荐)也不是测试 toasts 的真正可靠方法。它适用于大多数情况,但并非总是如此。考虑例如以下片段:
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()
检查。但此时吐司将消失,因此检查失败。我希望这足以说明这种方法的固有问题。 https://groups.google.com/d/msg/android-test-kit-discuss/uaHdXuVm-Bw/cuQASd3PdpgJ 中 Valera Zakharov 的以下声明似乎证实了没有简单的解决方案来测试浓缩咖啡吐司:
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 然后还建议在生产代码中添加一些挂钩(据我所知)。因此,我想添加一个基于一些生产代码挂钩的 IdlingResource
确实是你能做的最好的事情(这也可能使你的 toast 测试总体上更稳定,因为你可以然后按照 Zakharov 概述的那样测试你的 toasts)。
问题是在您测试第二个时显示之前显示的 toast。您可以通过使 mToast
成员变量对测试可见来解决此问题,并使用它来取消 @After
中的任何活动 toast,如下所示:
显示吐司时(测试中Activity的生产代码):
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Toast mToast;
private void showToast(final String text) {
mToast = Toast.makeText(this, text, Toast.LENGTH_LONG);
mToast.show();
}
测试代码(与被测代码在同一个包中):
@After
public void tearDown() {
// Remove any toast message that is still shown:
Toast toast = mActivityRule.getActivity().mToast;
if (toast != null) {
toast.cancel();
}
}
这将要求您稍微更改生产代码,但在最新版本的Android Studio中使用@VisibleForTesting
会报错如果你在其他地方使用了成员变量。
我在检查是否使用浓缩咖啡显示吐司时遇到问题。我正在使用 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()));
一切正常,直到我尝试检查同一个 class 中的另一个吐司,例如:
@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)
奇怪的是,当我注释掉其中一项测试时,第二项测试工作正常,没有任何更改。当一个吐司显示在另一个吐司之上时,Espresso 似乎变得愚蠢。有什么解决办法吗?
您看到的 NoMatchingViewException
意味着您的 ToastMatcher
确实找到了具有 TYPE_TOAST
的根视图,但无法在该根中找到请求的视图(否则,您会得到一个NoMatchingRootException
).
我想原因是 Android 没有显示敬酒 一个接一个 ,而是 一个接一个 .因此,它在 toast-root 中找到的唯一视图可能是您的第一个 toast(您的第二个 toast 尚未显示)。因此,在检查第二个吐司之前,您将不得不以某种方式等到您的第一个吐司消失。不幸的是,这不是微不足道的(见下文),我相信您无法绕过更改生产代码。
OnAttachStateChangedListener
附加到 toast 的视图 ,并使用该侦听器跟踪视图何时附加到视图层次结构和从视图层次结构中分离。然后可以使用它来实现可以等待吐司消失的自定义 IdlingResource
。
espresso 等待事物的方式是通过 IdlingResource
s。我目前看不到如何在不更改生产代码的情况下创建自定义空闲资源来等待吐司。因此,与上述答案类似的东西是我能想到的最好的,即使所需的生产代码更改不是很有吸引力。
也就是说,请注意您的 ToastMatcher
解决方案(通常在 Whosebug 和博客上推荐)也不是测试 toasts 的真正可靠方法。它适用于大多数情况,但并非总是如此。考虑例如以下片段:
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()
检查。但此时吐司将消失,因此检查失败。我希望这足以说明这种方法的固有问题。 https://groups.google.com/d/msg/android-test-kit-discuss/uaHdXuVm-Bw/cuQASd3PdpgJ 中 Valera Zakharov 的以下声明似乎证实了没有简单的解决方案来测试浓缩咖啡吐司:
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 然后还建议在生产代码中添加一些挂钩(据我所知)。因此,我想添加一个基于一些生产代码挂钩的 IdlingResource
确实是你能做的最好的事情(这也可能使你的 toast 测试总体上更稳定,因为你可以然后按照 Zakharov 概述的那样测试你的 toasts)。
问题是在您测试第二个时显示之前显示的 toast。您可以通过使 mToast
成员变量对测试可见来解决此问题,并使用它来取消 @After
中的任何活动 toast,如下所示:
显示吐司时(测试中Activity的生产代码):
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Toast mToast;
private void showToast(final String text) {
mToast = Toast.makeText(this, text, Toast.LENGTH_LONG);
mToast.show();
}
测试代码(与被测代码在同一个包中):
@After
public void tearDown() {
// Remove any toast message that is still shown:
Toast toast = mActivityRule.getActivity().mToast;
if (toast != null) {
toast.cancel();
}
}
这将要求您稍微更改生产代码,但在最新版本的Android Studio中使用@VisibleForTesting
会报错如果你在其他地方使用了成员变量。