Перевірка тостового повідомлення в еспресо для Android


78

Хтось знав би, як протестувати на появу повідомлення Тост в еспресо для Android? У robotium це просто, і я використовував, але почав працювати в еспресо, але не отримував точної команди.


3
Будь-яке з наведених нижче рішень не буде працювати, якщо діяльність завершується одночасно з відображенням тосту.
Слав

@Slav Ви знайшли якесь рішення, яке включатиме перевірку тостів, навіть у тому випадку, коли Діяльність закінчена?
NixSam

@NixSam На жаль, ні. Якщо я добре пам’ятаю, у випадку закінчення Діяльності я зупинився на перевірці, що Діяльність закінчується.
Слав

@Slav дякую за інформацію
NixSam

Відповіді:


120

Це трохи довге твердження працює для мене:

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()метод зайвий
Слава

5
@Slava правильна, це можна зробити, видаливши: onView (withText (R.string.TOAST_STRING)). InRoot (withDecorView (not (getActivity (). GetWindow (). GetDecorView ()))) .check (match (match ( isDisplayed ()));
облігація

5
get не може вирішити метод getActivity () Помилка, як це вирішити
Джон

8
@John: Ви, мабуть, використовуєте нові тести, засновані на правилах JUnit, із ActivityTestRule. Ви можете отримати активність із цього правила за допомогою ActivityTestRule#getActivity().
Лео Ніккіля,

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

50

Прийнята відповідь є доброю, але не спрацювала для мене. Тож я трохи пошукав і знайшов цю статтю в блозі . Це дало мені уявлення про те, як це зробити, і я оновив рішення вище.

Спочатку я реалізував 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();
}

Це працює як для мене чарівність.


1
Це не працює для мене, оскільки тест постійно повторюється. Єдине, що працює, це те, що якщо я торкаюся екрана, коли тост відкритий, здається, він перестає працювати на холостому ходу, а потім спрацює. Будь-які ідеї?
AdamMc331

Мені потрібно знати ваші налаштування тесту. Що ви хочете перевірити і що відображається? Звучить як проблема прогресу, stackoverflow.com/questions/33289152/progressbars-and-espresso/… . Це трапляється у всіх версіях API?
Томас Р.

Я не тестував кілька версій API. Що трапляється, ми робимо виклик API, коли фрагмент завантажується, що висміює відповідь на помилку, і в цьому випадку все, що ми робимо, це показувати тост. Я можу спробувати скласти суть того, що роблю пізніше сьогодні.
AdamMc331

2
Звідки береться "MobileViewMatchers"? Його не можна імпортувати або знайти в коді
gorbysbm

2
Я можу перевірити повідомлення тостів за допомогою цього коду, лише якщо тост з'являється на екрані. Але якщо є умова з такими результатами: а) msg1 б) msg2 в) жодного тосту взагалі. Потім варіанти a та b перевіряються, але код застрягає у варіанті c. Що може бути можливим рішенням для того самого?
Inderdeep Singh

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

Якщо ви використовуєте найновіші інструменти тестування Android від Jetpack , ви знаєте, що 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;


Дуже дякую! це спрацювало для мене, як пропозицію, ви можете передати ідентифікатор рядкаwithText()
Hermandroid

Також ви можете отримати decorView з правила, як це пропонують інші відповіді.inRoot(withDecorView(not(activityRule.activity.window.decorView)))
Hermandroid

1
@Herman у цьому прикладі немає способу отримати доступ до ActivityRule, оскільки ми використовуємо ActivityScenarioRule. У цьому прикладі ваш код не працюватиме.
LeonardoSibela

7

Хоча на запитання є прийнята відповідь - яка, до речі, для мене не працює, - я хотів би додати своє рішення у Котліні, яке я отримав із відповіді Томаса Р.:

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

}

Сподіваюся, це допоможе читачам пізніше - використання описано в коментарі.


Це дійсно хороша відповідь і вирішила проблему для мене, оскільки вбудована політика повторних спроб
MatPag

6

Спочатку створіть томатний матч, який ми зможемо використовувати в наших тестових випадках -

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. Перевірте, чи відображається тост

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

2. Перевірте, чи не відображається тост

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

3. Ідентифікатор тесту, який Тост містить конкретне текстове повідомлення

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

Дякую, Ануджа

Примітка - ця відповідь з цього POST.


Це має бути: if (windowToken == appToken) {// означає, що це вікно не міститься в інших вікнах. повернути істинно; }
Акшай Махаджан

@anuja Jain коли stackoverflow.com/a/40756080/5230044 відповідь працює , то чому ми повинні звернутися до вашої відповіді
cammando

1
як зазначено в оригінальному дописі в коментарях, це НЕ працює. Це не вдається за винятком. Також у цій публікації відсутнє повернення true; після коментаря, коли маркери збігаються, тому він також не буде працювати.
user330844

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

1

Я хотів би запропонувати альтернативний метод, особливо якщо вам потрібно перевірити, що певний тост НЕ відображається

Проблема ось у чому

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

або

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

або будь-які інші користувацькі inRootперевірки кидаються NoMatchingRootExceptionще до того, як код перейде до checkметоду

Ви можете просто вловити виняток і пройти тест, але це невдалий варіант, оскільки метання і ловля NoMatchingRootExceptionзабирає багато часу в порівнянні з типовим тестовим випадком. Здається, еспресо деякий час чекає Коріння

Для цього випадку пропонуємо просто відмовитись від еспресо тут і використати UiAutomatorдля цього твердження. EspressoІ UiAutomatorструктури могли б легко працювати разом в одному середовищі.

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

Я досить новачок у цьому, але я створив базовий клас "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 (збіги (withText ("Lalala")));


0

Для kotlin мені довелося використовувати функцію apply extension, і це працювало для мене.

1- оголосіть свій клас ToastMatcher у папці androidTest:

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

це помилково позитивний прохід, ви можете зіставити будь-який рядок
adek111

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

Спосіб реалізації тостів дозволяє виявити тост, який відображався. Однак немає можливості дізнатись, чи було запрошено тост, через дзвінок show ()) або заблокувати між періодом часу між show () і коли тост став видимим. Це відкриває невирішені проблеми з термінами (що ви можете вирішити лише через сон і надію).

Якщо ви дійсно хочете це перевірити, ось не дуже мила альтернатива використання 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/… ?
заперечує

3
Хоча відповідь копіюється, це правда. Прийнята відповідь забезпечує негерметичний тест, оскільки він впливає на потенціал наступного виконання.
mdelolmo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.