android – ProgressBars and Espresso-ThrowExceptions

Exception or error:

When I have a ProgressBar in layouts that are displayed when running some espresso-tests – then I run into:

Caused by: android.support.test.espresso.AppNotIdleException: Looped for 1670 iterations over 60 SECONDS. The following Idle Conditions failed .

What is a nice way to work around this? Found some hackish things but searching for a nice way

How to solve:

If the ProgressBar is invisible when the test starts, the Drawable can be replaced with by a custom ViewAction:

// Replace the drawable with a static color
onView(isAssignableFrom(ProgressBar.class)).perform(replaceProgressBarDrawable());

// Click a button (that will make the ProgressBar visible)
onView(withText("Show ProgressBar").perform(click());

The custom ViewAction:

public static ViewAction replaceProgressBarDrawable() {
    return actionWithAssertions(new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isAssignableFrom(ProgressBar.class);
        }

        @Override
        public String getDescription() {
            return "replace the ProgressBar drawable";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            // Replace the indeterminate drawable with a static red ColorDrawable
            ProgressBar progressBar = (ProgressBar) view;
            progressBar.setIndeterminateDrawable(new ColorDrawable(0xffff0000));
            uiController.loopMainThreadUntilIdle();
        }
    });
}

###

I have the same problem. I could not figure out a totally elegant solution, but I will post my approach either.

What I tried to do is to override the indeterminateDrawable on the ProgressBar. When having a simple drawable no animation takes place and the Espresso test does not ran into the Idle issue.

Unfortunately main and androidTest are treated the same. I did not find a way to override the styles for my ProgressBar.

It now ended up in combining some ideas from https://gist.github.com/Mauin/62c24c8a53593c0a605e#file-progressbar-java and How to detect whether android app is running UI test with Espresso.

At first I created to custom ProgressBar classes, one for debug and one for release. The release version only calls the super constructors and does nothing else. The debug version overrides the method setIndeterminateDrawable. With this I could set a simple drawable instead of the animated one.

Release code:

public class ProgressBar extends android.widget.ProgressBar {

  public ProgressBar(Context context) {
    super(context);
  }

  public ProgressBar(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }

}

Debug code:

public class ProgressBar extends android.widget.ProgressBar {

  public ProgressBar(Context context) {
    super(context);
  }

  public ProgressBar(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }

  @SuppressWarnings("deprecation")
  @Override
  public void setIndeterminateDrawable(Drawable d) {
    if (isRunningTest()) {
        d = getResources().getDrawable(R.drawable.ic_replay);
    }
    super.setIndeterminateDrawable(d);
  }

  private boolean isRunningTest() {
    try {
        Class.forName("base.EspressoTestBase");
        return true;
    } catch (ClassNotFoundException e) {
        /* no-op */
    }
    return false;
  }

}

As you can see I also added a check if my app is running an Espresso test, whereas the class I am searching for is the base of my Espresso tests.

The bad thing is that you have to update all your code to use your custom ProgressBar. But the good thing is that your release code does not have a major impact with this solution.

###

I have the similar issue. The test failed as early as the first call getActivity(). So the indeterminate drawable of ProgressBar have to be replaced after the activity started.

 Application application = (Application)this.getInstrumentation().getTargetContext().getApplicationContext();
    application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            //not here, it's too early
        }

        @Override
        public void onActivityStarted(Activity activity) {
            //find the progressBar in your activity
            ProgressBar progressBar = ((ProgressBar) activity.findViewById(R.id.progress_bar));
            if(progressBar != null) {
                //replace progress bar drawable as not animated
                progressBar.setIndeterminateDrawable(new ColorDrawable(0xffff0000));
            }
        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivityStopped(Activity activity) {

        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {

        }
    });

    //Now you can start the activity
    getActivity();

###

Based on Thomas R. solution, another approach is to change the drawable of the ProgressBar in the test, to avoid modifying production code.

Example:

    Activity activity = startActivity();

    // override progress bar infinite animation with a simple image
    ProgressBar progressBar = (ProgressBar) activity.findViewById(R.id.loading_progressbar);
    progressBar.setIndeterminateDrawable(activity.getDrawable(android.R.drawable.ic_lock_lock));

    // click on the button that triggers the display of the progress bar
    onView(withId(R.id.login_button)).perform(click());

###

This answer might be late. With espresso, you have to turn off animation.

On your device, under Settings > Developer options, disable the
following 3 settings:

Window animation scale, Transition animation scale, Animator duration scale

https://developer.android.com/training/testing/espresso/setup.html#set-up-environment

There is an answer at Testing progress bar on Android with Espresso by riwnodennyk

But be cautious about UIAnimator

Caution: We recommend testing your app using UI Automator only when
your app must interact with the system to fulfill a critical use case.
Because UI Automator interacts with system apps and UIs, you need to
re-run and fix your UI Automator tests after each system update. Such
updates include Android platform version upgrades and new versions of
Google Play services. As an alternative to using UI Automator, we
recommend adding hermetic tests or separating your large test into a
suite of small and medium tests. In particular, focus on testing one
piece of inter-app communication at a time, such as sending
information to other apps and responding to intent results. The
Espresso-Intents tool can help you write these smaller tests.

https://developer.android.com/training/testing/fundamentals.html#large-tests

Leave a Reply

Your email address will not be published. Required fields are marked *