Espresso: Thread.sleep ();


102

Espresso tuyên bố rằng không cần Thread.sleep();, nhưng mã của tôi không hoạt động trừ khi tôi bao gồm nó. Tôi đang kết nối với một IP. Trong khi kết nối, một hộp thoại tiến trình được hiển thị. Tôi cần sleepchờ hộp thoại loại bỏ. Đây là đoạn mã thử nghiệm của tôi, nơi tôi sử dụng nó:

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

Tôi đã thử mã này vớikhông sự Thread.sleep();nhưng nó nói R.id.Buttonkhông tồn tại. Cách duy nhất tôi có thể làm cho nó hoạt động là ngủ.

Ngoài ra, tôi đã thử thay thế Thread.sleep();bằng những thứ như thế getInstrumentation().waitForIdleSync();và vẫn không may mắn.

Đây có phải là cách duy nhất để làm điều này? Hay tôi đang thiếu cái gì đó?

Cảm ơn trước.


Bạn có thể đặt vòng lặp While không mong muốn hay không, bạn muốn chặn cuộc gọi.
kedark

ok .. để tôi giải thích. 2 gợi ý cho bạn 1) Thực hiện một cái gì đó giống như loại cơ chế gọi lại. trên kết nối-thiết lập cuộc gọi một phương pháp và hiển thị dạng xem. Thứ 2) bạn muốn tạo độ trễ giữa IP.enterIP (); và onView (....) để bạn có thể đặt vòng lặp while sẽ tạo ra loại độ trễ mô phỏng để gọi onview (..) ... nhưng tôi cảm thấy nếu có thể, hãy thích tùy chọn Số 1 (tạo cuộc gọi lại cơ chế) ...
kedark

@kedark Vâng đó là một lựa chọn, nhưng đó có phải là giải pháp của Espresso không?
Chad Bingham

Có những nhận xét chưa được trả lời trong câu hỏi của bạn, bạn có thể trả lời chúng không?
Bolhoso

@Bolhoso, câu hỏi gì?
Chad Bingham

Câu trả lời:


110

Theo suy nghĩ của tôi, cách tiếp cận đúng sẽ là:

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

Và sau đó kiểu sử dụng sẽ là:

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

3
Cảm ơn Alex, tại sao bạn lại chọn tùy chọn này thay vì IdlingResource hoặc AsyncTasks?
Tim Boland

1
Đây là cách tiếp cận giải quyết vấn đề, trong hầu hết các trường hợp Espresso thực hiện công việc mà không gặp bất kỳ sự cố nào và 'mã chờ' đặc biệt. Tôi thực sự đã thử một số cách khác nhau và nghĩ rằng đây là một trong những cách thiết kế / kiến ​​trúc Espresso phù hợp nhất.
Oleksandr Kucherenko

1
@AlexK điều này đã làm bạn đời của tôi!
dawid gdanski 12/02/16

1
đối với tôi, nó không cho api <= 19, tại dòng ném PerformException.Builder mới ()
Prabin Timsina

4
Tôi hy vọng bạn hiểu rằng nó là một mẫu, bạn có thể sao chép / dán và sửa đổi theo nhu cầu của riêng mình. Bạn hoàn toàn có trách nhiệm sử dụng nó đúng cách cho nhu cầu kinh doanh của riêng mình, không phải của tôi.
Oleksandr Kucherenko,

47

Cảm ơn AlexK vì câu trả lời tuyệt vời của anh ấy. Có những trường hợp bạn cần thực hiện một số trì hoãn trong mã. Nó không nhất thiết phải đợi phản hồi của máy chủ mà có thể đợi hoạt ảnh hoàn thành. Cá nhân tôi gặp vấn đề với Espresso idolingResources (tôi nghĩ rằng chúng tôi đang viết nhiều dòng mã cho một điều đơn giản) vì vậy tôi đã thay đổi cách AlexK đang làm thành mã sau:

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

Vì vậy, bạn có thể tạo một Delaylớp và đặt phương thức này vào đó để truy cập nó một cách dễ dàng. Bạn có thể sử dụng nó trong lớp Kiểm tra của mình theo cách tương tự:onView(isRoot()).perform(waitFor(5000));


7
phương thức thực hiện thậm chí có thể được đơn giản hóa bằng một dòng như thế này: uiController.loopMainThreadForAtLeast (millis);
Yair Kukielka

Thật tuyệt, tôi không biết điều đó: thumbs_up @YairKukielka
Hesam

Rất tiếc vì sự chờ đợi bận rộn.
TWiStErRob

Tuyệt vời. Tôi đã tìm kiếm điều đó cho nhiều tuổi. +1 cho một giải pháp đơn giản cho các vấn đề chờ đợi.
Tobias Reich,

Nhiều nhiều cách tốt hơn để thêm chậm trễ thay vì sử dụngThread.sleep()
Wahib Ul Haq

23

Tôi tình cờ gặp chủ đề này khi tìm kiếm câu trả lời cho một vấn đề tương tự trong đó tôi đang chờ phản hồi của máy chủ và thay đổi khả năng hiển thị của các phần tử dựa trên phản hồi.

Mặc dù giải pháp ở trên chắc chắn hữu ích, nhưng cuối cùng tôi đã tìm thấy ví dụ tuyệt vời này từ chiuki và bây giờ sử dụng cách tiếp cận đó làm mục đích của tôi bất cứ khi nào tôi chờ đợi các hành động xảy ra trong thời gian chờ ứng dụng.

Tôi đã thêm ElapsedTimeIdlingResource () vào lớp tiện ích của riêng mình, bây giờ có thể sử dụng hiệu quả lớp đó như một giải pháp thay thế phù hợp với Espresso và giờ đây việc sử dụng đã tốt đẹp và sạch sẽ:

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

Tôi nhận được một I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourcelỗi. Bất kỳ ý tưởng. Tôi sử dụng Proguard nhưng với chức năng vô hiệu hóa làm mờ.
Anthony

Hãy thử thêm một -keepcâu lệnh cho các lớp không được tìm thấy để đảm bảo rằng ProGuard không xóa chúng khi không cần thiết. Thông tin thêm tại đây: developer.android.com/tools/help/proguard.html#keep-code
MattMatt,

Tôi đăng một câu hỏi stackoverflow.com/questions/36859528/… . Lớp học nằm trong seed.txt và mapping.txt
Anthony

2
Nếu bạn cần thay đổi chính sách chạy không tải, có thể bạn đang không triển khai chính xác tài nguyên chạy không tải. Về lâu dài, tốt hơn là nên đầu tư thời gian vào việc khắc phục điều đó. Phương pháp này cuối cùng sẽ dẫn đến các bài kiểm tra chậm và bong tróc. Xem google.github.io/android-testing-support-library/docs/espresso/…
Jose Alcérreca

Bạn khá đúng. Câu trả lời này đã được hơn một năm và kể từ đó, hành vi của tài nguyên chạy không tải đã được cải thiện để trường hợp sử dụng tương tự mà tôi đã sử dụng mã ở trên hiện hoạt động tốt, phát hiện đúng ứng dụng khách API bị làm giả - chúng tôi không còn sử dụng trường hợp trên ElapsedTimeIdlingResource trong các bài kiểm tra công cụ của chúng tôi vì lý do đó. (Tất nhiên bạn cũng có thể Rx tất cả mọi thứ, điều này không cần thiết phải hack trong thời gian chờ đợi). Điều đó cho thấy, cách hoạt động của Google không phải lúc nào cũng tốt nhất: Philosophacker.com/post/… .
MattMatt

18

Tôi nghĩ việc thêm dòng này sẽ dễ dàng hơn:

SystemClock.sleep(1500);

Chờ một số mili giây nhất định (của uptimeMillis) trước khi quay lại. Tương tự như sleep (lâu), nhưng không ném InterruptException; các sự kiện ngắt () được hoãn lại cho đến khi hoạt động có thể ngắt tiếp theo. Không quay lại cho đến khi trôi qua ít nhất số mili giây được chỉ định.


Expresso là để tránh những giấc ngủ được mã hóa cứng này gây ra các bài kiểm tra không ổn định. nếu đây là trường hợp, tôi cũng có thể sử dụng các công cụ hộp đen như appium
Emjey

6

Bạn chỉ có thể sử dụng các phương pháp Barista:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista là một thư viện bao bọc Espresso để tránh thêm tất cả các mã cần thiết cho câu trả lời được chấp nhận. Và đây là một liên kết! https://github.com/SchibstedSpain/Barista


Tôi không hiểu sự khác biệt giữa điều này và chỉ thực hiện một luồng ngủ
Pablo Caviglia

Thành thật mà nói, tôi không nhớ trong video nào từ Google, một anh chàng đã nói rằng chúng ta nên sử dụng cách này để ngủ nướng thay vì làm việc chung Thread.sleep(). Lấy làm tiếc! Đó là trong một số video đầu tiên Google thực hiện về Espresso nhưng tôi không nhớ là video nào ... cách đây vài năm. Lấy làm tiếc! :·) Oh! Biên tập! Tôi đặt liên kết đến video trong phần PR mà tôi đã mở cách đây ba năm. Kiểm tra nó ra! github.com/AdevintaSpain/Barista/pull/19
Roc Boronat

5

Điều này tương tự với câu trả lời này nhưng sử dụng thời gian chờ thay vì thử và có thể được xâu chuỗi với các ViewInteraction khác:

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

Sử dụng:

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

4

Tôi mới làm quen với mã hóa và Espresso, vì vậy mặc dù tôi biết giải pháp tốt và hợp lý là sử dụng chế độ chạy không tải, nhưng tôi vẫn chưa đủ thông minh để làm điều đó.

Cho đến khi tôi trở nên hiểu biết hơn, tôi vẫn cần các bài kiểm tra của mình để chạy bằng cách nào đó, vì vậy hiện tại tôi đang sử dụng giải pháp bẩn này để thực hiện một số nỗ lực tìm kiếm một phần tử, dừng lại nếu nó tìm thấy và nếu không, sẽ ngủ và bắt đầu một thời gian ngắn. một lần nữa cho đến khi nó đạt đến nr lần thử tối đa (số lần thử cao nhất cho đến nay là khoảng 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;
}

Tôi đang sử dụng điều này trong tất cả các phương pháp tìm kiếm các phần tử theo ID, văn bản, cha mẹ, v.v.:

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

trong ví dụ của bạn, findById(int itemId)phương pháp này sẽ trả về một phần tử (mà có thể là NULL) liệu waitForElementUntilDisplayed(element);trả về true hoặc false .... như vậy, đó không phải là ok
mbob

Tôi chỉ muốn gọi và nói rằng đây là giải pháp tốt nhất theo ý kiến ​​của tôi. IdlingResources là không đủ đối với tôi do độ chi tiết của tỷ lệ bỏ phiếu trong 5 giây (quá lớn đối với trường hợp sử dụng của tôi). Câu trả lời được chấp nhận cũng không phù hợp với tôi (giải thích lý do tại sao đã được đưa vào nguồn cấp dữ liệu bình luận dài của câu trả lời đó). Cảm ơn vì điều đó! Tôi đã lấy ý tưởng của bạn và đưa ra giải pháp của riêng tôi và nó hoạt động như một sự quyến rũ.
oaskamay

Vâng, đây là giải pháp duy nhất cũng hiệu quả với tôi, khi muốn đợi các phần tử không có trong hoạt động hiện tại.
guildhermekrz

3

Espresso được tạo ra để tránh các cuộc gọi ngủ () trong các thử nghiệm. Thử nghiệm của bạn không được mở hộp thoại để nhập IP, đó phải là khả năng phản hồi của hoạt động được thử nghiệm.

Mặt khác, kiểm tra giao diện người dùng của bạn nên:

  • Chờ hộp thoại IP xuất hiện
  • Điền vào địa chỉ IP và nhấp vào enter
  • Chờ cho nút của bạn xuất hiện và nhấp vào nó

Bài kiểm tra sẽ trông giống như sau:

// 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 đợi mọi thứ đang diễn ra cả trong chuỗi giao diện người dùng và nhóm AsyncTask kết thúc trước khi thực hiện các thử nghiệm của bạn.

Hãy nhớ rằng các bài kiểm tra của bạn không nên làm bất cứ điều gì ảnh hưởng đến khả năng ứng dụng của bạn. Nó sẽ hoạt động giống như một "người dùng được thông báo tốt": người dùng nhấp vào, xác minh rằng một cái gì đó được hiển thị trên màn hình, nhưng thực tế là biết ID của các thành phần


2
Mã ví dụ của bạn về cơ bản giống như mã mà tôi đã viết trong câu hỏi của mình.
Chad Bingham

@Binghammer ý tôi là kiểm tra phải hoạt động giống như hành vi của người dùng. Có thể điểm tôi còn thiếu là phương thức IP.enterIP () của bạn làm được gì. Bạn có thể chỉnh sửa câu hỏi của mình và làm rõ điều đó không?
Bolhoso

Ý kiến ​​của tôi nói lên những gì nó làm. Nó chỉ là một phương thức trong espresso điền vào hộp thoại IP. Đó là tất cả giao diện người dùng.
Chad Bingham

mm ... ok, vậy bạn nói đúng, bài kiểm tra của tôi về cơ bản cũng làm như vậy. Bạn có làm điều gì đó ngoài chuỗi giao diện người dùng hoặc AsyncTasks không?
Bolhoso

16
Espresso không hoạt động như mã và văn bản của câu trả lời này dường như ngụ ý. Lệnh gọi kiểm tra trên ViewInteraction sẽ không đợi cho đến khi Matcher đã cho thành công, mà sẽ thất bại ngay lập tức nếu điều kiện không được đáp ứng. Cách thích hợp để làm điều này là sử dụng AsyncTasks, như đã đề cập trong câu trả lời này, hoặc, nếu không thể bằng cách nào đó, hãy triển khai IdlingResource sẽ thông báo cho UiController của Espresso về thời điểm có thể tiếp tục thực hiện thử nghiệm.
haffax

2

Bạn nên sử dụng Espresso Idling Resource, nó được đề xuất tại CodeLab này

Tài nguyên chạy không tải đại diện cho một hoạt động không đồng bộ có kết quả ảnh hưởng đến các hoạt động tiếp theo trong thử nghiệm giao diện người dùng. Bằng cách đăng ký tài nguyên chạy không tải với Espresso, bạn có thể xác thực các hoạt động không đồng bộ này một cách đáng tin cậy hơn khi thử nghiệm ứng dụng của mình.

Ví dụ về cuộc gọi không đồng bộ từ Người trình bày

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

Sự phụ thuộc

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

Đối với 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 chính thức: https://github.com/googlecodelabs/android-testing

Ví dụ về IdlingResource: https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample


0

Mặc dù tôi nghĩ tốt nhất nên sử dụng Idling Resources cho việc này ( https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/ ), bạn có thể sử dụng điều này như một phương án dự phòng:

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

và sau đó gọi nó trong mã của bạn như:

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

thay vì

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

Điều này cũng cho phép bạn thêm thời gian chờ cho các hành động xem và xem các xác nhận.


Sử dụng một dòng mã dưới đây để giải quyết bất kỳ trường hợp thử nghiệm Test Espresso nào: SystemClock.sleep (1000); // 1 giây
Nikunjkumar Kapupara

đối với tôi đây chỉ hoạt động bằng cách thay đổi dòng này return new TimedViewInteraction(Espresso.onView(viewMatcher));vớireturn new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));
Manuel Schmitzberger

0

Tiện ích của tôi lặp lại việc thực thi có thể chạy hoặc có thể gọi cho đến khi nó trôi qua mà không có lỗi hoặc ném có thể ném sau một thời gian chờ. Nó hoạt động hoàn hảo cho các bài kiểm tra Espresso!

Giả sử tương tác lần xem cuối cùng (nhấp vào nút) kích hoạt một số luồng nền (mạng, cơ sở dữ liệu, v.v.). Do đó, một màn hình mới sẽ xuất hiện và chúng tôi muốn kiểm tra nó trong bước tiếp theo, nhưng chúng tôi không biết khi nào màn hình mới sẽ sẵn sàng để kiểm tra.

Cách tiếp cận được đề xuất là buộc ứng dụng của bạn gửi thông báo về trạng thái chuỗi cho bài kiểm tra của bạn. Đôi khi chúng ta có thể sử dụng các cơ chế tích hợp sẵn như OkHttp3IdlingResource. Trong các trường hợp khác, bạn nên chèn các đoạn mã vào các vị trí khác nhau của nguồn ứng dụng (bạn nên biết logic ứng dụng!) Chỉ để hỗ trợ thử nghiệm. Hơn nữa, chúng tôi nên tắt tất cả các hoạt ảnh của bạn (mặc dù đó là một phần của giao diện người dùng).

Cách tiếp cận khác đang chờ đợi, ví dụ SystemClock.sleep (10000). Nhưng chúng ta không biết phải đợi bao lâu và ngay cả sự trì hoãn lâu cũng không thể đảm bảo thành công. Mặt khác, bài kiểm tra của bạn sẽ kéo dài.

Cách tiếp cận của tôi là thêm điều kiện thời gian để xem tương tác. Ví dụ: chúng tôi kiểm tra rằng màn hình mới sẽ xuất hiện trong 10000 mc (thời gian chờ). Nhưng chúng tôi không chờ đợi và kiểm tra nó nhanh như chúng tôi muốn (ví dụ: cứ sau 100 mili giây) Tất nhiên, chúng tôi chặn luồng kiểm tra theo cách như vậy, nhưng thông thường, đó chỉ là những gì chúng tôi cần trong những trường hợp như vậy.

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

Đây là nguồn lớp của tôi:

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

Đây là một trình trợ giúp mà tôi đang sử dụng trong Kotlin cho Android Tests. Trong trường hợp của tôi, tôi đang sử dụng longOperation để bắt chước phản hồi của máy chủ nhưng bạn có thể điều chỉnh nó theo mục đích của mình.

@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

Tôi sẽ thêm cách làm này vào hỗn hợp:

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

Được gọi như thế này:

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

Bạn có thể thêm các tham số như số lần lặp tối đa, độ dài lần lặp, v.v. vào hàm SuspeedUntilSuccess.

Tôi vẫn thích sử dụng tài nguyên ở chế độ chạy không tải, nhưng khi các bài kiểm tra đang hoạt động do hoạt ảnh chậm trên thiết bị, tôi sử dụng chức năng này và nó hoạt động tốt. Tất nhiên, nó có thể treo trong tối đa 5 giây như trước khi thất bại, vì vậy nó có thể tăng thời gian thực hiện các bài kiểm tra của bạn nếu hành động để thành công không bao giờ thành công.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.