Espresso: Thread.sleep ();

102

Espresso prétend que ce n'est pas nécessaire Thread.sleep();, mais mon code ne fonctionne que si je l'inclus. Je me connecte à une adresse IP. Lors de la connexion, une boîte de dialogue de progression s'affiche. J'ai besoin sleepd'attendre que la boîte de dialogue se ferme. Voici mon extrait de test où je l'utilise:

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

J'ai essayé ce code avec et sans le Thread.sleep();mais il dit R.id.Buttonn'existe pas. Le seul moyen de le faire fonctionner est de dormir.

En outre, j'ai essayé de remplacer Thread.sleep();par des choses comme getInstrumentation().waitForIdleSync();et toujours pas de chance.

Est-ce la seule façon de faire cela? Ou est-ce que je manque quelque chose?

Merci d'avance.

Chad Bingham
la source
est-il possible pour vous de mettre de toute façon une boucle While indésirable? Vous voulez bloquer l'appel.
kedark
ok .. laissez-moi vous expliquer. 2 suggestions pour vous 1er) Implémentez quelque chose comme un mécanisme de rappel. lors de l'établissement de la connexion, appelez une méthode et affichez la vue. 2ème) vous souhaitez créer le délai entre IP.enterIP (); et onView (....) afin que vous puissiez mettre la boucle while qui créera le type de délai similaire pour appeler onview (..) ... mais je pense que si possible, veuillez préférer l'option n ° 1 (création du rappel mécanisme) ...
kedark
@kedark Oui, c'est une option, mais est-ce la solution d'Espresso?
Chad Bingham
Il y a des commentaires sans réponse dans votre question, pourriez-vous y répondre?
Bolhoso
@Bolhoso, quelle question?
Chad Bingham

Réponses:

111

Dans mon esprit, l'approche correcte sera:

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

Et puis le modèle d'utilisation sera:

// wait during 15 seconds for a view
onView(isRoot()).perform(waitId(R.id.dialogEditor, TimeUnit.SECONDS.toMillis(15)));
Oleksandr Kucherenko
la source
3
Merci Alex, pourquoi avez-vous choisi cette option sur IdlingResource ou AsyncTasks?
Tim Boland
1
C'est une approche de contournement, dans la plupart des cas, Espresso fait le travail sans aucun problème et un «code d'attente» spécial. J'essaie en fait plusieurs façons différentes, et je pense que c'est l'une des architectures / design Espresso les plus adaptées.
Oleksandr Kucherenko
1
@AlexK cela a fait mon compagnon de jour!
dawid gdanski
1
pour moi, cela échoue pour api <= 19, à la ligne lancer la nouvelle PerformException.Builder ()
Prabin Timsina
4
J'espère que vous comprenez que c'est un exemple, vous pouvez copier / coller et modifier pour vos propres besoins. Il est entièrement de votre responsabilité de l'utiliser correctement pour les besoins de votre entreprise, pas les miens.
Oleksandr Kucherenko
47

Merci à AlexK pour sa réponse géniale. Dans certains cas, vous devez retarder le code. Il n'attend pas nécessairement la réponse du serveur, mais attend peut-être que l'animation soit terminée. J'ai personnellement un problème avec Espresso idolingResources (je pense que nous écrivons de nombreuses lignes de code pour une chose simple) alors j'ai changé la façon dont AlexK faisait le code suivant:

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

Vous pouvez donc créer une Delayclasse et y mettre cette méthode afin d'y accéder facilement. Vous pouvez l'utiliser dans votre classe Test de la même manière:onView(isRoot()).perform(waitFor(5000));

Hesam
la source
7
la méthode perform peut même être simplifiée avec une ligne comme celle-ci: uiController.loopMainThreadForAtLeast (millis);
Yair Kukielka
Génial, je ne savais pas ça: thumbs_up @YairKukielka
Hesam
Yikes pour l'attente occupée.
TWiStErRob
Impressionnant. Je cherchais ça depuis des lustres. +1 pour une solution simple aux problèmes d'attente.
Tobias Reich
Une bien meilleure façon d'ajouter du retard au lieu d'utiliserThread.sleep()
Wahib Ul Haq
23

Je suis tombé sur ce fil lorsque je cherchais une réponse à un problème similaire où j'attendais une réponse du serveur et changeait la visibilité des éléments en fonction de la réponse.

Alors que la solution ci-dessus a vraiment aidé, j'ai finalement trouvé cet excellent exemple de chiuki et j'utilise maintenant cette approche comme mon choix chaque fois que j'attends que des actions se produisent pendant les périodes d'inactivité de l'application.

J'ai ajouté ElapsedTimeIdlingResource () à ma propre classe d'utilitaires, je peux maintenant l'utiliser efficacement comme une alternative appropriée à Espresso, et maintenant l'utilisation est agréable et propre:

// 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);
MattMatt
la source
J'obtiens une I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceerreur. Une idée. J'utilise Proguard mais avec désactiver l'obfuscation.
Anthony
Essayez d'ajouter une -keepinstruction pour les classes qui ne sont pas trouvées pour vous assurer que ProGuard ne les supprime pas comme inutiles. Plus d'informations ici: developer.android.com/tools/help/proguard.html#keep-code
MattMatt
Je poste une question stackoverflow.com/questions/36859528/… . La classe est dans le seed.txt et le mapping.txt
Anthony
2
Si vous avez besoin de modifier les politiques de ralenti, vous n'implémentez probablement pas correctement les ressources de ralenti. À long terme, il est préférable d'investir du temps pour résoudre ce problème. Cette méthode conduira finalement à des tests lents et irréguliers. Découvrez google.github.io/android-testing-support-library/docs/espresso
Jose Alcérreca
Tu as plutot raison. Cette réponse a plus d'un an, et depuis lors, le comportement des ressources inactives s'est amélioré de telle sorte que le même cas d'utilisation que j'ai utilisé le code ci-dessus pour l'instant fonctionne hors de la boîte, détectant correctement le client API fictif - nous n'utilisons plus ce qui précède ElapsedTimeIdlingResource dans nos tests instrumentés pour cette raison. (Vous pouvez également bien sûr Rx toutes les choses, ce qui élimine le besoin de pirater dans une période d'attente). Cela dit, la manière de faire de Google n'est pas toujours la meilleure: philosophicalhacker.com/post/… .
MattMatt
18

Je pense que c'est plus facile d'ajouter cette ligne:

SystemClock.sleep(1500);

Attend un nombre donné de millisecondes (de uptimeMillis) avant de revenir. Similaire à sleep (long), mais ne lance pas InterruptedException; Les événements interrupt () sont différés jusqu'à la prochaine opération interruptible. Ne retourne pas avant que le nombre de millisecondes spécifié ne se soit écoulé.

Cabezas
la source
Expresso consiste à éviter ce sommeil codé en dur qui provoque des tests irréguliers. si c'est le cas je peux aussi opter pour des outils blackbox comme appium
Emjey
6

Vous pouvez simplement utiliser les méthodes Barista:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista est une bibliothèque qui enveloppe Espresso pour éviter d'ajouter tout le code nécessaire à la réponse acceptée. Et voici un lien! https://github.com/SchibstedSpain/Barista

Roc Boronat
la source
Je ne comprends pas la différence entre cela et juste faire un sommeil de fil
Pablo Caviglia
Honnêtement, je ne me souviens pas dans quelle vidéo de Google un gars a dit que nous devrions utiliser cette façon de dormir au lieu de faire un commun Thread.sleep(). Désolé! C'était dans certaines des premières vidéos que Google a faites sur Espresso mais je ne me souviens pas laquelle ... c'était il y a quelques années. Désolé! : ·) Oh! Éditer! J'ai mis le lien vers la vidéo dans le PR que j'ai ouvert il y a trois ans. Vérifiez-le! github.com/AdevintaSpain/Barista/pull/19
Roc Boronat
5

Ceci est similaire à cette réponse mais utilise un délai d'expiration au lieu de tentatives et peut être enchaîné avec d'autres ViewInteractions:

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

Usage:

onView(withId(R.id.whatever))
    .waitUntilVisible(5000)
    .perform(click())
Big McLargeÉnorme
la source
4

Je suis nouveau dans le codage et l'espresso, alors même si je connais la bonne et raisonnable solution est d'utiliser la marche au ralenti, je ne suis pas encore assez intelligent pour le faire.

Jusqu'à ce que je devienne plus compétent, j'ai encore besoin que mes tests s'exécutent d'une manière ou d'une autre, donc pour l'instant j'utilise cette solution sale qui fait un certain nombre de tentatives pour trouver un élément, s'arrête s'il le trouve et sinon, dort brièvement et démarre jusqu'à ce qu'il atteigne le nombre maximum de tentatives (le nombre le plus élevé de tentatives jusqu'à présent a été d'environ 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;
}

J'utilise ceci dans toutes les méthodes qui recherchent des éléments par ID, texte, parent, etc.:

static ViewInteraction findById(int itemId) {
    ViewInteraction element = onView(withId(itemId));
    waitForElementUntilDisplayed(element);
    return element;
}
anna3101
la source
dans votre exemple, le findById(int itemId) méthode retournera un élément (qui pourrait être NULL) si le waitForElementUntilDisplayed(element);retourne vrai ou faux .... donc, ce n'est pas ok
mbob
Je voulais juste intervenir et dire que c'est la meilleure solution à mon avis. IdlingResourceCela ne me suffit pas en raison de la granularité du taux d'interrogation de 5 secondes (beaucoup trop gros pour mon cas d'utilisation). La réponse acceptée ne fonctionne pas non plus pour moi (l'explication de pourquoi est déjà incluse dans le long fil de commentaires de cette réponse). Merci pour cela! J'ai pris votre idée et ai fait ma propre solution et cela fonctionne comme un charme.
oaskamay
Oui, c'est la seule solution qui a fonctionné pour moi aussi, lorsque je voulais attendre des éléments qui ne sont pas dans l'activité en cours.
guilhermekrz
3

Espresso est conçu pour éviter les appels sleep () dans les tests. Votre test ne doit pas ouvrir une boîte de dialogue pour entrer une adresse IP, qui devrait être la responsabilité de l'activité testée.

D'autre part, votre test d'interface utilisateur doit:

  • Attendez que la boîte de dialogue IP apparaisse
  • Remplissez l'adresse IP et cliquez sur Entrée
  • Attendez que votre bouton apparaisse et cliquez dessus

Le test devrait ressembler à ceci:

// 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 attend que tout ce qui se passe à la fois dans le thread d'interface utilisateur et dans le pool AsyncTask se termine avant d'exécuter vos tests.

N'oubliez pas que vos tests ne doivent rien faire qui relève de la responsabilité de votre application. Il doit se comporter comme un "utilisateur bien informé": un utilisateur qui clique, vérifie que quelque chose est affiché à l'écran, mais, en fait, connaît les identifiants des composants

Bolhoso
la source
2
Votre exemple de code est essentiellement le même code que j'ai écrit dans ma question.
Chad Bingham le
@Binghammer ce que je veux dire, c'est que le test doit se comporter comme l'utilisateur se comporte. Peut-être que le point qui me manque est ce que fait votre méthode IP.enterIP (). Pouvez-vous modifier votre question et clarifier cela?
Bolhoso
Mes commentaires disent ce qu'il fait. C'est juste une méthode dans l'espresso qui remplit la boîte de dialogue IP. C'est tout l'interface utilisateur.
Chad Bingham le
mm ... ok, donc vous avez raison, mon test fait essentiellement la même chose. Faites-vous quelque chose en dehors du thread d'interface utilisateur ou d'AsyncTasks?
Bolhoso
16
L'espresso ne fonctionne pas comme le code et le texte de cette réponse semblent le laisser entendre. Un appel de vérification sur un ViewInteraction n'attendra pas que le Matcher donné réussisse, mais échouera plutôt immédiatement si la condition n'est pas remplie. La bonne façon de faire est d'utiliser AsyncTasks, comme mentionné dans cette réponse, ou, si cela n'est pas possible, d'implémenter une IdlingResource qui informera l'UiController d'Espresso quand il est OK pour procéder à l'exécution du test.
haffax
2

Vous devriez utiliser Espresso Idling Resource, c'est suggéré dans ce CodeLab

Une ressource inactive représente une opération asynchrone dont les résultats affectent les opérations suivantes dans un test d'interface utilisateur. En enregistrant les ressources inactives avec Espresso, vous pouvez valider ces opérations asynchrones de manière plus fiable lors du test de votre application.

Exemple d'un appel asynchrone du présentateur

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

Dépendances

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

Pour 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 officiel: https://github.com/googlecodelabs/android-testing

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

Gastón Saillén
la source
0

Bien que je pense qu'il soit préférable d'utiliser Idling Resources pour cela ( https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/ ), vous pouvez probablement l'utiliser comme solution de secours:

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

puis appelez-le dans votre code comme par exemple:

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

au lieu de

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

Cela vous permet également d'ajouter des délais d'attente pour les actions d'affichage et les assertions d'affichage.

Piotr Zawadzki
la source
Utilisez cette ligne de code ci-dessous pour tous les cas de test Test Espresso: SystemClock.sleep (1000); // 1 seconde
Nikunjkumar Kapupara
pour moi cela ne fonctionne qu'en changeant cette ligne return new TimedViewInteraction(Espresso.onView(viewMatcher));avecreturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger
0

Mon utilitaire répète l'exécution exécutable ou appelable jusqu'à ce qu'il réussisse sans erreur ou lance une exécution après un délai. Cela fonctionne parfaitement pour les tests Espresso!

Supposons que la dernière interaction de vue (clic sur le bouton) active certains threads d'arrière-plan (réseau, base de données, etc.). En conséquence, un nouvel écran devrait apparaître et nous voulons le vérifier à notre prochaine étape, mais nous ne savons pas quand le nouvel écran sera prêt à être testé.

L'approche recommandée consiste à forcer votre application à envoyer des messages sur les états des threads à votre test. Parfois, nous pouvons utiliser des mécanismes intégrés comme OkHttp3IdlingResource. Dans d'autres cas, vous devez insérer des morceaux de code à différents endroits des sources de votre application (vous devez connaître la logique de l'application!) Pour tester la prise en charge uniquement. De plus, nous devrions désactiver toutes vos animations (bien que ce soit la partie de l'interface utilisateur).

L'autre approche attend, par exemple SystemClock.sleep (10000). Mais nous ne savons pas combien de temps attendre et même de longs retards ne peuvent garantir le succès. En revanche, votre test durera longtemps.

Mon approche consiste à ajouter une condition de temps pour visualiser l'interaction. Par exemple, nous testons que le nouvel écran doit apparaître pendant 10000 mc (timeout). Mais nous n'attendons pas et le vérifions aussi vite que nous le souhaitons (par exemple toutes les 100 ms) Bien sûr, nous bloquons le fil de test de cette manière, mais généralement, c'est exactement ce dont nous avons besoin dans de tels cas.

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

Voici ma source de 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

Alexshr
la source
0

Ceci est une aide que j'utilise dans Kotlin pour les tests Android. Dans mon cas, j'utilise longOperation pour imiter la réponse du serveur, mais vous pouvez l'adapter à votre objectif.

@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()
}
Bade
la source
0

J'ajouterai ma façon de faire cela au 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)
    }
}

Appelé comme ceci:

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

Vous pouvez ajouter des paramètres tels que les itérations maximales, la longueur d'itération, etc. à la fonction suspendUntilSuccess.

Je préfère toujours utiliser des ressources inactives, mais lorsque les tests fonctionnent en raison d'animations lentes sur l'appareil par exemple, j'utilise cette fonction et cela fonctionne bien. Il peut bien sûr se bloquer jusqu'à 5 secondes comme avant d'échouer, il pourrait donc augmenter le temps d'exécution de vos tests si l'action pour réussir ne réussit jamais.

Sean Blahovici
la source