エスプレッソ:Thread.sleep();


102

Espressoはの必要はないと主張しThread.sleep();ていますが、コードを含めないと私のコードは機能しません。IPに接続しています。接続中、進捗ダイアログが表示されます。sleepダイアログが消えるのを待つ必要があります。これは私が使用する私のテストスニペットです:

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

私はこのコードを使用して、または使用せずに試しましたThread.sleep();が、R.id.Button存在しないと表示されています。私がそれを働かせる唯一の方法は睡眠です。

また、私はのThread.sleep();ようなもので置き換えてみましたがgetInstrumentation().waitForIdleSync();、まだ運がありません。

これがこれを行う唯一の方法ですか?それとも何か不足していますか?

前もって感謝します。


とにかく不要なWhileループを配置して、呼び出しをブロックすることは可能ですか?
kedark 2014年

わかりました。説明させてください。2つの提案1)コールバックのようなメカニズムを実装します。on-connection-establishは1つのメソッドを呼び出してビューを表示します。2番目)IP.enterIP();の間に遅延を作成したい。とonView(....)なので、whileループを追加して、onview(..)を呼び出すための一種の遅延を作成できます...しかし、可能であれば、オプションNo 1。メカニズム)...
kedark 2014年

@kedarkええ、それはオプションですが、それはエスプレッソのソリューションですか?
チャドビンガム2014年

質問に未回答のコメントがあります。回答していただけますか?
ボリョソ2014

@ボリョソ、どんな質問?
チャドビンガム

回答:


110

私の考えでは、正しいアプローチは次のとおりです。

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

そして、使用パターンは次のようになります:

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

3
アレックス、どうしてIdlingResourceまたはAsyncTasksよりもこのオプションを選んだのですか?
Tim Boland、2014年

1
これは回避策であり、ほとんどの場合、Espressoは問題なく特別な「待機コード」を使用してジョブを実行します。私は実際にいくつかの方法を試してみましたが、これは最も一致するEspressoアーキテクチャ/デザインだと思います。
Oleksandr Kucherenko 2014年

1
@AlexKこれは私の仲間を作りました!
dawid gdanski 2016

1
私にとって、それはapi <= 19で失敗し、行スローで新しいPerformException.Builder()
Prabin Timsina

4
そのサンプルであることを理解していただければ幸いです。コピーして貼り付け、独自のニーズに合わせて変更できます。自分のビジネスニーズではなく、自分のビジネスニーズで適切に使用することは、完全にあなたの責任です。
Oleksandr Kucherenko

47

AlexKのすばらしい回答に感謝します。コードを少し遅延させる必要がある場合があります。サーバーの応答を待機しているとは限りませんが、アニメーションが完了するのを待機している可能性があります。私はEspresso idolingResourcesに個人的に問題があるので(単純なことのために多くのコード行を記述していると思います)、AlexKの方法を次のコードに変更しました。

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

したがってDelay、簡単にアクセスするために、クラスを作成し、その中にこのメソッドを配置できます。同じ方法でTestクラスで使用できます。onView(isRoot()).perform(waitFor(5000));


7
performメソッドは、次のような1行で簡略化することもできます。uiController.loopMainThreadForAtLeast(millis);
Yair Kukielka、2016年

すごい、私はそれを知らなかった:thumbs_up @YairKukielka
Hesam

忙しい待機のようです。
TWiStErRob

驚くばかり。ずっと探していました。待機中の問題の簡単な解決策として+1。
トビアスライヒ

使用する代わりに遅延を追加するはるかに優れた方法Thread.sleep()
Wahib Ul Haq

23

サーバーの応答を待っていて、応答に基づいて要素の可視性を変更していたのと同じような問題に対する答えを探しているときに、このスレッドに出くわしました。

上記の解決策は確かに役に立ちましたが、私は最終的にchiukiからこの優れた例を見つけ、アプリのアイドル期間中にアクションが発生するのを待っているときはいつでも、そのアプローチを私の頼みとして使用します。

ElapsedTimeIdlingResource()を自分のユーティリティクラスに追加しました。これをEspresso固有の代替として効果的に使用できるようになりました。使用方法はすっきりとしています。

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

私が取得 I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceエラー。何か案が。私はProguardを使用していますが、難読化を無効にしています。
アンソニー

-keep見つからないクラスのステートメントを追加して、ProGuardが不要なクラスを削除していないことを確認してください。詳細はこちら:developer.android.com/tools/help/proguard.html#keep-code
MattMatt

私は質問を投稿しますstackoverflow.com/questions/36859528/…。クラスは、seed.txtとmapping.txtにあります
Anthony

2
アイドリングポリシーを変更する必要がある場合は、アイドリングリソースを正しく実装していない可能性があります。長い目で見れば、それを修正するために時間を費やす方がはるかに良いでしょう。この方法は、最終的には遅くて不安定なテストにつながります。google.github.io/android-testing-support-library/docs/espresso/…を
JoseAlcérreca16年

あなたはまったく正しい。この回答は1年以上前のものであり、その後、アイドリングリソースの動作が改善され、上記のコードを使用したのと同じユースケースがそのまま動作し、モックされたAPIクライアントを適切に検出します-上記は使用しなくなりましたそのため、インストルメント化されたテストのElapsedTimeIdlingResource。(もちろん、すべてをRxにすることもできます。これにより、待機期間中にハッキングする必要がなくなります)。とは言っても、Googleのやり方は必ずしも最高とは限りません。philosophicalhacker.com/ post / です。
MattMatt 2016

18

この行を追加する方が簡単だと思います。

SystemClock.sleep(1500);

返される前に、(uptimeMillisの)ミリ秒数待機します。sleep(long)に似ていますが、InterruptedExceptionをスローしません。interrupt()イベントは、次の割り込み可能な操作まで延期されます。少なくとも指定されたミリ秒数が経過するまで戻りません。


Expressoは、不安定なテストを引き起こすこれらのハードコードされたスリープを回避するためのものです。これが当てはまる場合は、appiumなどのブラックボックスツールを使用することもできます
Emjey

6

あなたはバリスタの方法を使うことができます:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

バリスタは、Espressoをラップして、承認された回答に必要なすべてのコードを追加しないようにするライブラリです。そしてここにリンクがあります! https://github.com/SchibstedSpain/Barista


私はこの違いを取得し、ちょうどスレッド睡眠やっていない
パブロCaviglia

正直なところ、Googleのどの動画で、一般的なを作るのではなく、この方法で睡眠を取るべきだと言ったのか覚えていませんThread.sleep()。ごめんなさい!GoogleがEspressoについて作成した最初の動画の一部に含まれていましたが、どれを覚えているかはわかりません...数年前のことです。ごめんなさい!:・) ああ!編集!3年前にオープンしたPRに動画へのリンクを入れました。見てみな!github.com/AdevintaSpain/Barista/pull/19
Roc Boronat

5

これはこの回答に似ていますが、試行の代わりにタイムアウトを使用し、他の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()
}

使用法:

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

4

私はコーディングとEspressoが初めてなので、アイドリングを使用するのが適切で妥当な解決策であることはわかっていますが、それを実行するのに十分なほどインテリジェントではありません。

知識が増えるまでは、なんとかしてテストを実行する必要がありますが、今のところは、このダーティソリューションを使用しています。このソリューションでは、要素の検索を何度も試み、見つかった場合は停止し、見つからない場合は、短時間スリープして起動します。再び試行の最大数に達するまで(これまでの試行の最大数は約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;
}

ID、テキスト、親などで要素を検索するすべてのメソッドでこれを使用しています。

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

あなたの例では、findById(int itemId)方法は、かどうか(NULLである可能性がある)の要素を返しますwaitForElementUntilDisplayed(element);ので、それはOKではありません....戻って真または偽
mbob

私が考えて、これが私の意見では最良の解決策であると言いたかっただけです。IdlingResource5秒のポーリングレートの細分性(私のユースケースには大きすぎる)のため、sでは十分ではありません。受け入れられた回答も私には機能しません(理由の説明がその回答の長いコメントフィードに既に含まれているため)。これをありがとう!私はあなたのアイデアを取り入れ、私自身の解決策を作りました、そしてそれは魅力のように機能します。
oaskamay 2017

はい、これは、現在のアクティビティにない要素を待機したいときに、私にとっても有効な唯一のソリューションです。
guilhermekrz 2017

3

Espressoは、テストでsleep()呼び出しを回避するように構築されています。テストでは、IPを入力するためのダイアログを開かないでください。これは、テストされるアクティビティの責任です。

一方、UIテストは次のようにする必要があります。

  • IPダイアログが表示されるまで待ちます
  • IPアドレスを入力し、Enterをクリックします
  • ボタンが表示されるのを待ってクリックします

テストは次のようになります。

// 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は、テストを実行する前に、UIスレッドとAsyncTaskプールの両方で発生しているすべてが完了するのを待ちます。

テストは、アプリケーションの責任である何もすべきではないことを覚えておいてください。「十分な情報を得たユーザー」のように動作する必要があります。ユーザーは、クリックして画面に何かが表示されていることを確認しますが、実際にはコンポーネントのIDを知っています。


2
あなたのサンプルコードは本質的に私が私の質問で書いたのと同じコードです。
チャドビンガム2014年

@Binghammerは、テストがユーザーの動作と同じように動作することを意味します。多分私が欠けている点はあなたのIP.enterIP()メソッドが何をするかです。質問を編集してそれを明確にできますか?
ボリョソ2014年

私のコメントはそれが何をするかを言います。これは、IPダイアログに入力するespressoのメソッドにすぎません。すべてUIです。
チャドビンガム

ええと、そうです、私のテストは基本的に同じことをしています。あなたはUIスレッドまたはAsyncTasksから何かをしますか?
ボリョソ2014年

16
Espressoは、この回答のコードとテキストが示すように機能しません。ViewInteractionのチェック呼び出しは、指定されたマッチャーが成功するまで待機せず、条件が満たされない場合はすぐに失敗します。これを行う正しい方法は、この回答で述べたようにAsyncTasksを使用するか、何らかの理由で可能でない場合は、テストの実行を続行してもよいときにEspressoのUiControllerに通知するIdlingResourceを実装することです。
ハファックス14

2

Espresso Idling Resourceを使用する必要があります。このCodeLabで推奨されています

アイドリングリソースは、結果がUIテストの後続の操作に影響する非同期操作を表します。アイドリングリソースをEspressoに登録することで、アプリのテスト時にこれらの非同期操作をより確実に検証できます。

プレゼンターからの非同期呼び出しの例

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

依存関係

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

androidxの場合

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

公式リポジトリ: https : //github.com/googlecodelabs/android-testing

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


0

これにはアイドリングリソースを使用するのが最適だと思いますが(https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/)、おそらくこれをフォールバックとして使用できます。

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

次に、コードで次のように呼び出します。

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

の代わりに

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

これにより、ビューアクションとビューアサーションにタイムアウトを追加することもできます。


以下の1行のコードを使用して、Test Espressoテストケースに対処します。SystemClock.sleep(1000); // 1秒
Nikunjkumar Kapupara 2017

私にとってこれはこの行return new TimedViewInteraction(Espresso.onView(viewMatcher));を変更することによってのみ機能しますreturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger '31

0

私のユーティリティは、エラーなしでパスするか、タイムアウト後にスロー可能になるまで、実行可能または呼び出し可能な実行を繰り返します。エスプレッソテストに最適です。

最後のビュー操作(ボタンクリック)がいくつかのバックグラウンドスレッド(ネットワーク、データベースなど)をアクティブ化するとします。その結果、新しい画面が表示され、次のステップで確認する必要がありますが、新しい画面をいつテストする準備ができるかはわかりません。

推奨されるアプローチは、アプリにスレッドの状態に関するメッセージをテストに送信させることです。OkHttp3IdlingResourceのような組み込みメカニズムを使用できる場合もあります。それ以外の場合は、テストのサポートのみを目的として、アプリのソースのさまざまな場所にコードを挿入する必要があります(アプリのロジックはわかっているはずです!)。さらに、すべてのアニメーションをオフにする必要があります(ただし、UIの一部です)。

もう1つのアプローチは待機中です(SystemClock.sleep(10000)など)。しかし、どれだけ待つかわからず、長い遅延でも成功を保証することはできません。一方、テストは長く続きます。

私のアプローチは、相互作用を表示するために時間条件を追加することです。たとえば、10000 mc(タイムアウト)の間に新しい画面が表示されることをテストします。しかし、私たちは待つことなく、必要なだけすばやくチェックします(たとえば100ミリ秒ごと)。もちろん、そのようにしてテストスレッドをブロックしますが、通常、このような場合に必要なのはそれだけです。

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

これは私のクラスのソースです:

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

これは、Kotlin for Android Testsで使用しているヘルパーです。私の場合、サーバーの応答を模倣するためにlongOperationを使用していますが、目的に応じて調整できます。

@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

これを行う方法をミックスに追加します。

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

このように呼ばれます:

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

最大の反復数、反復の長さなどのパラメーターをsuspendUntilSuccess関数に追加できます。

私はまだアイドリングリソースを使用することを好みますが、たとえばデバイスのアニメーションが遅いためにテストが実行されている場合は、この機能を使用し、うまく機能します。もちろん、失敗する前と同じように最大5秒間ハングする可能性があるため、成功するアクションが成功しない場合、テストの実行時間が長くなる可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.