在Android espresso中检查吐司消息


76

有谁知道如何测试android espresso中Toast消息的外观?在robotium中,它很简单,我用过但开始在浓缩咖啡中使用,但未获得确切的命令。


3
如果在显示祝酒词的同时完成活动,则以下任何解决方案均无效。
斯拉夫(Slav)

@Slav您是否找到任何解决方案,即使在Activity完成的情况下,也包括Toast检查?
NixSam

@NixSam不幸的是没有。如果我没记错的话,在完成活动的情况下,我决定检查活动是否已完成。
斯拉夫

@Slav感谢您的信息
NixSam '18

Answers:


119

这段简短的声明对我有用:

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()));

7
is()方法是多余的
Slava 2015年

5
@Slava是正确的,可以通过以下方法删除它:onView(withText(R.string.TOAST_STRING))。inRoot(withDecorView(not(getActivity()。getWindow()。getDecorView()))).check(matches(被展示()));
债券

5
无法解决方法getActivity()错误如何解决此问题
约翰·约翰·

8
@John:您可能正在使用带有的新的基于JUnit规则的测试ActivityTestRule。您可以使用从该规则获取活动ActivityTestRule#getActivity()
LeoNikkilä15年

26
使用ActivityTestRule:onView(withText(R.string.toast_text)).inRoot(withDecorView(not(mActivityRule.getActivity().getWindow().getDecorView()))).check(matches(isDisplayed()));
StefanTo

50

公认的答案是一个很好的答案,但对我没有用。所以我搜索了一下,找到了这篇博客文章。这给了我一个想法,我更新了上面的解决方案。

首先,我实现了Toa​​stMatcher:

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

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


1
这对我不起作用,因为测试不断循环。唯一有效的方法是,当烤面包片打开时,如果我触摸屏幕,它似乎会停止空转,然后开始工作。有任何想法吗?
AdamMc331'9

我需要知道您的测试设置。您要测试什么,显示什么?听起来像进度问题,stackoverflow.com/questions/33289152/ progressbars- and-espresso/…。所有API版本都会发生这种情况吗?
Thomas R.

我尚未测试多个API版本。发生的情况是,当片段加载时我们正在进行API调用,这是模拟失败响应,在这种情况下,我们要做的只是显示敬酒。我可以试着总结一下今天晚些时候在做什么。
AdamMc331 '16

2
“ MobileViewMatchers”来自哪里?它不能导入或在代码中找到
gorbysbm

2
仅当屏幕上显示烤面包时,我才能使用此代码验证烤面包消息。但如果存在以下情况,则结果如下:a)味精1 b)味精2 c)根本不敬酒。然后验证了选项a和b,但是代码卡在了选项c中。相同的解决方案是什么?
Inderdeep Singh'2

13

首先请确保导入:

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()));

这对我有用,而且非常易于使用。


9

如果您使用的是Jetpack的最新Android测试工具,那么您会知道不推荐使用ActivityTestRule,而应该使用ActivityScenario或ActivityScenarioRule(其中包含第一个)。

先决条件。创建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()
Herman

您也可以从规则中获取decorView,如其他答案所示.inRoot(withDecorView(not(activityRule.activity.window.decorView)))
Herman

1
@Herman在此示例中无法访问ActivityRule,因为我们正在使用ActivityScenarioRule。在此示例中,您的代码将不起作用。
LeonardoSibela

6

尽管问题的答案是可以接受的-BTW不适用于我-我想在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)
        }
    }

}

希望对以后的读者有所帮助-用法在注释中进行了描述。


6

首先创建一个标准的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 Toast包含特定的文本消息

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

谢谢,Anuja

注意-此答案来自此POST。


应该是:if(windowToken == appToken){//表示该窗口不被任何其他窗口包含。返回true;}
Akshay Mahajan

@anuja jain当stackoverflow.com/a/40756080/5230044答案有效时,为什么我们应该参考您的答案
cammando '17

1
如评论中原始帖子中所述,这不起作用。它失败并带有异常。此外,该帖子缺少返回true的信息;令牌匹配时在注释后添加,因此也将无法正常工作。
user330844'9

4

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

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()))

2

我想说吐司消息首先定义您的规则

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

然后,无论您要查找什么吐司消息文本,请在引号之间键入它,例如,我使用了“无效的电子邮件地址”

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

0

我对此很陌生,但是我创建了一个基类'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()))
    }

0

这对我有用

onView(withId(R.id.inputField))。check(matches(withText(“ Lalala”))));


0

我想建议的替代方法,特别是如果你需要检查特别是敬酒显示

这里的问题是

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

要么

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

或什至在代码传递给方法之前inRoot引发任何其他自定义检查NoMatchingRootExceptioncheck

您可能只捕获了异常并完成了测试,但这不是一个好的选择,因为NoMatchingRootException与默认测试用例相比,抛出和捕获会花费大量时间。似乎意式浓缩咖啡正在等待根源一段时间

对于这种情况,建议仅在这里放弃浓缩咖啡,并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))))
}

0

对于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-然后您可以像这样使用以测试吐司消息是否实际显示

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
 */

0

使用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()));
    }

-3

实施Toast的方式可以检测到已显示的Toast。但是,无法通过调用show()来查看是否已请求Toast,也无法在show()与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();

10
侯赛因博士,您是否不想在答案中添加指向复制位置的链接-groups.google.com/forum/#!searchin/android-test-kit-discuss/…
否认2015年

3
尽管确实复制了答案,但提出了一个很好的观点。接受的答案提供了非密封性测试,因为它会影响执行后的潜在效果。
mdelolmo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.