How to automate number picker using espresso. I want to set specific time in the timePicker using espresso.
To match a View by its class name you can simply use:
onView(withClassName(Matchers.equalTo(TimePicker.class.getName())));
Once you have the ViewInteraction object you can set a value on it defining and using a ViewAction as following:
public static ViewAction setTime(final int hour, final int minute) {
return new ViewAction() {
#Override
public void perform(UiController uiController, View view) {
TimePicker tp = (TimePicker) view;
tp.setCurrentHour(hour);
tp.setCurrentMinute(minute)
}
#Override
public String getDescription() {
return "Set the passed time into the TimePicker";
}
#Override
public Matcher<View> getConstraints() {
return ViewMatchers.isAssignableFrom(TimePicker.class);
}
};
}
Match the view and then perform the action:
ViewInteraction numPicker = onView(withClassName(Matchers.equalTo(NumberPicker.class.getName())));
numPicker.perform(setNumber(1));
Create a ViewAction to set the number:
public static ViewAction setNumber(final int num) {
return new ViewAction() {
#Override
public void perform(UiController uiController, View view) {
NumberPicker np = (NumberPicker) view;
np.setValue(num);
}
#Override
public String getDescription() {
return "Set the passed number into the NumberPicker";
}
#Override
public Matcher<View> getConstraints() {
return ViewMatchers.isAssignableFrom(NumberPicker.class);
}
};
}
There is a problem with accepted answer : it does not fire on change event. So (if you need it) you can't test if your view react to this on change event.
The following code (kotlin) is not cool anyway but i think it's the only way.
fun setValue(value: Int): ViewAction {
return object : ViewAction {
override fun getDescription(): String {
return "set the value of a " + NumberPicker::class.java.name
}
override fun getConstraints(): Matcher<View> {
return ViewMatchers.isAssignableFrom(NumberPicker::class.java)
}
// the only way to fire onChange event is to call this private method
override fun perform(uiController: UiController?, view: View?) {
val numberPicker = view as NumberPicker
val setValueMethod = NumberPicker::class.java.getDeclaredMethod(
"setValueInternal",
Int::class.java,
Boolean::class.java
)
setValueMethod.isAccessible = true
setValueMethod.invoke(numberPicker, value, true)
}
}
}
For those looking at this question later (like me) this might be helpful: DateTimePickerTest makes use of PickerActions. PickerActions allows code for date pickers like this (Java):
onView(withClassName(Matchers.equalTo(DatePicker.class.getName()))).perform(PickerActions.setDate(year, month + 1, day));
Or for time pickers (Kotlin):
onView(withClassName(Matchers.equalTo(TimePicker::class.java.name))).perform(PickerActions.setTime(0, 10))
I used the built-in TimePickerDialog.
(Kotlin)
fun setAlarmTime(){
val calendar = Calendar.getInstance()
onView(isAssignableFrom(TimePicker::class.java)).perform(
PickerActions.setTime(
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE) + 2
)
)
onView(withText("OK")).perform(click())
}
Firstly, you should open the TimePickerDialog, then use this code. The time will be the current time + 2 minute. After the setup, it clicks on the OK button.
I have found a solution to the problem, that Luigi Massa Gallerano's solution does not trigger the change listener.
Additionally to his method you need to add a wrapping method, that swipes up and down one time each. This triggers the changeListener, though the former value of course, is lost.
fun setNumberPickerValue(viewInteraction: ViewInteraction, value: Int) {
viewInteraction.perform(setValue(value))
viewInteraction.perform(GeneralSwipeAction(Swipe.SLOW, GeneralLocation.TOP_CENTER, GeneralLocation.BOTTOM_CENTER, Press.FINGER))
SystemClock.sleep(50)
viewInteraction.perform(GeneralSwipeAction(Swipe.SLOW, GeneralLocation.BOTTOM_CENTER, GeneralLocation.TOP_CENTER, Press.FINGER))
SystemClock.sleep(50)
}
The example given is using NumberPicker and Kotlin, but in principle, it's the same.
Related
I use as library "com.jaygoo.widget.RangeSeekBar" to get a Range Seek Bar.
Here's my following code XML :
<com.jaygoo.widget.RangeSeekBar
android:id="#+id/seekBarPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:rsb_min="1"
app:rsb_max="5000"
app:rsb_gravity="center"
app:rsb_indicator_background_color="#color/white"
app:rsb_indicator_show_mode="alwaysShow"
app:rsb_indicator_text_color="#color/darkGrey"
app:rsb_indicator_text_size="10sp"
app:rsb_mode="range"
app:rsb_progress_color="#color/honey"
app:rsb_thumb_drawable="#drawable/circle"/>
This RangeSeekBar used to specify the price range, I would like to know How can I add the "$" symbol at the indicator in my seekrangeBar as the follwonig picture :
I add the following kotlin code :
seekBarPrice.leftSeekBar.setIndicatorText("$1")
seekBarPrice.rightSeekBar.setIndicatorText("$1")
seekBarPrice.setRange(1F,5000F)
seekBarPrice.setOnRangeChangedListener(object: OnRangeChangedListener {
override fun onStartTrackingTouch(view: RangeSeekBar?, isLeft: Boolean) {
}
override fun onRangeChanged(
view: RangeSeekBar?,
leftValue: Float,
rightValue: Float,
isFromUser: Boolean
) {
Log.d("tag", "Value: $leftValue")
seekBarPrice.leftSeekBar.setIndicatorText("$".plus(leftValue.toInt()))
seekBarPrice.rightSeekBar.setIndicatorText("$".plus(rightValue.toInt()))
}
override fun onStopTrackingTouch(view: RangeSeekBar?, isLeft: Boolean) {
}
})
And my problem is solved
In your library to put % sign this way so you will change as per your requirement:
seekBarPrice.setIndicatorTextStringFormat("$%s%")
I hope it'll help you...!
I think that's the right way
rangePrice.setOnRangeChangedListener(new OnRangeChangedListener() {
#Override
public void onRangeChanged(RangeSeekBar view, float leftValue, float rightValue, boolean isFromUser)
{
}
#Override
public void onStartTrackingTouch(RangeSeekBar view, boolean isLeft) {
}
#Override
public void onStopTrackingTouch(RangeSeekBar view, boolean isLeft) {
view.setIndicatorTextDecimalFormat("$%s%");
}
});
I'm trying to write simple test for pull to refresh as a part of integration testing. I'm using the newest androidX testing components and Robolectric. I'm testing isolated fragment in which one I'm injecting mocked presenter.
XML layout part
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerTasks"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
Fragment part
binding.refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
presenter.onRefresh();
}
});
Test:
onView(withId(R.id.refreshLayout)).perform(swipeDown());
verify(presenter).onRefresh();
but test doesn't pass, message:
Wanted but not invoked: presenter.onRefresh();
The app works perfectly fine and pull to refresh calls presenter.onRefresh(). I did also debugging of the test and setOnRefreshListener been called and it's not a null. If I do testing with custom matcher to check the status of SwipeRefreshLayout test passes.
onView(withId(R.id.refreshLayout)).check(matches(isRefreshing()));
I did some minor investigation over last weekend since I was facing the same issue and it was bothering me. I also did some comparing with what happens on a device to spot the differences.
Internally androidx.swiperefreshlayout.widget.SwipeRefreshLayout has an mRefreshListener that will run when onAnimationEnd is called. The AnimationEnd will trigger then OnRefreshListener.onRefresh method.
That animation listener (mRefreshListener) is passed to the mCircleView (CircleImageView) and the circle animation start is called.
On a device when the view draw method is called it will call the applyLegacyAnimation method that will, in turn, call the AnimationStart method. At the AnimationEnd, the onRefresh method will be called.
On Robolectric the draw method of the View is never called since the items are not actually drawn. This means that the animation will never run and thus neither will the onRefresh method.
My conclusion is that with the current version of Robolectric is not possible to verify that the onRefresh called due to implementation limitations. It seems though that it is planned to have a realistic rendering in the future.
I'm finally able to solve this using a hacky way :
fun swipeToRefresh(): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View>? {
return object : BaseMatcher<View>() {
override fun matches(item: Any): Boolean {
return isA(SwipeRefreshLayout::class.java).matches(item)
}
override fun describeMismatch(item: Any, mismatchDescription: Description) {
mismatchDescription.appendText(
"Expected SwipeRefreshLayout or its Descendant, but got other View"
)
}
override fun describeTo(description: Description) {
description.appendText(
"Action SwipeToRefresh to view SwipeRefreshLayout or its descendant"
)
}
}
}
override fun getDescription(): String {
return "Perform swipeToRefresh on the SwipeRefreshLayout"
}
override fun perform(uiController: UiController, view: View) {
val swipeRefreshLayout = view as SwipeRefreshLayout
swipeRefreshLayout.run {
isRefreshing = true
// set mNotify to true
val notify = SwipeRefreshLayout::class.memberProperties.find {
it.name == "mNotify"
}
notify?.isAccessible = true
if (notify is KMutableProperty<*>) {
notify.setter.call(this, true)
}
// mockk mRefreshListener onAnimationEnd
val refreshListener = SwipeRefreshLayout::class.memberProperties.find {
it.name == "mRefreshListener"
}
refreshListener?.isAccessible = true
val animatorListener = refreshListener?.get(this) as Animation.AnimationListener
animatorListener.onAnimationEnd(mockk())
}
}
}
}
TypeText doesn't seem to work with SearchView.
onView(withId(R.id.yt_search_box))
.perform(typeText("how is the weather?"));
gives the error:
Error performing 'type text(how is the weather?)' on view 'with id:../yt_search_box'
For anyone that bump into this problem too, the solution is to write a ViewAction for the type SearchView, since the typeText only supports TextEditView
Here my solution:
public static ViewAction typeSearchViewText(final String text){
return new ViewAction(){
#Override
public Matcher<View> getConstraints() {
//Ensure that only apply if it is a SearchView and if it is visible.
return allOf(isDisplayed(), isAssignableFrom(SearchView.class));
}
#Override
public String getDescription() {
return "Change view text";
}
#Override
public void perform(UiController uiController, View view) {
((SearchView) view).setQuery(text,false);
}
};
}
#MiguelSlv answer above, converted to kotlin
fun typeSearchViewText(text: String): ViewAction {
return object : ViewAction {
override fun getDescription(): String {
return "Change view text"
}
override fun getConstraints(): Matcher<View> {
return allOf(isDisplayed(), isAssignableFrom(SearchView::class.java))
}
override fun perform(uiController: UiController?, view: View?) {
(view as SearchView).setQuery(text, false)
}
}
}
This works for me:
onView(withId(R.id.search_src_text)).perform(typeText("how is the weather?"))
Based on César Noreña respose I manage to insert text in the search field.
First I clicked on my view and then typeText on the android search view id.
onView(withId(R.id.my_own_search_menu_id)).perform(click());
onView(withId(R.id.search_src_text)).perform(typeText("how is the weather?"))
Also the X button of SearchView has ID search_close_btn
onView(withId(R.id.search_close_btn)).perform(click()); // first time erase the content
onView(withId(R.id.search_close_btn)).perform(click()); // second time close the SearchView
you can use Resource#getSystem to get the View
Resources.getSystem().getIdentifier("search_src_text",
"id", "android")
onView(withId(Resources.getSystem().getIdentifier("search_src_text",
"id", "android"))).perform(clearText(),typeText("enter the text"))
.perform(pressKey(KeyEvent.KEYCODE_ENTER))
I'm playing with Kotlin and found interesting behavior.
So lets say i want to have some kind of a Factory :
internal interface SomeStupidInterface {
companion object FACTORY {
fun createNew(): ChangeListener {
val time = System.currentTimeMillis()
return ChangeListener { element -> Log.e("J2KO", "time " + time) }
}
fun createTheSame(): ChangeListener {
return ChangeListener { element -> Log.e("J2KO", "time " + System.currentTimeMillis()) }
}
}
fun notifyChanged()
}
where ChangeListener defined in java file:
interface ChangeListener {
void notifyChange(Object element);
}
And then I try to use it from Java like so:
ChangeListener a = SomeStupidInterface.FACTORY.createNew();
ChangeListener b = SomeStupidInterface.FACTORY.createNew();
ChangeListener c = SomeStupidInterface.FACTORY.createTheSame();
ChangeListener d = SomeStupidInterface.FACTORY.createTheSame();
Log.e("J2KO", "createNew a == b -> " + (a == b));
Log.e("J2KO", "createTheSame c == d -> " + (c == d));
The results are:
createNew: a == b -> false
createTheSame: c == d -> true
I can understand why createNew returns new objects due to closure.
But why I'm receiving the same instance from createTheSame method?
P.S. I know that code above is not idiomatic :)
This has to do with performance. Creating less objects obviously is better for performance, so that is what Kotlin tries to do.
For each lambda, Kotlin generates a class that implements the proper interface. So for example the following Kotlin code:
fun create() : () -> Unit {
return { println("Hello, World!") }
}
corresponds with something like:
Function0 create() {
return create$1.INSTANCE;
}
final class create$1 implements Function0 {
static final create$1 INSTANCE = new create$1();
void invoke() {
System.out.println("Hello, World!");
}
}
You can see here that the same instance is always returned.
If you reference a variable that is outside of the lamdba scope however, this won't work: there is no way for the singleton instance to access that variable.
fun create(text: String) : () -> Unit {
return { println(text) }
}
Instead, for each invocation of create, a new instance of the class needs to be instantiated which has access to the text variable:
Function0 create(String text) {
return new create$1(text);
}
final class create$1 implements Function0 {
final String text;
create$1(String text) {
this.text = text;
}
void invoke() {
System.out.println(text);
}
}
That is why your a and b instances are the same, but c and d are not.
First note: your example code doesn't work as is: the interface has to be written in Java in order to be available for use with SAM constructors.
As for the actual question, you've already touched on why this behavior is happening. Lambdas (in this case, the SAM constructors) are compiled to anonymous classes (unless they're inlined). If they capture any outside variables, then for every invocation, a new instance of the anonymous class will be created. Otherwise, since they don't have to have any state, only a single instance will back every invocation of the lambda. I suppose this is for performance reasons, if nothing else. (Credit to the Kotlin in Action book for the information in this paragraph.)
If you want to return a new instance every time without capturing any variables, you can use the full object notation:
fun createNotQUiteTheSame(): ChangeListener {
return object : ChangeListener {
override fun notifyChanged(element: Any?) {
println("time " + System.currentTimeMillis())
}
}
}
Calling the above function multiple times will return different instances for each call. Interestingly, IntelliJ will suggest converting this to the original SAM conversion syntax instead:
fun createNotQUiteTheSame(): ChangeListener {
return ChangeListener { println("time " + System.currentTimeMillis()) }
}
Which, as you've already found out, returns the same instance every time.
I suppose this conversion is offered because comparing whether these stateless instances are equal is very much an edge case. If you need to be able to do comparison between the instances that are returned, you're probably best off with the full object notation. Then you can even add some additional state to each listener, in the form of an id for example.
it looks like you try to use SAM conversion with Kotlin interface.
Note that SAM conversions only work for interfaces, not for abstract classes, even if those also have just a single abstract method.
Also note that this feature works only for Java interop; since Kotlin has proper function types, automatic conversion of functions into implementations of Kotlin interfaces is unnecessary and therefore unsupported.
For implementing interface like you want, you need to use object expression.
Also look at high order functions - I think you need them for your solution.
internal interface SomeStupidInterface {
interface ChangeListener {
fun notifyChanged(element: Any)
}
companion object FACTORY {
fun createNew(): ChangeListener {
val time = System.currentTimeMillis()
return object : ChangeListener {
override fun notifyChanged(element: Any) {
println("J2KO" + "time " + time)
}
}
}
fun createTheSame(): ChangeListener {
return object : ChangeListener {
override fun notifyChanged(element: Any) {
println("J2KO" + "time " + System.currentTimeMillis())
}
}
}
}
fun notifyChanged()
}
Also In IntelliJ IDEA I can't compile your code.
Is there a general approach for scrolling to non-list View items that are not yet visible on the screen?
Without any precautions, Espresso will indicate that "No Views in hierarchy found matching with id .....
I found this answer ... is this the best approach?
onView( withId( R.id.button)).perform( scrollTo(), click());
According to the scrollTo JavaDoc, to use the code you specified ( onView( withId( R.id.button)).perform( scrollTo(), click()); ), the preconditions are: "must be a descendant of ScrollView" and "must have visibility set to View.VISIBLE". If that is the case, then that will work just fine.
If it is in an AdapterView, then you should use onData instead. In some cases, you may have to implement the AdapterViewProtocol, if your AdapterView is not well behaved.
If it is neither in an AdapterView nor a child of a ScrollView, then you would have to implement a custom ViewAction.
If you have a view inside android.support.v4.widget.NestedScrollView instead of scrollView scrollTo() does not work.
In order to work you need to create a class that implements ViewAction just like ScrollToAction but allows NestedScrollViews:
public Matcher<View> getConstraints() {
return allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
isDescendantOfA(anyOf(
isAssignableFrom(ScrollView.class),
isAssignableFrom(HorizontalScrollView.class),
isAssignableFrom(NestedScrollView.class))
)
);
}
extra tip and access the action like:
public static ViewAction betterScrollTo() {
return actionWithAssertions(new AllScrollViewsScrollToAction());
}
But with this scroll it does not trigger events from the layout managers.
Code that worked for me is:
ViewInteraction tabView = onView(allOf(
childAtPosition(childAtPosition(withId(R.id.bottomControlTabView), 0), 1),
isDisplayed()));
tabView.perform(click());
tabView.perform(click());
public static Matcher<View> childAtPosition(final Matcher<View> parentMatcher,
final int position) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
#Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
The code onView( withId( R.id.button)).perform( scrollTo(), click()); will work if the view is descendant of ScrollView, HorizontalScrollView or ListView.
If we have NestedScrollView instead of ScrollView and for those who don't want to look into ScrollToAction class code I wrote the sample.
As Bruno Oliveira said we can do something like this:
class ScrollToActionImproved : ViewAction {
override fun getConstraints(): Matcher<View> {
return allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
isDescendantOfA(
anyOf(
isAssignableFrom(ScrollView::class.java),
isAssignableFrom(HorizontalScrollView::class.java),
isAssignableFrom(NestedScrollView::class.java)
)
)
)
}
override fun getDescription(): String = "scroll to view"
override fun perform(uiController: UiController?, view: View?) {
if (isDisplayingAtLeast(90).matches(view)) {
//View is already displayed
return
}
val rect = Rect()
view!!.getDrawingRect(rect)
if (!view.requestRectangleOnScreen(rect, true)) {
//Scrolling to view was requested, but none of the parents scrolled.
}
uiController!!.loopMainThreadUntilIdle()
if (!isDisplayingAtLeast(90).matches(view)) {
throw PerformException.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(
RuntimeException(
"Scrolling to view was attempted, but the view is not displayed"
)
)
.build()
}
}
}
And use it like this:
fun scrollToImproved(): ViewAction =
actionWithAssertions(ScrollToActionImproved())
/* some logic */
onView(withId(R.id.button)).perform(scrollToImproved())
It should work.