Espresso: Thread.sleep ();

102

Espresso behauptet, dass es keine Notwendigkeit gibt Thread.sleep();, aber mein Code funktioniert nur, wenn ich ihn einbinde. Ich verbinde mich mit einer IP. Während der Verbindung wird ein Fortschrittsdialog angezeigt. Ich muss sleepwarten, bis der Dialog beendet ist. Dies ist mein Testausschnitt, in dem ich ihn verwende:

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

Ich habe diesen Code versucht , mit und ohne das , Thread.sleep();aber es sagt R.id.Buttonnicht existiert. Ich kann es nur mit Schlaf zum Laufen bringen.

Außerdem habe ich versucht, Thread.sleep();durch Dinge wie getInstrumentation().waitForIdleSync();und immer noch kein Glück zu ersetzen .

Ist dies der einzige Weg, dies zu tun? Oder fehlt mir etwas?

Danke im Voraus.

Chad Bingham
quelle
Ist es möglich, dass Sie eine unerwünschte While-Schleife setzen, wenn Sie den Anruf blockieren möchten?
Kedark
ok .. lass es mich erklären. 2 Vorschläge für Sie 1.) Implementieren Sie so etwas wie einen Rückrufmechanismus. Beim Herstellen einer Verbindung rufen Sie eine Methode auf und zeigen Sie die Ansicht an. 2.) Sie möchten die Verzögerung zwischen IP.enterIP () erstellen; und onView (....), damit Sie die while-Schleife setzen können, die die ähnliche Verzögerung für den Aufruf von onview (..) erzeugt ... aber ich denke, wenn möglich, bevorzugen Sie bitte Option Nr. 1. (Erstellen des Rückrufs Mechanismus) ...
Kedark
@kedark Ja, das ist eine Option, aber ist das die Lösung von Espresso?
Chad Bingham
Ihre Frage enthält unbeantwortete Kommentare. Können Sie diese beantworten?
Bolhoso
@ Bolhoso, welche Frage?
Chad Bingham

Antworten:

110

In meinen Augen wird der richtige Ansatz sein:

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

Und dann wird das Verwendungsmuster sein:

// wait during 15 seconds for a view
onView(isRoot()).perform(waitId(R.id.dialogEditor, TimeUnit.SECONDS.toMillis(15)));
Oleksandr Kucherenko
quelle
3
Danke Alex, warum hast du diese Option gegenüber IdlingResource oder AsyncTasks gewählt?
Tim Boland
1
Dies ist ein Workaround-Ansatz. In den meisten Fällen erledigt Espresso die Arbeit ohne Probleme und mit einem speziellen Wartecode. Ich probiere verschiedene Möglichkeiten aus und denke, dass dies eine der am besten passenden Espresso-Architekturen / Designs ist.
Oleksandr Kucherenko
1
@AlexK das hat meinen Tageskameraden gemacht!
dawid gdanski
1
für mich scheitert es für api <= 19, bei der Zeile werfen neue PerformException.Builder ()
Prabin Timsina
4
Ich hoffe, Sie verstehen, dass es sich um ein Beispiel handelt, das Sie kopieren / einfügen und für eigene Bedürfnisse ändern können. Es liegt ganz in Ihrer Verantwortung, es ordnungsgemäß für die eigenen Geschäftsanforderungen zu verwenden, nicht für meine.
Oleksandr Kucherenko
47

Vielen Dank an AlexK für seine großartige Antwort. Es gibt Fälle, in denen Sie den Code etwas verzögern müssen. Es wartet nicht unbedingt auf die Antwort des Servers, sondern wartet möglicherweise darauf, dass die Animation ausgeführt wird. Ich persönlich habe ein Problem mit Espresso idolingResources (ich glaube, wir schreiben viele Codezeilen für eine einfache Sache), also habe ich die Art und Weise, wie AlexK es tat, in folgenden Code geändert:

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

Sie können also eine DelayKlasse erstellen und diese Methode einfügen, um leicht darauf zugreifen zu können. Sie können es in Ihrer Testklasse auf folgende Weise verwenden:onView(isRoot()).perform(waitFor(5000));

Hesam
quelle
7
Die perform-Methode kann sogar mit einer Zeile wie der folgenden vereinfacht werden: uiController.loopMainThreadForAtLeast (millis);
Yair Kukielka
Genial, das wusste ich nicht: thumbs_up @YairKukielka
Hesam
Huch auf das geschäftige Warten.
TWiStErRob
Genial. Ich habe ewig danach gesucht. +1 für eine einfache Lösung für Warteprobleme.
Tobias Reich
Viel bessere Möglichkeit, Verzögerung hinzuzufügen, anstatt zu verwendenThread.sleep()
Wahib Ul Haq
23

Ich bin auf diesen Thread gestoßen, als ich nach einer Antwort auf ein ähnliches Problem gesucht habe, bei dem ich auf eine Serverantwort gewartet und die Sichtbarkeit von Elementen basierend auf der Antwort geändert habe.

Während die obige Lösung definitiv half, fand ich schließlich dieses hervorragende Beispiel von Chiuki und verwende diesen Ansatz jetzt als meine , wenn ich darauf warte, dass Aktionen während der Leerlaufzeiten der App ausgeführt werden.

Ich habe ElapsedTimeIdlingResource () zu meiner eigenen Utilities-Klasse hinzugefügt , kann dies jetzt effektiv als Espresso-geeignete Alternative verwenden, und jetzt ist die Verwendung nett und sauber:

// 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
quelle
Ich bekomme eine I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceFehlermeldung. Irgendeine Idee. Ich benutze Proguard, aber mit deaktivierter Verschleierung.
Anthony
Fügen Sie eine -keepAnweisung für Klassen hinzu, die nicht gefunden werden, um sicherzustellen, dass ProGuard sie nicht als unnötig entfernt. Weitere Infos hier: developer.android.com/tools/help/proguard.html#keep-code
MattMatt
Ich poste eine Frage stackoverflow.com/questions/36859528/… . Die Klasse ist in der seed.txt und Mapping.txt
Anthony
2
Wenn Sie die Leerlaufrichtlinien ändern müssen, implementieren Sie Leerlaufressourcen wahrscheinlich nicht richtig. Auf lange Sicht ist es viel besser, Zeit in die Behebung dieses Problems zu investieren. Diese Methode führt schließlich zu langsamen und schuppigen Tests. Überprüfen Sie google.github.io/android-testing-support-library/docs/espresso/…
Jose Alcérreca
Du liegst richtig. Diese Antwort ist über ein Jahr alt, und seitdem hat sich das Verhalten von Ressourcen im Leerlauf so verbessert, dass derselbe Anwendungsfall, für den ich den obigen Code verwendet habe, sofort funktioniert und den verspotteten API-Client ordnungsgemäß erkennt. Wir verwenden den oben genannten nicht mehr ElapsedTimeIdlingResource in unseren instrumentierten Tests aus diesem Grund. (Sie könnten natürlich auch alle Dinge empfangen, was die Notwendigkeit des Hackens in einer Wartezeit zunichte macht). Die Art und Weise, wie Google Dinge tut, ist jedoch nicht immer die beste: philosophicalhacker.com/post/… .
MattMatt
18

Ich denke, es ist einfacher, diese Zeile hinzuzufügen:

SystemClock.sleep(1500);

Wartet eine bestimmte Anzahl von Millisekunden (von uptimeMillis), bevor Sie zurückkehren. Ähnlich wie im Schlaf (lang), löst jedoch keine InterruptedException aus. Interrupt () -Ereignisse werden bis zur nächsten unterbrechbaren Operation zurückgestellt. Wird erst zurückgegeben, wenn mindestens die angegebene Anzahl von Millisekunden verstrichen ist.

Cabezas
quelle
Expresso soll diesen fest codierten Schlaf vermeiden, der zu schuppigen Tests führt. Wenn dies der Fall ist, kann ich mich auch für Blackbox-Tools wie Appium entscheiden
Emjey
6

Sie können einfach Barista-Methoden verwenden:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista ist eine Bibliothek, die Espresso verpackt, um zu vermeiden, dass der gesamte Code hinzugefügt wird, der für die akzeptierte Antwort erforderlich ist. Und hier ist ein Link! https://github.com/SchibstedSpain/Barista

Roc Boronat
quelle
Ich verstehe den Unterschied nicht zwischen diesem und einem Thread-Schlaf
Pablo Caviglia
Ehrlich gesagt, ich erinnere mich nicht, in welchem ​​Video von Google ein Typ gesagt hat, wir sollten auf diese Weise schlafen, anstatt eine gemeinsame zu machen Thread.sleep(). Es tut uns leid! Es war in einigen der ersten Videos, die Google über Espresso gemacht hat, aber ich weiß nicht mehr, welches ... es war vor einigen Jahren. Es tut uns leid! :·) Oh! Bearbeiten! Ich habe den Link zu dem Video in die PR eingefügt, die ich vor drei Jahren eröffnet habe. Hör zu! github.com/AdevintaSpain/Barista/pull/19
Roc Boronat
5

Dies ähnelt dieser Antwort , verwendet jedoch anstelle von Versuchen eine Zeitüberschreitung und kann mit anderen ViewInteractions verkettet werden:

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

Verwendung:

onView(withId(R.id.whatever))
    .waitUntilVisible(5000)
    .perform(click())
Big McLargeHuge
quelle
4

Ich bin neu in Codierung und Espresso. Obwohl ich weiß, dass die gute und vernünftige Lösung darin besteht, Leerlauf zu verwenden, bin ich noch nicht intelligent genug, um dies zu tun.

Bis ich mich besser auskenne, müssen meine Tests trotzdem irgendwie ausgeführt werden. Daher verwende ich im Moment diese schmutzige Lösung, die eine Reihe von Versuchen unternimmt, ein Element zu finden, stoppt, wenn es es findet, und wenn nicht, schläft es kurz und startet erneut, bis die maximale Anzahl von Versuchen erreicht ist (die höchste Anzahl von Versuchen lag bisher bei etwa 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;
}

Ich verwende dies in allen Methoden, die Elemente nach ID, Text, übergeordnetem Element usw. finden:

static ViewInteraction findById(int itemId) {
    ViewInteraction element = onView(withId(itemId));
    waitForElementUntilDisplayed(element);
    return element;
}
anna3101
quelle
In Ihrem Beispiel gibt die findById(int itemId)Methode ein Element zurück (das NULL sein kann), unabhängig davon, ob es waitForElementUntilDisplayed(element);true oder false zurückgibt. Das ist also nicht in
Ordnung
Ich wollte mich nur einschalten und sagen, dass dies meiner Meinung nach die beste Lösung ist. IdlingResources sind mir aufgrund der Granularität der 5-Sekunden-Abfragerate nicht genug (viel zu groß für meinen Anwendungsfall). Die akzeptierte Antwort funktioniert auch bei mir nicht (Erklärung, warum sie bereits im langen Kommentar-Feed dieser Antwort enthalten ist). Danke dafür! Ich habe Ihre Idee aufgegriffen und meine eigene Lösung gefunden, die wie ein Zauber wirkt.
Oaskamay
Ja, dies ist die einzige Lösung, die auch für mich funktioniert hat, wenn Sie auf Elemente warten möchten, die nicht in der aktuellen Aktivität enthalten sind.
Guilhermekrz
3

Espresso wurde entwickelt, um sleep () -Aufrufe in den Tests zu vermeiden. Ihr Test sollte kein Dialogfeld zur Eingabe einer IP öffnen. Dies sollte die Verantwortung der getesteten Aktivität sein.

Auf der anderen Seite sollte Ihr UI-Test:

  • Warten Sie, bis der IP-Dialog angezeigt wird
  • Geben Sie die IP-Adresse ein und klicken Sie auf die Eingabetaste
  • Warten Sie, bis Ihre Schaltfläche angezeigt wird, und klicken Sie darauf

Der Test sollte ungefähr so ​​aussehen:

// 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 wartet, bis alles, was sowohl im UI-Thread als auch im AsyncTask-Pool passiert, abgeschlossen ist, bevor Sie Ihre Tests ausführen.

Denken Sie daran, dass Ihre Tests nichts tun sollten, was Ihrer Anwendungsverantwortung entspricht. Es sollte sich wie ein "gut informierter Benutzer" verhalten: Ein Benutzer, der klickt, überprüft, ob etwas auf dem Bildschirm angezeigt wird, aber tatsächlich die IDs der Komponenten kennt

Bolhoso
quelle
2
Ihr Beispielcode ist im Wesentlichen derselbe Code, den ich in meiner Frage geschrieben habe.
Chad Bingham
@Binghammer Ich meine, der Test sollte sich so verhalten, wie sich der Benutzer verhält. Vielleicht fehlt mir der Punkt, den Ihre IP.enterIP () -Methode bewirkt. Können Sie Ihre Frage bearbeiten und klarstellen?
Bolhoso
Meine Kommentare sagen, was es tut. Es ist nur eine Methode in Espresso, die den IP-Dialog ausfüllt. Es ist alles UI.
Chad Bingham
mm ... ok, du hast also recht, mein Test macht im Grunde das Gleiche. Tun Sie etwas aus dem UI-Thread oder AsyncTasks heraus?
Bolhoso
16
Espresso funktioniert nicht wie der Code und der Text dieser Antwort zu implizieren scheinen. Ein Überprüfungsaufruf für eine ViewInteraction wartet nicht, bis der angegebene Matcher erfolgreich ist, sondern schlägt sofort fehl, wenn die Bedingung nicht erfüllt ist. Der richtige Weg, dies zu tun, besteht darin, entweder AsyncTasks zu verwenden, wie in dieser Antwort erwähnt, oder, falls dies irgendwie nicht möglich ist, eine IdlingResource zu implementieren, die den UiController von Espresso benachrichtigt, wenn es in Ordnung ist, mit der Testausführung fortzufahren.
Haffax
2

Sie sollten Espresso Idling Resource verwenden, dies wird in diesem CodeLab empfohlen

Eine Leerlaufressource stellt eine asynchrone Operation dar, deren Ergebnisse sich auf nachfolgende Operationen in einem UI-Test auswirken. Durch die Registrierung von Ressourcen im Leerlauf bei Espresso können Sie diese asynchronen Vorgänge beim Testen Ihrer App zuverlässiger überprüfen.

Beispiel eines asynchronen Aufrufs vom Presenter

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

Abhängigkeiten

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

Für AndroidX

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

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

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

Gastón Saillén
quelle
0

Obwohl ich denke, dass es am besten ist, dafür Leerlaufressourcen zu verwenden ( https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/ ), könnten Sie dies wahrscheinlich als Fallback verwenden:

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

und nennen Sie es dann in Ihrem Code wie zB:

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

anstatt

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

Auf diese Weise können Sie auch Zeitüberschreitungen für Ansichtsaktionen und Ansichtszusicherungen hinzufügen.

Piotr Zawadzki
quelle
Verwenden Sie diese einzelne Codezeile für jeden Test-Espresso-Testfall: SystemClock.sleep (1000); // 1 Sekunde
Nikunjkumar Kapupara
Für mich funktioniert dies nur, indem ich diese Zeile return new TimedViewInteraction(Espresso.onView(viewMatcher));mitreturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger
0

Mein Dienstprogramm wiederholt die Ausführung von ausführbaren oder aufrufbaren Objekten, bis es nach einer Zeitüberschreitung fehlerfrei oder auslösbar ist. Es funktioniert perfekt für Espresso-Tests!

Angenommen, die letzte Ansichtsinteraktion (Tastenklick) aktiviert einige Hintergrundthreads (Netzwerk, Datenbank usw.). Als Ergebnis sollte ein neuer Bildschirm angezeigt werden, den wir in unserem nächsten Schritt überprüfen möchten. Wir wissen jedoch nicht, wann der neue Bildschirm zum Testen bereit ist.

Der empfohlene Ansatz besteht darin, Ihre App zu zwingen, Nachrichten über Thread-Status an Ihren Test zu senden. Manchmal können wir eingebaute Mechanismen wie OkHttp3IdlingResource verwenden. In anderen Fällen sollten Sie Codeteile an verschiedenen Stellen Ihrer App-Quellen einfügen (Sie sollten die App-Logik kennen!), Nur um die Unterstützung zu testen. Außerdem sollten wir alle Ihre Animationen deaktivieren (obwohl dies Teil der Benutzeroberfläche ist).

Der andere Ansatz wartet, z. B. SystemClock.sleep (10000). Aber wir wissen nicht, wie lange wir warten sollen, und selbst lange Verzögerungen können keinen Erfolg garantieren. Auf der anderen Seite wird Ihr Test lange dauern.

Mein Ansatz ist es, eine Zeitbedingung hinzuzufügen, um die Interaktion anzuzeigen. Zum Beispiel testen wir, dass während 10000 mc (Timeout) ein neuer Bildschirm angezeigt werden soll. Aber wir warten nicht und überprüfen es so schnell wir wollen (z. B. alle 100 ms). Natürlich blockieren wir den Test-Thread so, aber normalerweise ist es genau das, was wir in solchen Fällen brauchen.

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

Dies ist meine Klassenquelle:

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

Dies ist ein Helfer, den ich in Kotlin für Android-Tests verwende. In meinem Fall verwende ich longOperation, um die Serverantwort nachzuahmen, aber Sie können sie an Ihren Zweck anpassen.

@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
quelle
0

Ich werde meine Art, dies zu tun, der Mischung hinzufügen:

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

So genannt:

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

Sie können der Funktion suspendUntilSuccess Parameter wie maximale Iterationen, Iterationslänge usw. hinzufügen.

Ich bevorzuge immer noch die Verwendung von Ressourcen im Leerlauf, aber wenn die Tests beispielsweise aufgrund langsamer Animationen auf dem Gerät ausgeführt werden, verwende ich diese Funktion und sie funktioniert gut. Es kann natürlich bis zu 5 Sekunden lang hängen bleiben, bevor es fehlschlägt. Daher kann es die Ausführungszeit Ihrer Tests verlängern, wenn die erfolgreiche Aktion niemals erfolgreich ist.

Sean Blahovici
quelle