Espresso: Nitka.sen();

Espresso twierdzi, że nie ma potrzeby Thread.sleep();, ale mój kod nie działa, chyba że go dołączę. Podłączam się do IP. Podczas łączenia wyświetlane jest okno dialogowe postępu. Potrzebuję sleep, aby poczekać na zamknięcie okna dialogowego. To jest mój testowy fragment, w którym go używam:

    IP.enterIP(); // fills out an IP dialog (this is done with espresso)

    //progress dialog is now shown
    Thread.sleep(1500);

    onView(withId(R.id.button).perform(click());

Próbowałem tego kodu z i bez Thread.sleep(); ale mówi, że R.id.Button nie istnieje. Jedynym sposobem, żeby to zadziałało, jest sen.

Próbowałem też zastąpić Thread.sleep(); rzeczami jak getInstrumentation().waitForIdleSync(); i nadal bez powodzenia.

Czy to jedyny sposób, aby to zrobić? Czy coś przeoczyłem? Z góry dzięki.
Author: Chad Bingham, 2014-01-29

9 answers

W mojej głowie poprawne podejście będzie:

/** Perform action of waiting for a specific view id. */
public static ViewAction waitId(final int viewId, final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "wait for a specific view with id <" + viewId + "> during " + millis + " millis.";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            uiController.loopMainThreadUntilIdle();
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + millis;
            final Matcher<View> viewMatcher = withId(viewId);

            do {
                for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                    // found view with required ID
                    if (viewMatcher.matches(child)) {
                        return;
                    }
                }

                uiController.loopMainThreadForAtLeast(50);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withActionDescription(this.getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new TimeoutException())
                    .build();
        }
    };
}

I wtedy wzór użycia będzie:

// wait during 15 seconds for a view
onView(isRoot()).perform(waitId(R.id.dialogEditor, TimeUnit.SECONDS.toMillis(15)));
 88
Author: Oleksandr Kucherenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-30 10:05:29

Dzięki Alexkowi za świetną odpowiedź. Są przypadki, w których musisz opóźnić kod. Niekoniecznie czeka na odpowiedź serwera, ale może czekać na wykonanie animacji. Ja osobiście mam problem z idolingiem Espresso (myślę, że piszemy wiele linijek kodu dla prostej rzeczy) więc zmieniłem sposób postępowania Alexka na następujący kod:

/**
 * Perform action of waiting for a specific time.
 */
public static ViewAction waitFor(final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "Wait for " + millis + " milliseconds.";
        }

        @Override
        public void perform(UiController uiController, final View view) {
            uiController.loopMainThreadForAtLeast(millis);
        }
    };
}

Możesz więc utworzyć klasę Delay i umieścić w niej tę metodę, aby uzyskać do niej łatwy dostęp. Możesz użyć to w klasie testowej w ten sam sposób: onView(isRoot()).perform(waitFor(5000));

 39
Author: Hesam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-02 00:23:27

Natknąłem się na ten wątek szukając odpowiedzi na podobny problem, gdzie czekałem na odpowiedź serwera i zmieniałem widoczność elementów na podstawie odpowiedzi.

Podczas gdy powyższe rozwiązanie zdecydowanie pomogło, w końcu znalazłem ten doskonały przykład z chiuki i teraz używam tego podejścia jako mojego wyjścia, gdy czekam na działania w okresach bezczynności aplikacji.

Dodałem ElapsedTimeIdlingResource () do mojej własnej klasy utilities, can teraz efektywnie używaj tego jako Espresso-właściwa alternatywa, a teraz użycie jest ładne i czyste: {]}

// Make sure Espresso does not time out
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);

// Now we wait
IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
Espresso.registerIdlingResources(idlingResource);

// Stop and verify
onView(withId(R.id.toggle_button))
    .check(matches(withText(R.string.stop)))
    .perform(click());
onView(withId(R.id.result))
    .check(matches(withText(success ? R.string.success: R.string.failure)));

// Clean up
Espresso.unregisterIdlingResources(idlingResource);
 21
Author: MattMatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-21 21:11:03

Myślę, że łatwiej jest dodać ten wiersz:

SystemClock.sleep(1500);

Czeka określoną liczbę milisekund (uptimeMillis) przed powrotem. Podobne do sleep (long), ale nie powoduje przerwania; zdarzenia interrupt () są odroczone do następnej operacji przerywanej. Nie zwraca dopóki nie upłynie co najmniej określona liczba milisekund.

 14
Author: Cabezas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-01 07:41:37

Możesz po prostu użyć metod baristy:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista jest biblioteką, która owija Espresso, aby uniknąć dodawania całego kodu potrzebnego do zaakceptowanej odpowiedzi. A tu link! https://github.com/SchibstedSpain/Barista

 5
Author: Roc Boronat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-30 15:54:08

Espresso jest zbudowane, aby uniknąć wywołania funkcji sleep () w testach. Twój test nie powinien otwierać okna dialogowego, aby wprowadzić adres IP, który powinien odpowiadać testowanej aktywności.

Z drugiej strony, twój test UI powinien:

  • poczekaj, aż pojawi się okno dialogowe IP
  • wpisz adres IP i kliknij enter
  • poczekaj, aż pojawi się twój przycisk i kliknij go

Test powinien wyglądać mniej więcej tak:

// type the IP and press OK
onView (withId (R.id.dialog_ip_edit_text))
  .check (matches(isDisplayed()))
  .perform (typeText("IP-TO-BE-TYPED"));

onView (withText (R.string.dialog_ok_button_title))
  .check (matches(isDisplayed()))
  .perform (click());

// now, wait for the button and click it
onView (withId (R.id.button))
  .check (matches(isDisplayed()))
  .perform (click());

Espresso czeka na wszystko, co się dzieje zarówno w wątku UI, jak i puli AsyncTask, aby zakończyć przed wykonaniem testów.

Pamiętaj, że twoje testy nie powinny działać na nic, co odpowiada twojej aplikacji. Powinien zachowywać się jak "dobrze poinformowany użytkownik": użytkownik, który klika, sprawdza, czy coś jest pokazane na ekranie, ale w rzeczywistości zna identyfikatory komponentów

 4
Author: Bolhoso,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-29 10:55:58

Jestem nowy w kodowaniu i Espresso, więc chociaż wiem, że dobrym i rozsądnym rozwiązaniem jest używanie biegu jałowego, nie jestem jeszcze wystarczająco inteligentny, aby to zrobić.

Dopóki nie będę bardziej kompetentny, nadal potrzebuję moich testów, aby jakoś uruchomić, więc na razie używam tego brudnego rozwiązania, które sprawia, że wiele prób znalezienia elementu, zatrzymuje się, jeśli go znajdzie, a jeśli nie, krótko śpi i zaczyna od nowa, aż osiągnie maksymalny numer prób (największa liczba prób do tej pory była około 1000). 150).

private static boolean waitForElementUntilDisplayed(ViewInteraction element) {
    int i = 0;
    while (i++ < ATTEMPTS) {
        try {
            element.check(matches(isDisplayed()));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                Thread.sleep(WAITING_TIME);
            } catch (Exception e1) {
                e.printStackTrace();
            }
        }
    }
    return false;
}

Używam tego we wszystkich metodach, które wyszukują elementy według ID, text, parent itp:

static ViewInteraction findById(int itemId) {
    ViewInteraction element = onView(withId(itemId));
    waitForElementUntilDisplayed(element);
    return element;
}
 2
Author: anna3101,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-02 10:55:51

Chociaż myślę, że najlepiej jest użyć zasobów jałowych do tego ( https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/) prawdopodobnie przydałoby się to jako alternatywa:

/**
 * Contains view interactions, view actions and view assertions which allow to set a timeout
 * for finding a view and performing an action/view assertion on it.
 * To be used instead of {@link Espresso}'s methods.
 * 
 * @author Piotr Zawadzki
 */
public class TimeoutEspresso {

    private static final int SLEEP_IN_A_LOOP_TIME = 50;

    private static final long DEFAULT_TIMEOUT_IN_MILLIS = 10 * 1000L;

    /**
     * Use instead of {@link Espresso#onView(Matcher)}
     * @param timeoutInMillis timeout after which an error is thrown
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(long timeoutInMillis, @NonNull final Matcher<View> viewMatcher) {

        final long startTime = System.currentTimeMillis();
        final long endTime = startTime + timeoutInMillis;

        do {
            try {
                return new TimedViewInteraction(Espresso.onView(viewMatcher));
            } catch (NoMatchingViewException ex) {
                //ignore
            }

            SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
        }
        while (System.currentTimeMillis() < endTime);

        // timeout happens
        throw new PerformException.Builder()
                .withCause(new TimeoutException("Timeout occurred when trying to find: " + viewMatcher.toString()))
                .build();
    }

    /**
     * Use instead of {@link Espresso#onView(Matcher)}.
     * Same as {@link #onViewWithTimeout(long, Matcher)} but with the default timeout {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(@NonNull final Matcher<View> viewMatcher) {
        return onViewWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewMatcher);
    }

    /**
     * A wrapper around {@link ViewInteraction} which allows to set timeouts for view actions and assertions.
     */
    public static class TimedViewInteraction {

        private ViewInteraction wrappedViewInteraction;

        public TimedViewInteraction(ViewInteraction wrappedViewInteraction) {
            this.wrappedViewInteraction = wrappedViewInteraction;
        }

        /**
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction perform(final ViewAction... viewActions) {
            wrappedViewInteraction.perform(viewActions);
            return this;
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(final ViewAction... viewActions) {
            return performWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewActions);
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(long timeoutInMillis, final ViewAction... viewActions) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return perform(viewActions);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to perform view actions: " + viewActions))
                    .build();
        }

        /**
         * @see ViewInteraction#withFailureHandler(FailureHandler)
         */
        public TimedViewInteraction withFailureHandler(FailureHandler failureHandler) {
            wrappedViewInteraction.withFailureHandler(failureHandler);
            return this;
        }

        /**
         * @see ViewInteraction#inRoot(Matcher)
         */
        public TimedViewInteraction inRoot(Matcher<Root> rootMatcher) {
            wrappedViewInteraction.inRoot(rootMatcher);
            return this;
        }

        /**
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction check(final ViewAssertion viewAssert) {
            wrappedViewInteraction.check(viewAssert);
            return this;
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(final ViewAssertion viewAssert) {
            return checkWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewAssert);
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(long timeoutInMillis, final ViewAssertion viewAssert) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return check(viewAssert);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to check: " + viewAssert.toString()))
                    .build();
        }
    }
}

A następnie nazwij go w kodzie jako np.:

onViewWithTimeout(withId(R.id.button).perform(click());

Zamiast

onView(withId(R.id.button).perform(click());

Pozwala to również na dodanie timeoutów dla akcji widoku i asercji widoku.

 0
Author: Piotr Zawadzki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-17 14:18:13

Moje narzędzie powtarza wykonywanie runnable lub callable, dopóki nie przejdzie bez błędów lub nie wyrzuci po upływie czasu. Doskonale sprawdza się w testach Espresso!

Załóżmy, że ostatnia interakcja widoku (kliknięcie przycisku) aktywuje niektóre wątki tła (sieć, baza danych itp.). W rezultacie powinien pojawić się nowy ekran i chcemy go sprawdzić w następnym kroku, ale nie wiemy, kiedy nowy ekran będzie gotowy do testów.

Zalecanym podejściem jest zmuszenie aplikacji do wysłania wiadomości o stanach wątków do testu. Czasami możemy użyć wbudowanych mechanizmów, takich jak OkHttp3IdlingResource. W innych przypadkach należy wstawić fragmenty kodu w różnych miejscach źródeł aplikacji (należy znać logikę aplikacji!) tylko do testowania wsparcia. Co więcej, powinniśmy wyłączyć wszystkie animacje (chociaż jest to część interfejsu użytkownika).

Inne podejście czeka, np. SystemClock.snu(10000). Ale nie wiemy, jak długo czekać i nawet duże opóźnienia nie gwarantują sukcesu. Na z drugiej strony, twój test będzie trwał długo.

Moje podejście polega na dodaniu warunku czasu do wyświetlania interakcji. Np. testujemy, że nowy ekran powinien pojawić się podczas 10000 mc (timeout). Ale nie czekamy i sprawdzamy go tak szybko, jak chcemy (np. co 100 ms) Oczywiście blokujemy wątek testowy w ten sposób, ale zazwyczaj tego właśnie potrzebujemy w takich przypadkach.

Usage:

long timeout=10000;
long matchDelay=100; //(check every 100 ms)
EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);

ViewInteraction loginButton = onView(withId(R.id.login_btn));
loginButton.perform(click());

myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));

To jest moja klasa źródło:

/**
 * Created by alexshr on 02.05.2017.
 */

package com.skb.goodsapp;

import android.os.SystemClock;
import android.util.Log;

import java.util.Date;
import java.util.concurrent.Callable;

/**
 * The utility repeats runnable or callable executing until it pass without errors or throws throwable after timeout.
 * It works perfectly for Espresso tests.
 * <p>
 * Suppose the last view interaction (button click) activates some background threads (network, database etc.).
 * As the result new screen should appear and we want to check it in our next step,
 * but we don't know when new screen will be ready to be tested.
 * <p>
 * Recommended approach is to force your app to send messages about threads states to your test.
 * Sometimes we can use built-in mechanisms like OkHttp3IdlingResource.
 * In other cases you should insert code pieces in different places of your app sources (you should known app logic!) for testing support only.
 * Moreover, we should turn off all your animations (although it's the part on ui).
 * <p>
 * The other approach is waiting, e.g. SystemClock.sleep(10000). But we don't known how long to wait and even long delays can't guarantee success.
 * On the other hand your test will last long.
 * <p>
 * My approach is to add time condition to view interaction. E.g. we test that new screen should appear during 10000 mc (timeout).
 * But we don't wait and check new screen as quickly as it appears.
 * Of course, we block test thread such way, but usually it's just what we need in such cases.
 * <p>
 * Usage:
 * <p>
 * long timeout=10000;
 * long matchDelay=100; //(check every 100 ms)
 * EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);
 * <p>
 * ViewInteraction loginButton = onView(withId(R.id.login_btn));
 * loginButton.perform(click());
 * <p>
 * myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));
 */
public class EspressoExecutor<T> {

    private static String LOG = EspressoExecutor.class.getSimpleName();

    public static long REPEAT_DELAY_DEFAULT = 100;
    public static long BEFORE_DELAY_DEFAULT = 0;

    private long mRepeatDelay;//delay between attempts
    private long mBeforeDelay;//to start attempts after this initial delay only

    private long mTimeout;//timeout for view interaction

    private T mResult;

    /**
     * @param timeout     timeout for view interaction
     * @param repeatDelay - delay between executing attempts
     * @param beforeDelay - to start executing attempts after this delay only
     */

    public EspressoExecutor(long timeout, long repeatDelay, long beforeDelay) {
        mRepeatDelay = repeatDelay;
        mBeforeDelay = beforeDelay;
        mTimeout = timeout;
        Log.d(LOG, "created timeout=" + timeout + " repeatDelay=" + repeatDelay + " beforeDelay=" + beforeDelay);
    }

    public EspressoExecutor(long timeout, long repeatDelay) {
        this(timeout, repeatDelay, BEFORE_DELAY_DEFAULT);
    }

    public EspressoExecutor(long timeout) {
        this(timeout, REPEAT_DELAY_DEFAULT);
    }


    /**
     * call with result
     *
     * @param callable
     * @return callable result
     * or throws RuntimeException (test failure)
     */
    public T call(Callable<T> callable) {
        call(callable, null);
        return mResult;
    }

    /**
     * call without result
     *
     * @param runnable
     * @return void
     * or throws RuntimeException (test failure)
     */
    public void call(Runnable runnable) {
        call(runnable, null);
    }

    private void call(Object obj, Long initialTime) {
        try {
            if (initialTime == null) {
                initialTime = new Date().getTime();
                Log.d(LOG, "sleep delay= " + mBeforeDelay);
                SystemClock.sleep(mBeforeDelay);
            }

            if (obj instanceof Callable) {
                Log.d(LOG, "call callable");
                mResult = ((Callable<T>) obj).call();
            } else {
                Log.d(LOG, "call runnable");
                ((Runnable) obj).run();
            }
        } catch (Throwable e) {
            long remain = new Date().getTime() - initialTime;
            Log.d(LOG, "remain time= " + remain);
            if (remain > mTimeout) {
                throw new RuntimeException(e);
            } else {
                Log.d(LOG, "sleep delay= " + mRepeatDelay);
                SystemClock.sleep(mRepeatDelay);
                call(obj, initialTime);
            }
        }
    }
}

Https://gist.github.com/alexshr/ca90212e49e74eb201fbc976255b47e0

 0
Author: alexshr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-06 23:31:49