I was working on a simple example for using a SeekBar, and am having problems getting my Espresso test to work. When I run the tests, I can see the SeekBar move on the emulator screen, but it apparently is not calling the OnSeekBarChangeListener because the TextView doesn't change value ('Hello World!' is the initial value). If I run the program and test it manually the TextView is updated as I expect.
Main Program:
package edu.eku.styere.seekbar;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
public class MainActivity extends Activity {
private int cur_progress = 0;
boolean running = false;
final int SEEK_MAX = 100;
final int DELAY_MILLIS = 200;
private TextView mProgressLabel;
private SeekBar mProgressBar;
// message handler
private Handler mHandler = new Handler();
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// fetch items
mProgressLabel = (TextView) findViewById( R.id.progress_text );
mProgressBar = (SeekBar) findViewById( R.id.progress_seekbar );
Button startButton = (Button) findViewById( R.id.btn_start );
startButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// start the timer/progresss bar
cur_progress = 0;
running = true;
// start messages
mHandler.postDelayed( mUpdateTimeTask, DELAY_MILLIS );
}
});
// respond to changes in the seek bar by the user
mProgressBar.setOnSeekBarChangeListener( new OnSeekBarChangeListener() {
public void onProgressChanged( SeekBar sb, int progress, boolean fromUser ) {
// user change or from us?
if ( fromUser ) {
// user changed the bar, so get a new value
cur_progress = progress;
// update the text box
mProgressLabel.setText("progress: " + cur_progress );
mHandler.removeCallbacks(mUpdateTimeTask);
if ( running )
mHandler.postDelayed(mUpdateTimeTask, DELAY_MILLIS);
}
}
// need to have these, but not using them here
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
});
}
// the runnable object that corresponds to our timer
private Runnable mUpdateTimeTask = new Runnable() {
public void run() {
// done?
if ( cur_progress >= SEEK_MAX ) {
// stop everything
mHandler.removeCallbacks( mUpdateTimeTask );
running = false;
return;
}
// update the progress
cur_progress++;
// update the text box
mProgressLabel.setText("progress: " + cur_progress );
// update the seek bar (does not cause onChange event)
mProgressBar.setProgress( cur_progress );
//do this again
mHandler.postDelayed( this, DELAY_MILLIS );
}
}; // mUpdateTimeTask
}
Test Code:
package edu.eku.styere.seekbar;
import android.app.Instrumentation;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.CoordinatesProvider;
import android.support.test.espresso.action.GeneralSwipeAction;
import android.support.test.espresso.action.Press;
import android.support.test.espresso.action.Swipe;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.InstrumentationTestCase;
import android.view.MotionEvent;
import android.view.View;
import android.widget.SeekBar;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Random;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
//Random rng = new Random();
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void moveSeekBar() {
onView( withId( R.id.progress_seekbar )).perform(setProgress(35));
// Check that the text was changed
onView(withId(R.id.progress_text))
.check(matches(withText("progress: " + 35)));
}
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);
}
};
}
}
Result of the failed test:
android.support.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'with text: is "progress: 35"' doesn't match the selected view.
Expected: with text: is "progress: 35"
Got: "TextView{id=2131165185, res-name=progress_text, visibility=VISIBLE, width=480, height=29,
has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true,
is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false,
root-is-layout-requested=false, has-input-connection=false, x=0.0, y=72.0,
text=Hello world!, input-type=0, ime-target=false, has-links=false}"
...
Well after some more work I managed to create a solution to my problem, based on a GenericClickAction:
package edu.eku.styere.seekbar;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.CoordinatesProvider;
import android.support.test.espresso.action.GeneralClickAction;
import android.support.test.espresso.action.Press;
import android.support.test.espresso.action.Tap;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
import android.widget.SeekBar;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Random;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
Random rng = new Random();
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void moveSeekBar() {
int cur_progress;
// do an initial move in case the first random number is 0
// -- if it didn't move, the OnSeekBarChangeListener isn't called
onView( withId( R.id.progress_seekbar )).perform(clickSeekBar(25));
// try 10 random locations
for( int i=0; i<10; i++ ) {
cur_progress = rng.nextInt(101); // 0..100
// move it to a random location
onView(withId(R.id.progress_seekbar)).perform(clickSeekBar(cur_progress));
try {
Thread.sleep(1000);
} catch ( Exception e ) {
// do nothing
}
// Check that the text was changed
onView(withId(R.id.progress_text))
.check(matches(withText("progress: " + cur_progress)));
}
}
public static ViewAction clickSeekBar(final int pos){
return new GeneralClickAction(
Tap.SINGLE,
new CoordinatesProvider() {
@Override
public float[] calculateCoordinates(View view) {
SeekBar seekBar = (SeekBar) view;
final int[] screenPos = new int[2];
seekBar.getLocationOnScreen(screenPos);
// get the width of the actual bar area
// by removing padding
int trueWidth = seekBar.getWidth()
- seekBar.getPaddingLeft() - seekBar.getPaddingRight();
// what is the position on a 0-1 scale
// add 0.3f to avoid roundoff to the next smaller position
float relativePos = (0.3f + pos)/(float) seekBar.getMax();
if ( relativePos > 1.0f )
relativePos = 1.0f;
// determine where to click
final float screenX = trueWidth*relativePos + screenPos[0]
+ seekBar.getPaddingLeft();
final float screenY = seekBar.getHeight()/2f + screenPos[1];
float[] coordinates = {screenX, screenY};
return coordinates;
}
},
Press.FINGER);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With