Espresso: Thread.sleep ();


102

Espresso afferma che non ce n'è bisogno Thread.sleep();, ma il mio codice non funziona se non lo includo. Mi sto collegando a un IP. Durante la connessione, viene visualizzata una finestra di dialogo di avanzamento. Ho bisogno sleepdi aspettare che la finestra di dialogo venga chiusa. Questo è il mio frammento di prova in cui lo uso:

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

Ho provato questo codice con e senza il Thread.sleep();ma dice R.id.Buttonnon esiste. L'unico modo per farlo funzionare è dormire.

Inoltre, ho provato a sostituire Thread.sleep();con cose come getInstrumentation().waitForIdleSync();e ancora senza fortuna.

È questo l'unico modo per farlo? Oppure mi sfugge qualcosa?

Grazie in anticipo.


è possibile che tu metta il ciclo While indesiderato comunque vuoi bloccare la chiamata.
kedark

ok .. lasciami spiegare. 2 suggerimenti per te 1 °) Implementa qualcosa come un meccanismo di richiamo. alla connessione-stabilisci chiama un metodo e mostra la vista. 2 °) si desidera creare il ritardo tra IP.enterIP (); e onView (....) in modo da poter inserire il ciclo while che creerà il tipo di ritardo simile per chiamare onview (..) ... ma credo che, se possibile, preferisca l'opzione n. 1. (creando il call-back meccanismo) ...
kedark

@kedark Sì, è un'opzione, ma è la soluzione di Espresso?
Chad Bingham

Ci sono commenti senza risposta nella tua domanda, potresti rispondere?
Bolhoso

@ Bolhoso, quale domanda?
Chad Bingham

Risposte:


110

Secondo me l'approccio corretto sarà:

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

E quindi il modello di utilizzo sarà:

// wait during 15 seconds for a view
onView(isRoot()).perform(waitId(R.id.dialogEditor, TimeUnit.SECONDS.toMillis(15)));

3
Grazie Alex, perché hai scelto questa opzione su IdlingResource o AsyncTasks?
Tim Boland

1
Questo è un approccio alternativo, nella maggior parte dei casi Espresso fa il lavoro senza problemi e uno speciale "codice di attesa". In realtà provo diversi modi e penso che questa sia l'architettura / design Espresso più corrispondente.
Oleksandr Kucherenko

1
@AlexK questo ha reso il mio compagno di giornata!
dawid gdanski

1
per me, fallisce per api <= 19, alla linea lancia la nuova PerformException.Builder ()
Prabin Timsina

4
Spero che tu capisca che è un esempio, puoi copiare / incollare e modificare per le tue esigenze. È completamente tua responsabilità usarlo correttamente nelle proprie esigenze aziendali, non mia.
Oleksandr Kucherenko

47

Grazie ad AlexK per la sua fantastica risposta. Ci sono casi in cui è necessario ritardare il codice. Non è necessariamente in attesa della risposta del server, ma potrebbe essere in attesa del completamento dell'animazione. Personalmente ho problemi con Espresso idolingResources (penso che stiamo scrivendo molte righe di codice per una cosa semplice), quindi ho cambiato il modo in cui AlexK stava facendo il seguente codice:

/**
 * 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);
        }
    };
}

Quindi puoi creare una Delayclasse e inserire questo metodo per accedervi facilmente. Puoi usarlo nella tua classe di test allo stesso modo:onView(isRoot()).perform(waitFor(5000));


7
il metodo perform può anche essere semplificato con una riga come questa: uiController.loopMainThreadForAtLeast (millis);
Yair Kukielka

Fantastico, non lo sapevo: thumbs_up @YairKukielka
Hesam

Oddio per l'attesa impegnativa.
TWiStErRob

Eccezionale. Lo stavo cercando da anni. +1 per una semplice soluzione ai problemi di attesa.
Tobias Reich

Un modo molto migliore per aggiungere ritardo invece di usareThread.sleep()
Wahib Ul Haq

23

Mi sono imbattuto in questo thread mentre cercavo una risposta a un problema simile in cui stavo aspettando una risposta del server e modificando la visibilità degli elementi in base alla risposta.

Sebbene la soluzione di cui sopra abbia sicuramente aiutato, alla fine ho trovato questo eccellente esempio di chiuki e ora uso quell'approccio come mio punto di riferimento ogni volta che aspetto che si verifichino azioni durante i periodi di inattività dell'app.

Ho aggiunto ElapsedTimeIdlingResource () alla mia classe di utilità, ora posso usarlo efficacemente come un'alternativa adeguata a Espresso, e ora l'utilizzo è piacevole e pulito:

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

Ottengo un I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceerrore. Qualche idea. Uso Proguard ma con disabilita l'offuscamento.
Anthony

Prova ad aggiungere -keepun'istruzione per le classi che non vengono trovate per assicurarti che ProGuard non le rimuova perché non necessarie. Maggiori informazioni qui: developer.android.com/tools/help/proguard.html#keep-code
MattMatt

Pubblico una domanda stackoverflow.com/questions/36859528/… . La classe è in seed.txt e mapping.txt
Anthony

2
Se è necessario modificare le politiche di inattività, probabilmente non stai implementando correttamente le risorse di inattività. A lungo termine è molto meglio investire tempo per risolverlo. Questo metodo alla fine porterà a test lenti e traballanti. Dai
Jose Alcérreca

Hai proprio ragione. Questa risposta è vecchia di oltre un anno e da allora il comportamento delle risorse inattive è migliorato in modo tale che lo stesso caso d'uso per il quale ho usato il codice sopra ora funziona immediatamente, rilevando correttamente il client API deriso - non usiamo più quanto sopra ElapsedTimeIdlingResource nei nostri test strumentati per questo motivo. (Potresti anche ovviamente Rx tutte le cose, il che elimina la necessità di hackerare in un periodo di attesa). Detto questo, il modo di fare le cose di Google non è sempre il migliore: Philosophicalhacker.com/post/… .
MattMatt

18

Penso che sia più facile aggiungere questa riga:

SystemClock.sleep(1500);

Attende un determinato numero di millisecondi (di uptimeMillis) prima di tornare. Simile a sleep (long), ma non genera InterructedException; Gli eventi interrupt () vengono rimandati fino alla successiva operazione interrompibile. Non restituisce finché non è trascorso almeno il numero di millisecondi specificato.


L'espresso serve per evitare questo sonno codificato che causa test traballanti. se questo è il caso posso anche optare per strumenti blackbox come appium
Emjey

6

Puoi semplicemente usare i metodi Barista:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista è una libreria che avvolge Espresso per evitare di aggiungere tutto il codice necessario alla risposta accettata. Ed ecco un link! https://github.com/SchibstedSpain/Barista


Non capisco la differenza tra questo e il solo fare un sonno thread
Pablo Caviglia

Onestamente, non ricordo in quale video di Google un ragazzo abbia detto che dovremmo usare questo modo per dormire invece di fare un comune Thread.sleep(). Scusa! Era in alcuni dei primi video che Google ha fatto sull'Espresso ma non ricordo quale ... era qualche anno fa. Scusa! : ·) Oh! Modificare! Ho messo il link al video nella PR che ho aperto tre anni fa. Controlla! github.com/AdevintaSpain/Barista/pull/19
Roc Boronat

5

È simile a questa risposta ma utilizza un timeout invece di tentativi e può essere concatenato con altre ViewInteraction:

/**
 * Wait for view to be visible
 */
fun ViewInteraction.waitUntilVisible(timeout: Long): ViewInteraction {
    val startTime = System.currentTimeMillis()
    val endTime = startTime + timeout

    do {
        try {
            check(matches(isDisplayed()))
            return this
        } catch (e: NoMatchingViewException) {
            Thread.sleep(50)
        }
    } while (System.currentTimeMillis() < endTime)

    throw TimeoutException()
}

Uso:

onView(withId(R.id.whatever))
    .waitUntilVisible(5000)
    .perform(click())

4

Sono nuovo nella programmazione e nell'espresso, quindi mentre so che la soluzione buona e ragionevole è usare il minimo, non sono ancora abbastanza intelligente per farlo.

Fino a quando non diventerò più informato, ho ancora bisogno che i miei test vengano eseguiti in qualche modo, quindi per ora sto usando questa soluzione sporca che fa diversi tentativi per trovare un elemento, si ferma se lo trova e in caso contrario, dorme brevemente e si avvia ancora fino a raggiungere il numero massimo di tentativi (il numero massimo di tentativi finora è stato di circa 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;
}

Lo sto usando in tutti i metodi che trovano elementi per ID, testo, genitore ecc:

static ViewInteraction findById(int itemId) {
    ViewInteraction element = onView(withId(itemId));
    waitForElementUntilDisplayed(element);
    return element;
}

nel tuo esempio, il findById(int itemId)metodo restituirà un elemento (che potrebbe essere NULL) indipendentemente dal fatto che waitForElementUntilDisplayed(element);restituisca vero o falso .... quindi, non è ok
mbob

Volevo solo intervenire e dire che questa è la soluzione migliore secondo me. IdlingResourceNon sono sufficienti per me a causa della granularità della frequenza di polling di 5 secondi (troppo grande per il mio caso d'uso). La risposta accettata non funziona neanche per me (la spiegazione del perché è già inclusa nel lungo feed di commenti di quella risposta). Grazie per questo! Ho preso la tua idea e ho creato la mia soluzione e funziona a meraviglia.
oaskamay

Sì, questa è l'unica soluzione che ha funzionato anche per me, quando volevo aspettare elementi che non sono nell'attività corrente.
Guilhermekrz

3

Espresso è progettato per evitare chiamate sleep () nei test. Il tuo test non dovrebbe aprire una finestra di dialogo per inserire un IP, che dovrebbe essere la responsabilità dell'attività testata.

D'altra parte, il test dell'interfaccia utente dovrebbe:

  • Attendi che venga visualizzata la finestra di dialogo IP
  • Compila l'indirizzo IP e fai clic su Invio
  • Attendi che venga visualizzato il pulsante e fai clic su di esso

Il test dovrebbe essere simile a questo:

// 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 attende che tutto ciò che accade sia nel thread dell'interfaccia utente che nel pool AsyncTask finisca prima di eseguire i test.

Ricorda che i tuoi test non dovrebbero fare nulla che è responsabilità della tua applicazione. Dovrebbe comportarsi come un "utente ben informato": un utente che clicca, verifica che qualcosa venga mostrato sullo schermo, ma, di fatto, conosce gli ID dei componenti


2
Il tuo codice di esempio è essenzialmente lo stesso codice che ho scritto nella mia domanda.
Chad Bingham

@Binghammer quello che voglio dire è che il test dovrebbe comportarsi come si comporta l'utente. Forse il punto che mi manca è quello che fa il tuo metodo IP.enterIP (). Puoi modificare la tua domanda e chiarirlo?
Bolhoso

I miei commenti dicono cosa fa. È solo un metodo in espresso che riempie la finestra di dialogo IP. È tutta l'interfaccia utente.
Chad Bingham

mm ... ok, quindi hai ragione, il mio test fondamentalmente sta facendo lo stesso. Fai qualcosa fuori dal thread dell'interfaccia utente o da AsyncTasks?
Bolhoso

16
L'espresso non funziona come il codice e il testo di questa risposta sembra suggerire. Una chiamata di controllo su un ViewInteraction non aspetterà fino a quando il Matcher dato ha successo, ma fallirà immediatamente se la condizione non è soddisfatta. Il modo giusto per farlo è utilizzare AsyncTasks, come menzionato in questa risposta, o, se in qualche modo non è possibile, implementare un IdlingResource che notificherà l'UiController di Espresso quando è OK procedere con l'esecuzione del test.
haffax

2

Dovresti usare Espresso Idling Resource, è suggerito in questo CodeLab

Una risorsa inattiva rappresenta un'operazione asincrona i cui risultati influiscono sulle operazioni successive in un test dell'interfaccia utente. Registrando le risorse inattive con Espresso, puoi convalidare queste operazioni asincrone in modo più affidabile durante il test della tua app.

Esempio di una chiamata asincrona dal relatore

@Override
public void loadNotes(boolean forceUpdate) {
   mNotesView.setProgressIndicator(true);
   if (forceUpdate) {
       mNotesRepository.refreshData();
   }

   // The network request might be handled in a different thread so make sure Espresso knows
   // that the app is busy until the response is handled.
   EspressoIdlingResource.increment(); // App is busy until further notice

   mNotesRepository.getNotes(new NotesRepository.LoadNotesCallback() {
       @Override
       public void onNotesLoaded(List<Note> notes) {
           EspressoIdlingResource.decrement(); // Set app as idle.
           mNotesView.setProgressIndicator(false);
           mNotesView.showNotes(notes);
       }
   });
}

dipendenze

androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'

Per androidx

androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support.test.espresso:espresso-idling-resource:3.0.2'

Repo ufficiale: https://github.com/googlecodelabs/android-testing

Esempio di IdlingResource: https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample


0

Anche se penso che sia meglio usare Idling Resources per questo ( https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/ ) potresti probabilmente usarlo come riserva:

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

e poi chiamalo nel tuo codice come ad esempio:

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

invece di

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

Ciò consente anche di aggiungere timeout per le azioni di visualizzazione e le asserzioni di visualizzazione.


Utilizzare questo sotto una singola riga di codice per qualsiasi caso di test Test Espresso: SystemClock.sleep (1000); // 1 secondo
Nikunjkumar Kapupara

per me funziona solo cambiando questa riga return new TimedViewInteraction(Espresso.onView(viewMatcher));conreturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger

0

La mia utility ripete l'esecuzione eseguibile o richiamabile fino a quando non passa senza errori o lancia lanciabile dopo un timeout. Funziona perfettamente per i test Espresso!

Supponiamo che l'ultima interazione di visualizzazione (clic del pulsante) attivi alcuni thread in background (rete, database ecc.). Di conseguenza, dovrebbe apparire una nuova schermata e vogliamo verificarla nel passaggio successivo, ma non sappiamo quando la nuova schermata sarà pronta per essere testata.

L'approccio consigliato consiste nel forzare la tua app a inviare messaggi sugli stati dei thread al tuo test. A volte possiamo usare meccanismi incorporati come OkHttp3IdlingResource. In altri casi, dovresti inserire parti di codice in punti diversi delle origini dell'app (dovresti conoscere la logica dell'app!) Solo per il supporto del test. Inoltre, dovremmo disattivare tutte le animazioni (sebbene faccia parte dell'interfaccia utente).

L'altro approccio è in attesa, ad esempio SystemClock.sleep (10000). Ma non sappiamo quanto tempo aspettare e anche lunghi ritardi non possono garantire il successo. D'altra parte, il tuo test durerà a lungo.

Il mio approccio consiste nell'aggiungere la condizione temporale per visualizzare l'interazione. Ad esempio, testiamo che la nuova schermata dovrebbe apparire durante 10000 mc (timeout). Ma non aspettiamo e lo controlliamo velocemente come vogliamo (es. Ogni 100 ms) Ovviamente blocchiamo il thread di test in questo modo, ma di solito è proprio ciò di cui abbiamo bisogno in questi casi.

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

Questa è la mia fonte di classe:

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

Questo è un aiuto che sto usando in Kotlin per i test Android. Nel mio caso sto usando longOperation per imitare la risposta del server, ma puoi adattarla al tuo scopo.

@Test
fun ensureItemDetailIsCalledForRowClicked() {
    onView(withId(R.id.input_text))
        .perform(ViewActions.typeText(""), ViewActions.closeSoftKeyboard())
    onView(withId(R.id.search_icon)).perform(ViewActions.click())
    longOperation(
        longOperation = { Thread.sleep(1000) },
        callback = {onView(withId(R.id.result_list)).check(isVisible())})
}

private fun longOperation(
    longOperation: ()-> Unit,
    callback: ()-> Unit
){
    Thread{
        longOperation()
        callback()
    }.start()
}

0

Aggiungerò il mio modo di farlo al mix:

fun suspendUntilSuccess(actionToSucceed: () -> Unit, iteration : Int = 0) {
    try {
        actionToSucceed.invoke()
    } catch (e: Throwable) {
        Thread.sleep(200)
        val incrementedIteration : Int = iteration + 1
        if (incrementedIteration == 25) {
            fail("Failed after waiting for action to succeed for 5 seconds.")
        }
        suspendUntilSuccess(actionToSucceed, incrementedIteration)
    }
}

Chiamato così:

suspendUntilSuccess({
    checkThat.viewIsVisible(R.id.textView)
})

È possibile aggiungere parametri come iterazioni massime, lunghezza dell'iterazione, ecc. Alla funzione suspendUntilSuccess.

Preferisco ancora usare le risorse inattive, ma quando i test funzionano a causa di animazioni lente sul dispositivo, ad esempio, utilizzo questa funzione e funziona bene. Ovviamente può bloccarsi per un massimo di 5 secondi prima di fallire, quindi potrebbe aumentare il tempo di esecuzione dei tuoi test se l'azione per avere successo non ha successo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.