25

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

ligi
  • 39,001
  • 44
  • 144
  • 244

5 Answers5

16

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();
        }
    });
}
thaussma
  • 9,756
  • 5
  • 44
  • 46
10

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.

Community
  • 1
  • 1
Thomas R.
  • 7,988
  • 3
  • 30
  • 39
8

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();
Raylen Moore
  • 125
  • 1
  • 8
  • Thanks! Very useful when the progress bar appears in the creation of the activity. – matteoh Aug 03 '17 at 10:13
  • This is fantastic solution. I just took this code and wrapped it in a static `@ClassRule`. No changes in app code needed which was exactly what I was looking for. – Gautham C. Dec 29 '17 at 18:14
  • Nice solution. I just had to move code in onActivityResumed instead of onActivityStarted as my progress bar is inside a fragment that is loaded by the activity. – Stéphane Aug 16 '18 at 14:52
  • This was the solution that worked best for me since my progress bars are all presented visible and indeterminate. – David Berry Feb 11 '19 at 20:48
  • This helps prevent the Espresso bug on Android 5: `"java.lang.RuntimeException: Could not launch intent Intent { Activity } within 45 seconds. Perhaps the main thread has not gone idle within a reasonable amount of time?"` You can put this code in a `@BeforeClass` method, and it doesn't need any changes to the app code under test. More info: https://jasonfry.co.uk/blog/android-espresso-test-hangs-with-indeterminate-progressbar/ – Mr-IDE Mar 01 '20 at 16:58
3

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());
Community
  • 1
  • 1
Alvaro Gutierrez Perez
  • 3,669
  • 1
  • 16
  • 24
1

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

hjchin
  • 864
  • 2
  • 8
  • 25