Würde jemand wissen, wie man das Aussehen einer Toast-Nachricht in Android-Espresso testet? In Robotium ist es einfach und ich habe es benutzt, aber angefangen, mit Espresso zu arbeiten, aber nicht den genauen Befehl bekommen.
Würde jemand wissen, wie man das Aussehen einer Toast-Nachricht in Android-Espresso testet? In Robotium ist es einfach und ich habe es benutzt, aber angefangen, mit Espresso zu arbeiten, aber nicht den genauen Befehl bekommen.
Antworten:
Diese etwas lange Aussage funktioniert bei mir:
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()
Methode ist redundant
ActivityTestRule
. Sie können die Aktivität aus dieser Regel mit abrufen ActivityTestRule#getActivity()
.
onView(withText(R.string.toast_text)).inRoot(withDecorView(not(mActivityRule.getActivity().getWindow().getDecorView()))).check(matches(isDisplayed()));
Die akzeptierte Antwort ist gut, hat aber bei mir nicht funktioniert. Also habe ich ein bisschen gesucht und diesen Blog-Artikel gefunden . Dies gab mir eine Idee, wie es geht, und ich aktualisierte die obige Lösung.
Zuerst habe ich den ToastMatcher implementiert:
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;
}
}
Dann habe ich meine Prüfmethoden folgendermaßen implementiert:
public void isToastMessageDisplayed(int textId) {
onView(withText(textId)).inRoot(MobileViewMatchers.isToast()).check(matches(isDisplayed()));
}
MobileViewMatchers ist ein Container für den Zugriff auf die Matcher. Dort habe ich die statische Methode definiert isToast()
.
public static Matcher<Root> isToast() {
return new ToastMatcher();
}
Das funktioniert wie ein Zauber für mich.
Stellen Sie zunächst sicher, dass Sie Folgendes importieren:
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;
In Ihrer Klasse haben Sie wahrscheinlich eine Regel wie die folgende:
@Rule
public ActivityTestRule<MyNameActivity> activityTestRule =
new ActivityTestRule<>(MyNameActivity.class);
In Ihrem Test:
MyNameActivity activity = activityTestRule.getActivity();
onView(withText(R.string.toast_text)).
inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))).
check(matches(isDisplayed()));
Das hat bei mir funktioniert und war ziemlich einfach zu bedienen.
Wenn Sie die neuesten Android-Testtools von Jetpack verwenden , wissen Sie, dass ActivityTestRule veraltet ist und Sie ActivityScenario oder ActivityScenarioRule (das erste enthält) verwenden sollten.
Voraussetzungen. Erstellen Sie eine decorView-Variable und weisen Sie sie vor den Tests zu.
@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();
}
});
}
Testen Sie sich
@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 () kann entnommen werdenandroidx.test.core.app.ApplicationProvider.getApplicationContext;
withText()
.inRoot(withDecorView(not(activityRule.activity.window.decorView)))
ActivityScenarioRule
. In diesem Beispiel funktioniert Ihr Code nicht.
Obwohl die Frage eine akzeptierte Antwort hat - was übrigens für mich nicht funktioniert - möchte ich meine Lösung in Kotlin hinzufügen, die ich aus der Antwort von Thomas R. abgeleitet habe:
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)
}
}
}
Ich hoffe, dass dies für spätere Leser hilfreich sein wird - die Verwendung wird im Kommentar beschrieben.
Erstellen Sie zuerst einen Cutom Toast Matcher, den wir in unseren Testfällen verwenden können -
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. Testen Sie, ob die Toastnachricht angezeigt wird
onView(withText(R.string.mssage)).inRoot(new ToastMatcher())
.check(matches(isDisplayed()));
2. Testen Sie, ob die Toastnachricht nicht angezeigt wird
onView(withText(R.string.mssage)).inRoot(new ToastMatcher())
.check(matches(not(isDisplayed())));
3. Test-ID Der Toast enthält eine bestimmte Textnachricht
onView(withText(R.string.mssage)).inRoot(new ToastMatcher())
.check(matches(withText("Invalid Name"));
Danke, Anuja
Hinweis - Diese Antwort stammt aus diesem POST.
Ich schreibe meinen benutzerdefinierten Toast Matcher:
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
}
}
Und verwenden Sie wie folgt:
onView(withText(R.string.please_input_all_fields)).inRoot(ToastMatcher()).check(matches(isDisplayed()))
Ich würde sagen, für Toastnachrichten definieren Sie zuerst Ihre Regel
@Rule
public ActivityTestRule<AuthActivity> activityTestRule =
new ActivityTestRule<>(AuthActivity.class);
Was auch immer Sie für einen Toastnachrichtentext suchen, geben Sie ihn zwischen den Zitaten ein. Ich habe beispielsweise "Ungültige E-Mail-Adresse" verwendet.
onView(withText("Invalid email address"))
.inRoot(withDecorView(not(activityTestRule.getActivity().getWindow().getDecorView())))
.check(matches(isDisplayed()));
Ich bin ziemlich neu in diesem Bereich, aber ich habe eine Basisklasse 'BaseTest' erstellt, die alle meine Aktionen (Wischen, Klicken usw.) und Überprüfungen (Überprüfen der Textansichten auf Inhalt usw.) enthält.
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()))
}
Ich möchte eine alternative Methode vorschlagen, insbesondere wenn Sie überprüfen müssen, ob ein bestimmter Toast NICHT angezeigt wird
Das Problem hier ist das
onView(viewMatcher)
.inRoot(RootMatchers.isPlatformPopup())
.check(matches(not(isDisplayed())))
oder
onView(viewMatcher)
.inRoot(RootMatchers.isPlatformPopup())
.check(doesNotExist())
oder andere benutzerdefinierte inRoot
Überprüfungen werden ausgelöst, NoMatchingRootException
noch bevor der Code an die check
Methode übergeben wird
Sie können nur die Ausnahme abfangen und den Test abschließen, aber das ist keine gute Option, da das Werfen und Fangen NoMatchingRootException
im Vergleich zum Standardtestfall viel Zeit in Anspruch nimmt. Espresso scheint eine Weile auf die Wurzel zu warten
Für diesen Fall wird empfohlen, hier nur auf Espresso zu verzichten und UiAutomator
für diese Behauptung zu verwenden. Die Espresso
und UiAutomator
Frameworks können problemlos in einer Umgebung zusammenarbeiten.
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))))
}
Für Kotlin musste ich die Apply-Erweiterungsfunktion verwenden, und das funktionierte für mich.
1- Deklarieren Sie Ihre ToastMatcher-Klasse im androidTest-Ordner:
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- Dann testen Sie auf diese Weise, ob die Toastnachricht tatsächlich angezeigt wird
onView(withText(R.string.invalid_phone_number))
.inRoot(ToastMatcher().apply {
matches(isDisplayed())
});
Zuordnung zur ToastMatcher-Klasse:
/**
* Author: http://www.qaautomated.com/2016/01/how-to-test-toast-message-using-espresso.html
*/
Verwenden von ActivityScenarioRule und Java
Einige Importe für den Code
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. Deklarieren Sie die Regel
//Change YourActivity by the activity you are testing
@Rule
public ActivityScenarioRule<YourActivity> activityRule
= new ActivityScenarioRule<>(YourActivity.class);
2. Initialisieren Sie die Dekoransicht
private View decorView;
@Before
public void loadDecorView() {
activityRule.getScenario().onActivity(
activity -> decorView = activity.getWindow().getDecorView()
);
}
3. Testen Sie es schließlich
@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()));
}
Die Art und Weise, wie Toasts implementiert werden, ermöglicht es, zu erkennen, dass ein Toast angezeigt wurde. Es gibt jedoch keine Möglichkeit zu sehen, ob ein Toast angefordert wurde, durch einen Aufruf von show ()) oder zu blockieren zwischen dem Zeitraum zwischen show () und dem Zeitpunkt, zu dem der Toast sichtbar geworden ist. Dies führt zu unlösbaren Timing-Problemen (die Sie nur durch Schlaf und Hoffnung angehen können).
Wenn Sie dies wirklich wirklich überprüfen möchten, finden Sie hier eine nicht ganz so schöne Alternative mit Mockito und einem Testspion:
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();