Хтось знав би, як протестувати на появу повідомлення Тост в еспресо для Android? У robotium це просто, і я використовував, але почав працювати в еспресо, але не отримував точної команди.
Хтось знав би, як протестувати на появу повідомлення Тост в еспресо для Android? У robotium це просто, і я використовував, але почав працювати в еспресо, але не отримував точної команди.
Відповіді:
Це трохи довге твердження працює для мене:
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()
метод зайвий
ActivityTestRule
. Ви можете отримати активність із цього правила за допомогою ActivityTestRule#getActivity()
.
onView(withText(R.string.toast_text)).inRoot(withDecorView(not(mActivityRule.getActivity().getWindow().getDecorView()))).check(matches(isDisplayed()));
Прийнята відповідь є доброю, але не спрацювала для мене. Тож я трохи пошукав і знайшов цю статтю в блозі . Це дало мені уявлення про те, як це зробити, і я оновив рішення вище.
Спочатку я реалізував 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();
}
Це працює як для мене чарівність.
Спочатку переконайтеся, що імпортуєте:
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()));
Це працювало у мене, і було досить просто у використанні.
Якщо ви використовуєте найновіші інструменти тестування 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()
.inRoot(withDecorView(not(activityRule.activity.window.decorView)))
ActivityScenarioRule
. У цьому прикладі ваш код не працюватиме.
Хоча на запитання є прийнята відповідь - яка, до речі, для мене не працює, - я хотів би додати своє рішення у Котліні, яке я отримав із відповіді Томаса Р.:
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)
}
}
}
Сподіваюся, це допоможе читачам пізніше - використання описано в коментарі.
Спочатку створіть томатний матч, який ми зможемо використовувати в наших тестових випадках -
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.
Я пишу свій власний збіг тостів:
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()))
Я б сказав, що для тостів спочатку визначте своє правило
@Rule
public ActivityTestRule<AuthActivity> activityTestRule =
new ActivityTestRule<>(AuthActivity.class);
тоді будь-який текст повідомлення тосту, який ви шукаєте, вводьте його між цитатами, наприклад, я використав "Недійсна електронна адреса"
onView(withText("Invalid email address"))
.inRoot(withDecorView(not(activityTestRule.getActivity().getWindow().getDecorView())))
.check(matches(isDisplayed()));
Я хотів би запропонувати альтернативний метод, особливо якщо вам потрібно перевірити, що певний тост НЕ відображається
Проблема ось у чому
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))))
}
Я досить новачок у цьому, але я створив базовий клас "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()))
}
Для 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
*/
Використання 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()));
}
Спосіб реалізації тостів дозволяє виявити тост, який відображався. Однак немає можливості дізнатись, чи було запрошено тост, через дзвінок 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();