Wait for view pager animations with espresso?

Since I've done this at least twice now, here is the accepted answer in Kotlin and with androidx ViewPager2:

class ViewPager2IdlingResource(viewPager: ViewPager2, name: String) : IdlingResource {

    private val name: String
    private var isIdle = true // Default to idle since we can't query the scroll state.
    private var resourceCallback: IdlingResource.ResourceCallback? = null

    init {
        viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageScrollStateChanged(state: Int) {
                isIdle = (state == ViewPager.SCROLL_STATE_IDLE // Treat dragging as idle, or Espresso will block itself when swiping.
                    || state == ViewPager.SCROLL_STATE_DRAGGING)
                if (isIdle && resourceCallback != null) {
                    resourceCallback!!.onTransitionToIdle()
                }
            }
        })
        this.name = name
    }

    override fun getName(): String {
        return name
    }

    override fun isIdleNow(): Boolean {
        return isIdle
    }

    override fun registerIdleTransitionCallback(resourceCallback: IdlingResource.ResourceCallback) {
        this.resourceCallback = resourceCallback
    }
}

And here is how you use it from a UI test using ActivityScenarioRule:

@get:Rule
val testRule = ActivityScenarioRule(OnboardingActivity::class.java)

private lateinit var viewPager2IdlingResource: ViewPager2IdlingResource

....

@Before
fun setUp() {
    testRule.scenario.onActivity {
        viewPager2IdlingResource =
            ViewPager2IdlingResource(it.findViewById(R.id.onboarding_view_pager), "viewPagerIdlingResource")
        IdlingRegistry.getInstance().register(viewPager2IdlingResource)
    }
}

@After
fun tearDown() {
    IdlingRegistry.getInstance().unregister(viewPager2IdlingResource)
}

The androidx.test.espresso:espresso-core library offers a ViewPagerActions class which contains a number of methods for scrolling between the pages of a ViewPager. It takes care of waiting until the scroll is complete so you don't need to add any explicit waits or sleeps in your test methods.

If you need to perform similar scrolling on a ViewPager2 instance, you can take the source of the ViewPagerActions class from here and make some minor tweaks to it to get it to work for ViewPager2. Here is an example which you are welcome to take and use.


The IdlingResource @Simas suggests is actually pretty simple to implement:

public class ViewPagerIdlingResource implements IdlingResource {

    private final String mName;

    private boolean mIdle = true; // Default to idle since we can't query the scroll state.

    private ResourceCallback mResourceCallback;

    public ViewPagerIdlingResource(ViewPager viewPager, String name) {
        viewPager.addOnPageChangeListener(new ViewPagerListener());
        mName = name;
    }

    @Override
    public String getName() {
        return mName;
    }

    @Override
    public boolean isIdleNow() {
        return mIdle;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        mResourceCallback = resourceCallback;
    }

    private class ViewPagerListener extends ViewPager.SimpleOnPageChangeListener {

        @Override
        public void onPageScrollStateChanged(int state) {
            mIdle = (state == ViewPager.SCROLL_STATE_IDLE
                    // Treat dragging as idle, or Espresso will block itself when swiping.
                    || state == ViewPager.SCROLL_STATE_DRAGGING);
            if (mIdle && mResourceCallback != null) {
                mResourceCallback.onTransitionToIdle();
            }
        }
    }
}