Überprüfen der Toastnachricht in Android Espresso


76

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.


3
Eine der folgenden Lösungen funktioniert nicht, wenn die Aktivität gleichzeitig mit der Anzeige des Toasts beendet wird.
Slawischer

@Slav Haben Sie eine Lösung gefunden, die Toastprüfungen auch dann beinhaltet, wenn die Aktivität beendet ist?
NixSam

@NixSam Leider nicht. Wenn ich mich richtig erinnere, habe ich mich im Falle des Abschlusses der Aktivität entschlossen zu überprüfen, ob die Aktivität beendet ist.
Slawischer

@Slav danke für die Info
NixSam

Antworten:


119

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

7
is()Methode ist redundant
Slava

5
@Slava ist korrekt. Sie können Folgendes entfernen: onView (withText (R.string.TOAST_STRING)). InRoot (withDecorView (nicht (getActivity (). GetWindow (). GetDecorView ())) .check (Übereinstimmungen () wird angezeigt()));
Anleihe

5
Getting kann die Methode getActivity () nicht auflösen Fehler bei der Lösung
John

8
@ John: Sie verwenden wahrscheinlich die neuen regelbasierten JUnit-Tests mit einem ActivityTestRule. Sie können die Aktivität aus dieser Regel mit abrufen ActivityTestRule#getActivity().
Leo Nikkilä

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

50

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.


1
Dies funktioniert bei mir nicht, da der Test kontinuierlich wiederholt wird. Das einzige, was funktioniert, ist, wenn ich den Bildschirm berühre, während der Toast geöffnet ist, scheint er nicht mehr im Leerlauf zu laufen und funktioniert dann. Irgendwelche Ideen?
AdamMc331

Ich muss Ihren Testaufbau kennen. Was möchten Sie testen und was wird angezeigt? Klingt nach einem Fortschrittsproblem, stackoverflow.com/questions/33289152/progressbars-and-espresso/… . Kommt dies bei allen API-Versionen vor?
Thomas R.

Ich habe nicht mehrere API-Versionen getestet. Was passiert, ist, dass wir einen API-Aufruf ausführen, wenn ein Fragment geladen wird, das eine Fehlerantwort verspottet. In diesem Fall zeigen wir nur den Toast. Ich kann versuchen, einen Überblick über das zu bekommen, was ich heute später mache.
AdamMc331

2
Woher kommt "MobileViewMatchers"? Es kann nicht importiert oder im Code gefunden werden
gorbysbm

2
Ich kann Toastnachrichten mit diesem Code nur validieren, wenn Toast auf dem Bildschirm angezeigt wird. Wenn jedoch eine Bedingung mit folgenden Ergebnissen vorliegt: a) msg1 b) msg2 c) Überhaupt kein Toast. Dann werden die Optionen a und b validiert, aber der Code bleibt in Option c stecken. Was kann eine mögliche Lösung dafür sein?
Inderdeep Singh

13

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.


9

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;


Danke vielmals! Dies hat bei mir funktioniert. Als Vorschlag können Sie die Zeichenfolgen-ID anwithText()
Herman

Sie können auch decorView von der Regel erhalten, wie die anderen Antworten vermuten lassen.inRoot(withDecorView(not(activityRule.activity.window.decorView)))
Herman

1
@Herman In diesem Beispiel gibt es keine Möglichkeit, auf eine ActivityRule zuzugreifen, da wir eine verwenden ActivityScenarioRule. In diesem Beispiel funktioniert Ihr Code nicht.
LeonardoSibela

6

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.


6

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.


Es sollte sein: if (windowToken == appToken) {// bedeutet, dass dieses Fenster in keinem anderen Fenster enthalten ist. return true; }
Akshay Mahajan

@anuja jain wenn stackoverflow.com/a/40756080/5230044 Antwort funktioniert, warum sollten wir uns dann auf Ihre Antwort beziehen
cammando

1
Wie im ursprünglichen Beitrag in den Kommentaren erwähnt, funktioniert dies NICHT. Es schlägt mit einer Ausnahme fehl. Auch in diesem Beitrag fehlt die Rückgabe true; Nach dem Kommentar, wenn die Token übereinstimmen, funktioniert es auch nicht.
user330844

4

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

2

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

0

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

0

das funktioniert bei mir

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


0

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, NoMatchingRootExceptionnoch bevor der Code an die checkMethode ü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 NoMatchingRootExceptionim 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 UiAutomatorfür diese Behauptung zu verwenden. Die Espressound UiAutomatorFrameworks 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))))
}

0

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

0

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

-3

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

10
Md Hussain, möchten Sie Ihrer Antwort keinen Link zu der Stelle hinzufügen, an der Sie sie kopiert haben - groups.google.com/forum/#!searchin/android-test-kit-discuss/… ?
bestreitet

3
Obwohl es stimmt, dass die Antwort kopiert wird, wirft sie einen guten Punkt auf. Die akzeptierte Antwort liefert einen nicht hermetischen Test, da sie ein Potenzial nach der Ausführung beeinflusst.
Mdelolmo
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.