20

The workflow should be the following:

  1. Activity starts
  2. Progress bar is visible
  3. Network request fires (idling resource is already registered so espresso knows how to wait for it).
  4. Progress bar is hidden
  5. Text from network is shown.

Up to this point, I have written assertions for steps 1, 3, 5 and it works perfectly:

onView(withText("foo 1"))
    .check(matches(isDisplayed()));

Problem is, I have no idea how to let espresso know to verify the visibility of progress bar before the request is made and after the request is made.

Consider the onCreate() method is the following:

super.onCreate(...);
setContentView(...);

showProgressBar(true);
apiClient.getStuff(new Callback() {
    public void onSuccess() {
        showProgressBar(false);
    }
});

I have tried the following but it doesn't work:

// Activity is launched at this point.
activityRule.launchActivity(new Intent());

// Up to this point, the request has been fired and response was 
// returned, so the progress bar is now GONE.
onView(withId(R.id.progress_bar))
   .check(matches(isDisplayed()));

onView(withId(R.id.progress_bar))
    .check(matches(not(isDisplayed())));

The reason this is happening is because, since the client is registered as an idling resource, espresso will wait until it is idle again before running the first onView(...progressbar...)... so I need a way to let espresso know to run that BEFORE going to idle.

EDIT: this doesn't work either:

idlingResource.registerIdleTransitionCallback(new IdlingResource.ResourceCallback() {
        @Override
        public void onTransitionToIdle() {
            onView(withId(R.id.progress_bar))
                    .check(matches(isDisplayed()));
        }
    });
Christopher Francisco
  • 15,672
  • 28
  • 94
  • 206
  • Well you will have to make your activity testable. If you want to stick with running integration tests you will need to mock / stub your api calls – David Medenjak Feb 03 '16 at 20:24
  • 1
    The api is stubbed and registered as an idling resource already. That's not what I'm asking. – Christopher Francisco Feb 03 '16 at 20:33
  • @ChristopherFrancisco How did you solve this problem? I'm kinda testing an almost similar scenario, where I press a login button and the async task displays a dialog which I want to test. But espresso won't let me do anything until the async task finishes. – Shivam Pokhriyal May 25 '20 at 10:09
  • Did you find a solution for this problem? I have a similar problem. Check here for my solution attempts https://stackoverflow.com/questions/70830477/test-runs-forever-when-using-espressos-onview-in-mockito-doanswer – enapi Jan 26 '22 at 11:50

4 Answers4

14

Espresso has problems with the animation. You can just set the drawable of the progress bar to something static just for the test and it works as expected.

Drawable notAnimatedDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.whatever);
((ProgressBar) getActivity().findViewById(R.id.progress_bar)).setIndeterminateDrawable(notAnimatedDrawable);

onView(withId(R.id.progress_bar)).check(matches(isDisplayed()));
Matthias Robbers
  • 15,689
  • 6
  • 63
  • 73
5

As I can see Espresso is tightly coupled with skipping dynamic UI actions whatsoever, that's why you can't test ProgressBar using Espresso. However, you can easily accomplish this with another Android Google tool: UiAutomator as following:

    saveButton().click(); // perform action opening ProgressBar with UiAutomator, not Espresso
    assertTrue(progressBar().exists());

Using these static utils:

public static UiObject progressBar() {
    return uiObjectWithText(R.string.my_progress);
}

public static UiObject saveButton() {
    return uiObjectWithId(R.id.my_save_button);
}

public static UiObject uiObjectWithId(@IdRes int id) {
    String resourceId = getTargetContext().getResources().getResourceName(id);
    UiSelector selector = new UiSelector().resourceId(resourceId);
    return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}

public static UiObject uiObjectWithText(@StringRes int stringRes) {
    UiSelector selector = new UiSelector().text(getTargetContext().getString(stringRes));
    return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}

Make sure your build.gradle includes:

androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
riwnodennyk
  • 8,140
  • 4
  • 35
  • 37
  • your solution doesn't work for me here is my question. https://stackoverflow.com/questions/54320660/espresso-testing-call-view-button-click-waiting-after-call-api-response-data-pro please help me sir. – Htut Jan 24 '19 at 02:46
4

It looks like this may not be truly possible. Though it is an older group posting, there is a fairly decisive answer in the Android Test Kit Discussion where it is stated that the UI threads don't rest during the animation of progress bars, and so the Espresso framework cannot execute.

Marcus Klepp recommends moving past this here through the use of build types. The Gradle plugin will permit you to define different build types. You could set up a different layout in your androidTest build type which replaces the View in question with something generic. If all you're doing is confirming that the widget isDisplayed() under one set of conditions, and not(isDisplayed()) under another set of conditions, then you could surely implement that through different layout files. Not that it is not a little bit of a lift.

Finally, there may be another post here which carries some additional information here: "java.lang.RuntimeException: Could not launch intent" for UI with indeterminate ProgressBar

Community
  • 1
  • 1
OYRM
  • 1,395
  • 10
  • 29
0

In my case solution which was provided above works as well but I simplify it, so added build.gradle uiautomator library

androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'

and created a new class which will work for example with Progress bar

public class ProgressBarHandler {

public static void waitUntilGoneProgressBar() {
    progressBar().waitUntilGone(10000);
}

private static UiObject progressBar() {
    return uiObjectWithId(R.id.progress_bar);
}

private static UiObject uiObjectWithId(@IdRes int id) {
    String resourceId = getTargetContext().getResources().getResourceName(id);
    UiSelector selector = new UiSelector().resourceId(resourceId);
    return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}

}

and in my tests use all Espresso methods and when needed only then address to UiAutomator in tests, for example

public class LoginTest extends AbstractTest {

@Rule
public ActivityTestRule<LoginActivity> createAccountActivityTestRule = new ActivityTestRule<>(LoginActivity.class);

@Test
public void loginTest() {
    onView(withId(R.id.login_email)).perform(typeText("autotest666@gmail.com"));
    onView(withId(R.id.input_password)).perform(typeText("Password123."));
    onView(withId(R.id.login_log_in)).perform(click());
    waitUntilGoneProgressBar();
    onView(withId(R.id.fragment_home_title)).check(matches(isDisplayed()));
}