1

I'm testing a ViewPager application. I'm starting a ViewPagerIdlingResource, then performing a swipeLeft(), but before ViewPager can even begin scrolling the onView().check() is called and the test fails. Here is my ViewPagerIdlingResource which I pretty much copied from vaughandroid

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;

  private boolean anyPageScrolledHappened = false;

  public ViewPagerIdlingResource(ViewPager viewPager, String name) {
    Log.i("ATAG", "ViewPagerIdlingResource constructor");
    viewPager.addOnPageChangeListener(new ViewPagerListener());
    mName = name;
  }

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

  @Override
  public boolean isIdleNow() {
    Log.i("ATAG", "isIdleNow: "+mIdle);
    return mIdle;
  }

  @Override
  public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
    Log.i("ATAG", "ViewPagerIdlingResource registerIdleTransitionCallback");
    mIdle = true;
    mResourceCallback = resourceCallback;
  }

  public void setIdle (boolean idle) {
    Log.i("ATAG", "setIdle: false and anyPageScrolledHappened = "+anyPageScrolledHappened);
    if (!anyPageScrolledHappened && false == idle) {
      mIdle = false;
    }
  }

  private class ViewPagerListener extends ViewPager.SimpleOnPageChangeListener {

    @Override
    public void onPageScrollStateChanged(int state) {
      anyPageScrolledHappened = true;
      Log.i("ATAG", "ViewPager OnPageScrollStateChanged: ScrollState: " + 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) {
        Log.i("ATAG", "OnTransitionToIdle");
      mResourceCallback.onTransitionToIdle();
      }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
      Log.i("ATAG", "ViewPager OnPageScrolled:           ScrollState: " + "none");
    }

    @Override
    public void onPageSelected(int position) {
      Log.i("ATAG", "ViewPager OnPageSelected:           ScrollState: " + "none");
    }
  }
} 

And here are my test.

@RunWith(AndroidJUnit4.class)
public class StraightLineTest {
  private ViewPagerIdlingResource idlingResource;

  private static final String shmi_s   = "Shmi Skywalker";
  private static final String anakin_s = "Anakin Skywalker";
  private static final String leia_o   = "Leia Organa";
  private static final String jacen_s  = "Jacen Solo";
  private static final String allana_s = "Allana Solo";

  @Rule
  public IntentsTestRule<MainActivity> mActivityRule =
    new IntentsTestRule(MainActivity.class, true, false);

  @After
  public void tearDownIdlingResource () {
    unregisterIdlingResources(idlingResource);
  }

  @Test
  public void firstSwipe () {

    Activity activity = startActivity();

    idlingResource = new ViewPagerIdlingResource((ViewPager)activity.
      findViewById(R.id.view_pager), "VPIR_0");
    registerIdlingResources(idlingResource);


    onView(isRoot()).perform(swipeLeft());
    //idlingResource.setIdle(false);
    //waitForViewPagerResponse(1000);

    onView(allOf(withId(R.id.character_name),withText(anakin_s))).
    check(matches(isCompletelyDisplayed()));
  }

  @Test
  public void fifthSwipe () {
    Activity activity = startActivity();

    idlingResource = new ViewPagerIdlingResource((ViewPager)activity.
      findViewById(R.id.view_pager), "VPIR_0");
    registerIdlingResources(idlingResource);

    onView(isRoot()).perform(swipeLeft());
    onView(allOf(withId(R.id.character_name),withText(anakin_s))).
      check(matches(isCompletelyDisplayed()));

    onView(isRoot()).perform(swipeLeft());
    onView(allOf(withId(R.id.character_name),withText(leia_o))).
      check(matches(isCompletelyDisplayed()));

    onView(isRoot()).perform(swipeLeft());
    onView(allOf(withId(R.id.character_name),withText(jacen_s))).
      check(matches(isCompletelyDisplayed()));

    onView(isRoot()).perform(swipeLeft());
    onView(allOf(withId(R.id.character_name),withText(allana_s))).
      check(matches(isCompletelyDisplayed()));
  }
  private MainActivity startActivity() {
    return mActivityRule.launchActivity(null);
  }

  public static void waitForViewPagerResponse(long millis) {
    final long startTime = System.currentTimeMillis();
    final long endTime = startTime + millis;
    do {}
    while (System.currentTimeMillis() < endTime);
  }
}

So, the fifthSwipe() test always passes.

The firstSwipe() test usually fails. When firstSwipe() fails none of the Log methods inside of the ViewPageListener are called. If I call waitForViewPagerResponse() after the swipeLeft() call, then the ViewPagerListener log methods are called, and the test passes.

If I set the IdlingResource's mIdle to false after swipeLeft() with a call to setIdle(false), then the test just sits until it is timed out and there are no ViewPagerListener log messages.

I tried to find some way to set mIdle to false until it can start to scroll for the first time, then it will set itself to true in the onPageScrollStateChanged() method. But the test just waits, the scroll never happens and the check never happens, it just times out. I conclude that when isIdleNow() returns false, ViewPager can't start to scroll. Why would isIdleNow() affect the ViewPager? And I think that it does because if I just let time pass with waitForViewPagerResponse() then eventually I get callbacks to ViewPagerListener.

Does anyone have an IdlingResource that doesn't have this problem for ViewPagers? Am I implementing this IdlingResource incorrectly?

Here's the project on github. https://github.com/flocela/PagerAdapter

EDIT Here is the log when it fails (case where .setIdle() and waitForViewPagerResponse() are still commented out.) The point I'm trying to make is that Performing 'fast swipe' action happens, but before the ViewPager can scroll (which would be indicated by ViewPagerListener logs) the check is made and the test fails.

I/ATAG: ViewPagerIdlingResource constructor
I/ATAG: ViewPagerIdlingResource registerIdleTransitionCallback
I/ATAG: isIdleNow: true
D/InputManagerEventInjectionStrategy: Creating injection strategy with input manager.
I/ATAG: isIdleNow: true
I/ATAG: isIdleNow: true
I/ViewInteraction: Performing 'fast swipe' action on view is a root view. I/ATAG: isIdleNow: true
I/ATAG: isIdleNow: true (Repeats for a while)
D/LifecycleMonitor: Lifecycle status change: ---.---.MainActivity@d771792 in: PAUSED
I/TestRunner: failed: firstSwipe(---.---.StraightLineWIRTest)
I/TestRunner: ----- begin exception ----- I/TestRunner: android.support.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'at least 100 percent of the view's area is displayed to the user.' doesn't match the selected view.
Expected: at least 100 percent of the view's area is displayed to the user.
Got: "AppCompatTextView{id=2131427414, res-name=character_name, visibility=VISIBLE,(lots of stuff), x=0.0, y=0.0, text=Anakin Skywalker, input-type=0, ime-target=false, has-links=false}"

EDIT Here is the log output when I call idlingResource.setIdle(false) after swipeLeft(). waitForViewPagerResponse() is still commented out. My point being that because isIdleNow() returns false, the ViewPager isn't scrolling. Why would that be?

I/ATAG: ViewPagerIdlingResource constructor
I/ATAG: ViewPagerIdlingResource registerIdleTransitionCallback
I/ATAG: isIdleNow: true
D/InputManagerEventInjectionStrategy: Creating injection strategy with input manager.
I/ATAG: isIdleNow: true
I/ATAG: isIdleNow: true
I/ViewInteraction: Performing 'fast swipe' action on view is a root view.
I/ATAG: isIdleNow: true
I/ATAG: isIdleNow: true (repeats for a while)
I/ATAG: setIdle: false and anyPageScrolledHappened = false
I/ATAG: isIdleNow: false
I/ATAG: isIdleNow: false
W/IdlingPolicy: These resources are not idle: [VPIR_0] (2 lines repeat)
I/ATAG: isIdleNow: false (2 lines repeat)
D/LifecycleMonitor: Lifecycle status change: ---.---.MainActivity@d771792 in: PAUSED
I/TestRunner: failed: firstSwipe(flobee.pageradapterex.StraightLineWIRTest)
I/TestRunner: ----- begin exception ----- I/TestRunner: android.support.test.espresso.IdlingResourceTimeoutException: Wait for [VPIR_0] to become idle timed out

Community
  • 1
  • 1
flobacca
  • 936
  • 2
  • 17
  • 42
  • 1
    I think you need to initialize isIdle to false or use your setIdle() method. – dazza5000 Jan 07 '17 at 02:41
  • I tried that, using setIdle(), and it sets mIdle to false, but then the ViewPager never scrolls, or at least the callback methods in ViewPager are never called, which leads me to believe ViewPager never scrolls. Then the test just times out. – flobacca Jan 07 '17 at 03:45
  • What is isRoot() ? – dazza5000 Jan 07 '17 at 12:20
  • I found this: https://stackoverflow.com/questions/37294132/espresso-doesnt-wait-for-swipe-action-on-a-viewpager-to-be-finished – dazza5000 Jan 07 '17 at 12:22
  • @dazza5000, that's true Espresso doesn't wait for the swipe to go through, which I believe is why they invented IdlingResource, which is what I am trying to implement. – flobacca Jan 07 '17 at 17:55

0 Answers0