【问题标题】:Checking toast message in android espresso在 android espresso 中检查 toast 消息
【发布时间】:2015-04-08 01:25:35
【问题描述】:

有人知道如何在 android espresso 中测试 Toast 消息的外观吗?在 robotsium 中,它很简单,我使用过,但开始在 espresso 中工作,但没有得到确切的命令。

【问题讨论】:

  • 如果活动在显示 toast 的同时完成,则以下任何解决方案都不起作用。
  • @Slav 你有没有找到任何解决方案,即使在活动完成的情况下也包括 Toast 检查?
  • @NixSam 不幸的是没有。如果我没记错的话,在完成 Activity 的情况下,我决定检查 Activity 是否正在完成。
  • @Slav 感谢您提供的信息

标签: android android-espresso toast


【解决方案1】:

这个略长的陈述对我有用:

import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
....
onView(withText(R.string.TOAST_STRING)).inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));

【讨论】:

  • is() 方法是多余的
  • @Slava 是正确的,可以通过删除来完成:onView(withText(R.string.TOAST_STRING)).inRoot(withDecorView(not(getActivity().getWindow().getDecorView() ))) .check(matches(isDisplayed()));
  • getting cannot resolve method getActivity() 报错如何解决
  • @John:您可能正在使用新的JUnit rule-based testsActivityTestRule。您可以使用 ActivityTestRule#getActivity() 从该规则中获取活动。
  • 使用 ActivityTestRule:onView(withText(R.string.toast_text)).inRoot(withDecorView(not(mActivityRule.getActivity().getWindow().getDecorView()))).check(matches(isDisplayed()));
【解决方案2】:

接受的答案是一个很好的答案,但对我不起作用。于是我搜索了一下,发现this blog article。 这让我知道了如何去做,我更新了上面的解决方案。

首先我实现了 ToastMatcher:

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;
  }

}

然后我实现了这样的检查方法:

public void isToastMessageDisplayed(int textId) {
    onView(withText(textId)).inRoot(MobileViewMatchers.isToast()).check(matches(isDisplayed()));
}

MobileViewMatchers 是一个用于访问匹配器的容器。在那里我定义了静态方法isToast()

public static Matcher<Root> isToast() {
    return new ToastMatcher();
}

这对我来说就像一个魅力。

【讨论】:

  • 这对我不起作用,因为测试不断循环。唯一有效的是,如果我在吐司打开时触摸屏幕,它似乎会停止空转,然后就可以工作了。有什么想法吗?
  • 我需要知道您的测试设置。您要测试什么以及显示什么?听起来像是一个进度问题,stackoverflow.com/questions/33289152/progressbars-and-espresso/…。所有 API 版本都会出现这种情况吗?
  • “MobileViewMatchers”从何而来?无法导入或在代码中找到
  • WindowManager.LayoutParams.TYPE_TOAST 现已弃用。
  • 只有当 toast 出现在屏幕上时,我才能使用此代码验证 toast 消息。但是如果有以下结果的情况:a)msg1 b)msg2 c)根本没有吐司。然后选项 a 和 b 被验证,但代码卡在选项 c 中。有什么可能的解决方案?
【解决方案3】:

首先确保导入:

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;

在你的班级里,你可能有这样的规则:

@Rule
public ActivityTestRule<MyNameActivity> activityTestRule =
            new ActivityTestRule<>(MyNameActivity.class);

在你的测试中:

MyNameActivity activity = activityTestRule.getActivity();
onView(withText(R.string.toast_text)).
    inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))).
    check(matches(isDisplayed()));

这对我有用,而且很容易使用。

【讨论】:

【解决方案4】:

如果您使用的是来自 Jetpack 的最新 Android 测试工具,您知道,ActivityTestRule 已被弃用,您应该使用 ActivityScenarioActivityScenarioRule (其中包含第一个)。

先决条件。创建 decorView 变量并在测试前分配它;

@Rule
public ActivityScenarioRule<FeedActivity> activityScenarioRule = new ActivityScenarioRule<>(FeedActivity.class);

private View decorView;

@Before
public void setUp() {
    activityScenarioRule.getScenario().onActivity(new ActivityScenario.ActivityAction<FeedActivity>() {
        @Override
        public void perform(FeedActivityactivity) {
            decorView = activity.getWindow().getDecorView();
        }
    });
}

测试自己

@Test
public void given_when_thenShouldShowToast() {
    String expectedWarning = getApplicationContext().getString(R.string.error_empty_list);
    onView(withId(R.id.button))
            .perform(click());

    onView(withText(expectedWarning))
            .inRoot(withDecorView(not(decorView)))// Here we use decorView
            .check(matches(isDisplayed()));
}

getApplicationContext() 可以取自androidx.test.core.app.ApplicationProvider.getApplicationContext;

【讨论】:

  • 非常感谢!这对我有用,作为建议,您可以将字符串 id 传递给 withText()
  • 您也可以从规则中获取 decorView,因为其他答案建议 .inRoot(withDecorView(not(activityRule.activity.window.decorView)))
  • @Herman 在此示例中无法访问 ActivityRule,因为我们使用的是 ActivityScenarioRule。在此示例中,您的代码将不起作用。
  • 您真的不应该(!)在完整的测试执行期间存储对活动中任何内容的引用。而是在需要时获取参考。可能您遇到了这样一个事实,即您无法从 lambda 内部设置局部变量。使用 AtomicReference 可以避免这个问题。
【解决方案5】:

首先创建一个自定义 Toast Matcher,我们可以在测试用例中使用它 -

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) {
                  //means this window isn't contained by any other windows. 
                  return true;
                }
            }
            return false;
        }
}

1.测试是否显示 Toast 消息

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

2。测试 Toast 消息是否不显示

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

3.测试 id 吐司包含特定文本消息

onView(withText(R.string.mssage)).inRoot(new ToastMatcher())
.check(matches(withText("Invalid Name"));

谢谢, 阿努贾

注意 - 此答案来自 This POST.

【讨论】:

  • 应该是: if (windowToken == appToken) { //表示此窗口不包含在任何其他窗口中。返回真; }
  • @anuja jain 当stackoverflow.com/a/40756080/5230044 回答有效时,为什么我们应该参考您的回答
  • 如 cmets 中的原始帖子中所述,这不起作用。它失败并出现异常。这篇文章也缺少返回 true;在标记匹配时的注释之后,因此它也不起作用。
  • Root 不是公共 API 的一部分。
  • 很好的答案,这应该是公认的
【解决方案6】:

虽然这个问题有一个公认的答案——顺便说一句,这对我不起作用——我想在 Kotlin 中添加我从 Thomas R. 的答案中得出的解决方案:

package somepkg

import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.Root
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
import android.view.WindowManager.LayoutParams.TYPE_TOAST
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher

/**
 * This class allows to match Toast messages in tests with Espresso.
 *
 * Idea taken from: https://stackoverflow.com/a/33387980
 *
 * Usage in test class:
 *
 * import somepkg.ToastMatcher.Companion.onToast
 *
 * // To assert a toast does *not* pop up:
 * onToast("text").check(doesNotExist())
 * onToast(textId).check(doesNotExist())
 *
 * // To assert a toast does pop up:
 * onToast("text").check(matches(isDisplayed()))
 * onToast(textId).check(matches(isDisplayed()))
 */
class ToastMatcher(private val maxFailures: Int = DEFAULT_MAX_FAILURES) : TypeSafeMatcher<Root>() {

    /** Restrict number of false results from matchesSafely to avoid endless loop */
    private var failures = 0

    override fun describeTo(description: Description) {
        description.appendText("is toast")
    }

    public override fun matchesSafely(root: Root): Boolean {
        val type = root.windowLayoutParams.get().type
        @Suppress("DEPRECATION") // TYPE_TOAST is deprecated in favor of TYPE_APPLICATION_OVERLAY
        if (type == TYPE_TOAST || type == TYPE_APPLICATION_OVERLAY) {
            val windowToken = root.decorView.windowToken
            val appToken = root.decorView.applicationWindowToken
            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
            }
        }
        // Method is called again if false is returned which is useful because a toast may take some time to pop up. But for
        // obvious reasons an infinite wait isn't of help. So false is only returned as often as maxFailures specifies.
        return (++failures >= maxFailures)
    }

    companion object {

        /** Default for maximum number of retries to wait for the toast to pop up */
        private const val DEFAULT_MAX_FAILURES = 5

        fun onToast(text: String, maxRetries: Int = DEFAULT_MAX_FAILURES) = onView(withText(text)).inRoot(isToast(maxRetries))!!

        fun onToast(textId: Int, maxRetries: Int = DEFAULT_MAX_FAILURES) = onView(withText(textId)).inRoot(isToast(maxRetries))!!

        fun isToast(maxRetries: Int = DEFAULT_MAX_FAILURES): Matcher<Root> {
            return ToastMatcher(maxRetries)
        }
    }

}

希望对以后的读者有所帮助——用法在评论中有所描述。

【讨论】:

  • 这是一个非常好的答案,为我解决了问题,因为集成了重试策略
  • 这适用于我的旧设备但在 android 11 上它在我尝试过的两台设备上失败..
【解决方案7】:

我编写了我的自定义吐司匹配器:

import android.view.WindowManager
import androidx.test.espresso.Root
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
class ToastMatcher : TypeSafeMatcher<Root>() {

    override fun describeTo(description: Description) {
        description.appendText("is toast")
    }

    override fun matchesSafely(root: Root): Boolean {
        val type = root.getWindowLayoutParams().get().type
        if (type == WindowManager.LayoutParams.TYPE_TOAST) {
            val windowToken = root.getDecorView().getWindowToken()
            val appToken = root.getDecorView().getApplicationWindowToken()
            if (windowToken === appToken) {
                return true
            }
        }
        return false
    }
}

并像这样使用:

onView(withText(R.string.please_input_all_fields)).inRoot(ToastMatcher()).check(matches(isDisplayed()))

【讨论】:

    【解决方案8】:

    我会说对于 toast 消息首先定义你的规则

     @Rule
       public ActivityTestRule<AuthActivity> activityTestRule =
       new ActivityTestRule<>(AuthActivity.class);
    

    然后在引号之间输入您要查找的任何 toast 消息文本 例如我使用了“无效的电子邮件地址”

       onView(withText("Invalid email address"))
        .inRoot(withDecorView(not(activityTestRule.getActivity().getWindow().getDecorView())))
        .check(matches(isDisplayed()));
    

    【讨论】:

      【解决方案9】:

      我想建议一种替代方法,尤其是如果您需要检查特定的 toast 是否显示

      这里的问题

      onView(viewMatcher)
          .inRoot(RootMatchers.isPlatformPopup())
          .check(matches(not(isDisplayed())))
      

      onView(viewMatcher)
          .inRoot(RootMatchers.isPlatformPopup())
          .check(doesNotExist())
      

      或任何其他自定义inRoot 检查 甚至在代码传递给check 方法之前就抛出NoMatchingRootException

      您可能只是捕获异常并完成测试,但这不是一个好的选择,因为与默认测试用例相比,抛出和捕获NoMatchingRootException 会消耗大量时间。似乎 Espresso 正在等待 Root 一段时间

      对于这种情况,建议在这里放弃浓缩咖啡并使用UiAutomator 进行此断言。 EspressoUiAutomator 框架可以在一个环境中轻松协同工作。

      val device: UiDevice
         get() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
      
      fun assertPopupIsNotDisplayed() {
          device.waitForIdle()
          assertFalse(device.hasObject(By.text(yourText))))
      }
      
      fun assertPopupIsDisplayed() {
          device.waitForIdle()
          assertTrue(device.hasObject(By.text(yourText))))
      }
      

      【讨论】:

        【解决方案10】:

        对于 kotlin,我必须使用 apply 扩展功能,这对我有用。

        1- 在 androidTest 文件夹中声明您的 ToastMatcher 类:

        class ToastMatcher : TypeSafeMatcher<Root?>() {
        
        override fun matchesSafely(item: Root?): Boolean {
                val type: Int? = item?.windowLayoutParams?.get()?.type
                if (type == WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW) {
                    val windowToken: IBinder = item.decorView.windowToken
                    val appToken: IBinder = item.decorView.applicationWindowToken
                    if (windowToken === appToken) { // means this window isn't contained by any other windows.
                        return true
                    }
                }
                return false
            }
        
            override fun describeTo(description: Description?) {
                description?.appendText("is toast")
            }
        }
        

        2- 然后你像这样使用来测试 toast 消息是否实际显示

        onView(withText(R.string.invalid_phone_number))
                .inRoot(ToastMatcher().apply {
                    matches(isDisplayed())
                });
        

        归因于 ToastMatcher 类:

        /**
         * Author: http://www.qaautomated.com/2016/01/how-to-test-toast-message-using-espresso.html
         */
        

        【讨论】:

        • 是误报,可以匹配任意字符串
        【解决方案11】:

        使用 ActivityScenarioRule 和 Java

        代码的一些导入

        import android.view.View;
        import androidx.test.ext.junit.rules.ActivityScenarioRule;
        
        import org.junit.Before;
        import org.junit.Rule;
        import org.junit.Test;
        
        import static androidx.test.espresso.Espresso.onView;
        import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
        import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
        import static androidx.test.espresso.matcher.ViewMatchers.withId;
        import static androidx.test.espresso.matcher.ViewMatchers.withText;
        import static org.hamcrest.Matchers.not;
        
        

        1.声明规则

        //Change YourActivity by the activity you are testing
        @Rule
        public ActivityScenarioRule<YourActivity> activityRule
                = new ActivityScenarioRule<>(YourActivity.class);
        

        2。初始化装饰视图

            private View decorView;
        
            @Before
            public void loadDecorView() {
                activityRule.getScenario().onActivity(
                        activity -> decorView = activity.getWindow().getDecorView()
                );
            }
        

        3.最后测试一下

            @Test
            public void testWithToasts() {
        
        
                //Arrange and act code
        
                //Modify toast_msg to your own string resource
                onView(withText(R.string.toast_msg)).
                        inRoot(RootMatchers.withDecorView(not(decorView)))
                        .check(matches(isDisplayed()));
            }
        

        【讨论】:

          【解决方案12】:

          我对此很陌生,但我创建了一个基类“BaseTest”,其中包含我的所有操作(滑动、单击等)和验证(检查文本视图的内容等)。

          protected fun verifyToastMessageWithText(text: String, activityTestRule: ActivityTestRule<*>) {
                  onView(withText(text)).inRoot(withDecorView(not(activityTestRule.activity.window.decorView))).check(matches(isDisplayed()))
              }
          
          protected fun verifyToastMessageWithStringResource(id: Int, activityTestRule: ActivityTestRule<*>) {
                  onView(withText(id)).inRoot(withDecorView(not(activityTestRule.activity.window.decorView))).check(matches(isDisplayed()))
              }
          

          【讨论】:

            【解决方案13】:

            这对我有用

            onView(withId(R.id.inputField)).check(matches(withText("Lalala")));

            【讨论】:

              【解决方案14】:

              Toast 的实现方式可以检测到已显示的 Toast。但是,无法通过调用 show()) 或在 show() 之间的时间段和 toast 变得可见之间阻塞来查看是否已请求 Toast。这会引发无法解决的时间问题(您只能通过睡眠和希望来解决)。

              如果您真的想验证这一点,这里有一个使用 Mockito 和测试间谍的不太漂亮的替代方案:

              public interface Toaster {
               public void showToast(Toast t);
              
               private static class RealToaster {
                @Override
                public void showToast(Toast t) {
                  t.show();
                }
              
               public static Toaster makeToaster() {
                 return new RealToaster();
               }
              }
              
              Then in your test
              
              public void testMyThing() {
               Toaster spyToaster = Mockito.spy(Toaster.makeToaster());
               getActivity().setToaster(spyToaster);
               onView(withId(R.button)).perform(click());
               getInstrumentation().runOnMainSync(new Runnable() {
               @Override
                public void run() {
                 // must do this on the main thread because the matcher will be interrogating a view...
                 Mockito.verify(spyToaster).showToast(allOf(withDuration(Toast.LENGTH_SHORT), withView(withText("hello world"));
               });
              }
              
              // create a matcher that calls getDuration() on the toast object
              Matcher<Toast> withDuration(int)
              // create a matcher that calls getView() and applies the given view matcher
              Matcher<Toast> withView(Matcher<View> viewMatcher)
              
              
              
              
              another answer regarding this 
              
              
              
              
              if(someToast == null)
                  someToast = Toast.makeText(this, "sdfdsf", Toast.LENGTH_LONG);
              boolean isShown = someToast.getView().isShown();
              

              【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-07-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-12-09
              • 2014-07-04
              相关资源
              最近更新 更多