Reputation: 2542
Trying to do some tests with a ViewPager.
I want to swipe between tabs, and I don't want to continue until the swipe is complete. But there doesn't appear to be a way to turn off the animation for the view pager (all animations under the developer options are disabled).
So this always results in a test failure, because the view pager hasn't completed it's animation, and so the view is not completely displayed yet:
// swipe left
onView(withId(R.id.viewpager)).check(matches(isDisplayed())).perform(swipeLeft());
// check to ensure that the next tab is completely visible.
onView(withId(R.id.next_tab)).check(matches(isCompletelyDisplayed()));
Is there an elegant or maybe even recommended way to do this, or am I stuck putting some kind of timed wait in there?
Upvotes: 18
Views: 7296
Reputation: 32103
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 code of the ViewPagerActions class 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.
Upvotes: 4
Reputation: 946
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)
}
Upvotes: 3
Reputation: 1261
My goal was to make a screenshot of the screen with ViewPager2
using Facebook screenshot test library. The easiest approach for me was to check almost every frame whether animation completed, if yes then it's time to make a screenshot:
fun waitForViewPagerAnimation(parentView: View) {
if (parentView is ViewGroup) {
parentView.childrenViews<ViewPager2>().forEach {
while (it.scrollState != ViewPager2.SCROLL_STATE_IDLE) {
Thread.sleep(16)
}
}
}
}
childrenViews
function can be found here
Upvotes: 0
Reputation: 151
I was having issues with @vaughandroid approach, so I did some changes to his approach. This approach will set idle to false as soon as it detects a scrolling is happening and "force" the ViewPager to finish scrolling by using setCurrentItem().
public class ViewPagerIdlingResource implements IdlingResource {
private volatile boolean mIdle = true; // Default to idle since we can't query the scroll state.
private ResourceCallback mResourceCallback;
private ViewPager mViewPager;
public static ViewPagerIdlingResource waitViewPagerSwipe(ViewPager viewPager) {
return new ViewPagerIdlingResource(viewPager);
}
private ViewPagerIdlingResource(ViewPager viewPager) {
mViewPager = viewPager;
mViewPager.addOnPageChangeListener(new ViewPagerListener());
}
@Override
public String getName() {
return ViewPagerIdlingResource.class.getSimpleName();
}
@Override
public boolean isIdleNow() {
return mIdle;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
mResourceCallback = resourceCallback;
}
private class ViewPagerListener extends ViewPager.SimpleOnPageChangeListener {
float mPositionOffset = 0.0f;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (isSwipingToRight(positionOffset)) {
mIdle = false;
mViewPager.setCurrentItem(position + 1);
} else if (isSwipingToLeft(positionOffset)) {
mIdle = false;
mViewPager.setCurrentItem(position - 1);
}
mPositionOffset = positionOffset;
if (positionOffset == 0 && !mIdle && mResourceCallback != null) {
mResourceCallback.onTransitionToIdle();
mIdle = true;
mPositionOffset = 0.0f;
}
}
private boolean isSwipingToRight(float positionOffset) {
return mPositionOffset != 0.0f && positionOffset > mPositionOffset && mIdle;
}
private boolean isSwipingToLeft(float positionOffset) {
return mPositionOffset != 0.0f && positionOffset < mPositionOffset && mIdle;
}
}
}
Upvotes: 0
Reputation: 901
Try this,
onView(withId(R.id.pager)).perform(pagerSwipeRight()).perform(pagerSwipeLeft());
private GeneralSwipeAction pagerSwipeRight(){
return new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT,
GeneralLocation.CENTER_RIGHT, Press.FINGER);
}
private GeneralSwipeAction pagerSwipeLeft(){
return new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_RIGHT,
GeneralLocation.CENTER_LEFT, Press.FINGER);
}
Upvotes: 0
Reputation: 4374
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();
}
}
}
}
Upvotes: 29
Reputation: 44118
You can either do a lot of work and use an IdlingResource to implement an OnPageChangeListener
or simply:
SystemClock.sleep(500);
Upvotes: -11