Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ProgressBars and Espresso

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

like image 343
ligi Avatar asked Oct 22 '15 19:10

ligi


4 Answers

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();
        }
    });
}
like image 161
thaussma Avatar answered Nov 15 '22 07:11

thaussma


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.

like image 37
Thomas R. Avatar answered Nov 15 '22 07:11

Thomas R.


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();
like image 8
Raylen Moore Avatar answered Nov 15 '22 06:11

Raylen Moore


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());
like image 3
Alvaro Gutierrez Perez Avatar answered Nov 15 '22 07:11

Alvaro Gutierrez Perez