18

does anybody know, how to set a seekBar to a specific value or just a click on that view on Espresso UI Testing ?

I just get an exception: Error performing 'single click' on view with id...

onView(withId(R.id.FilterPriceMax)).perform(click());
ligi
  • 39,001
  • 44
  • 144
  • 244
Luser_k
  • 514
  • 1
  • 4
  • 18

5 Answers5

33

You might want to give the following code a try:

First you could match the SeekBar with its class name:

onView(withClassName(Matchers.equalTo(SeekBar.class.getName()))).perform(setProgress(progress));

where setProgress(final int progress) is a ViewAction you defined as:

public static ViewAction setProgress(final int progress) {
        return new ViewAction() {
            @Override
            public void perform(UiController uiController, View view) {
                SeekBar seekBar = (SeekBar) view;
                seekBar.setProgress(progress);
            }
            @Override
            public String getDescription() {
                return "Set a progress on a SeekBar";
            }
            @Override
            public Matcher<View> getConstraints() {
                return ViewMatchers.isAssignableFrom(SeekBar.class);
            }
        };
    }
Luigi Massa Gallerano
  • 2,347
  • 4
  • 23
  • 25
21

I have a custom Seekbar, and also I have multiple Seekbars in my View. I managed do my test with this code below:

onView(withId(R.id.my_seek_bar)).perform(setProgress(10));

and

public static ViewAction setProgress(final int progress) {
        return new ViewAction() {
            @Override
            public void perform(UiController uiController, View view) {
                ((MyCustomSeekBar) view).setSelectedProgress(progress);
                //or ((SeekBar) view).setProgress(progress);
            }

            @Override
            public String getDescription() {
                return "Set a progress";
            }

            @Override
            public Matcher<View> getConstraints() {
                return ViewMatchers.isAssignableFrom(RangeSeekBar.class);
            }
        };
    }
Androiderson
  • 16,865
  • 6
  • 62
  • 72
rsicarelli
  • 1,013
  • 1
  • 11
  • 28
14

I use a swipe-action to perform an actual swipe on the SeekBar. This makes sure the callback method (SeekBar.OnSeekBarChangeListener. onProgressChanged) is called with fromUser set to true. It is also more in the line of click testing.

public static ViewAction scrubSeekBarAction(int progress) {
    return actionWithAssertions(new GeneralSwipeAction(
            Swipe.SLOW,
            new SeekBarThumbCoordinatesProvider(0),
            new SeekBarThumbCoordinatesProvider(progress),
            Press.PINPOINT));
}

private static class SeekBarThumbCoordinatesProvider implements CoordinatesProvider {
    int mProgress;

    public SeekBarThumbCoordinatesProvider(int progress) {
        mProgress = progress;
    }

    private static float[] getVisibleLeftTop(View view) {
        final int[] xy = new int[2];
        view.getLocationOnScreen(xy);
        return new float[]{ (float) xy[0], (float) xy[1] };
    }

    @Override
    public float[] calculateCoordinates(View view) {
        if (!(view instanceof SeekBar)) {
            throw new PerformException.Builder()
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new RuntimeException(String.format("SeekBar expected"))).build();
        }
        SeekBar seekBar = (SeekBar) view;
        int width = seekBar.getWidth() - seekBar.getPaddingLeft() - seekBar.getPaddingRight();
        double progress = mProgress == 0 ? seekBar.getProgress() : mProgress;
        int xPosition = (int) (seekBar.getPaddingLeft() + width * progress / seekBar.getMax());
        float[] xy = getVisibleLeftTop(seekBar);
        return new float[]{ xy[0] + xPosition, xy[1] + 10 };
    }
}
Jakob C
  • 325
  • 2
  • 7
  • 1
    This is a promising solution but it returns the wrong value on my device. – msc87 Jan 18 '18 at 12:24
  • Not sure what you mean by "wrong value", but if you mean that the progress value after `scrubSeekBarAction` is not the same as you set it, than it seems to help to have a `Thread.sleep(1000)` before (and maybe after) 'scrub...'. I guess it's similar problem like this "perform(click()) is doing a long click" issue. – allofmex Feb 22 '18 at 17:03
  • I experienced inaccuracy on LG devices API 21-23 (virtual and physical). The swipe was performed correctly (tracing using `Pointer location` in `Developer options`), but the `SeekBar` did not register last ~ 20 % of the swipe regardless of speed, I don't know why. I then changed `GeneralSwipeAction` with `GeneralClickAction` and works great for my case. It's a click instead of swipe. I also added `0.25` to `progress` to be on a safe side as older APIs round down the click/swipe whereas the newer ones round it to the nearest value, though it did not turn out to be necessary so far. – Jure Sencar Jul 30 '20 at 23:45
10

In case you want to check the progress value

  public static Matcher<View> withProgress(final int expectedProgress) {
    return new BoundedMatcher<View, SeekBar>(SeekBar.class) {
      @Override
      public void describeTo(Description description) {
        description.appendText("expected: ");
        description.appendText(""+expectedProgress);
      }

      @Override
      public boolean matchesSafely(SeekBar seekBar) {
        return seekBar.getProgress() == expectedProgress;
      }
    };
  }
Maragues
  • 37,861
  • 14
  • 95
  • 96
5

I have used this to do some basic testing on seekbar UI components if you don't require specific values. It assumes the 'check()' is based on a 0-100 percentage.

onView(withId(R.id.yourSeekBar)).perform(new GeneralClickAction(Tap.SINGLE, GeneralLocation.CENTER_LEFT, Press.FINGER));
onView(withId(R.id.yourSeekBarUserFeedback)).check(matches(withText("0")));

onView(withId(R.id.yourSeekBar)).perform(new GeneralClickAction(Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER));
onView(withId(R.id.yourSeekBarUserFeedback)).check(matches(withText("50")));

onView(withId(R.id.yourSeekBar)).perform(new GeneralClickAction(Tap.SINGLE, GeneralLocation.CENTER_RIGHT, Press.FINGER));
onView(withId(R.id.yourSeekBarUserFeedback)).check(matches(withText("100")));
Frost Metoh
  • 161
  • 1
  • 4
  • 2
    The `GeneralClickAction()` that takes 3 parameters is declared as deprecated. We are now recommended to use `GeneralClickAction()` that takes 5 parameters, as shown here: https://stackoverflow.com/questions/22177590/click-by-bounds-coordinates#48446640 – Mr-IDE Aug 04 '19 at 08:50