Recording an Espresso test with a DatePicker - android

The test recorder produces code that promptly fails on being run after recording.
The reason is that while recording, I tap the year, the year spinner pops up, I scroll back and then select one of the years. The recorder does not capture the scrolling.
In Xcode, they added a method to scroll to the item. I could not find something akin in Espresso.
(Using Android Studio 2.3.)

I have not used the recorder in a long time and instead wrote my tests by hand.
I use the following line to set the date in a DatePicker:
onView(withClassName(Matchers.equalTo(DatePicker.class.getName()))).perform(PickerActions.setDate(year, monthOfYear, dayOfMonth));
The PickerActions class is defined in the espresso-contrib library. Add it as follows to your gradle file:
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0'
I then use this in a helper method which clicks the button that opens a DatePickerDialog, sets the date and confirms it by clicking the OK button:
public static void setDate(int datePickerLaunchViewId, int year, int monthOfYear, int dayOfMonth) {
onView(withParent(withId(buttonContainer)), withId(datePickerLaunchViewId)).perform(click());
onView(withClassName(Matchers.equalTo(DatePicker.class.getName()))).perform(PickerActions.setDate(year, monthOfYear, dayOfMonth));
onView(withId(android.R.id.button1)).perform(click());
}
And then use it in my tests as follows:
TestHelper.setDate(R.id.date_button, 2017, 1, 1);
//TestHelper is my helper class that contains the helper method above

Note: This answer is intended to supplement #stamanuel's answer.
To see an example in the Android source code of an Android instrumentation test which opens a DatePickerDialog and selects a date, refer to the PickerActionsTest.java file.
The two key lines of code in that file are the following:
// Sets a date on the date picker widget
onView(isAssignableFrom(DatePicker.class)).perform(setDate(1980, 10, 30));
// Confirm the selected date. This example uses a standard DatePickerDialog
// which uses
// android.R.id.button1 for the positive button id.
onView(withId(android.R.id.button1)).perform(click());
As #stamanuel has mentioned in his answer, the PickerActions.setDate(year:monthOfYear:dayOfMonth:) method is defined in the androidx.test.espresso:espresso-contrib library.

Related

How to perform an action on MaterialDatePicker in an Espresso test?

I have a MaterialDatePicker dialog, and I want to write an Espresso test that selects a date. Unfortunately, I can't use PickerActions for this. I'm looking for something similar to this:
onView(withClassName(Matchers.equalTo(DatePicker.class.getName()))).perform(PickerActions.setDate(year, monthOfYear, dayOfMonth));
Does anyone have any ideas on how to go about this?
Thanks in advance!
I found this answer buried in the question linked above, which you may find useful for setting the date when the InputMode is INPUT_MODE_TEXT.
public static void setDate(LocalDate date){
onView(withTagValue((Matchers.is((Object) ("TOGGLE_BUTTON_TAG"))))).perform(click());
onView(withId(com.google.android.material.R.id.mtrl_picker_text_input_date)).perform(replaceText(date.format(DateTimeFormatter.ofPattern("M/d/yy"))));
}
public static void setTime(LocalTime time){
onView(withId(com.google.android.material.R.id.material_timepicker_mode_button)).perform(click());
onView(withId(com.google.android.material.R.id.material_hour_text_input)).perform(click());
onView(allOf(isDisplayed(), withClassName(is(AppCompatEditText.class.getName())))).perform(replaceText(time.format(DateTimeFormatter.ofPattern("hh"))));
onView(withId(com.google.android.material.R.id.material_minute_text_input)).perform(click());
onView(allOf(isDisplayed(), withClassName(is(AppCompatEditText.class.getName())))).perform(replaceText(time.format(DateTimeFormatter.ofPattern("mm"))));
}
It's definitely janky in terms of relying very closely on the ids to not change, but it is effective.
If you want to select a date when the InputMode is INPUT_MODE_CALENDAR, then this worked for me:
onView(
// the date you are selecting must be visible for this to work
withContentDescription("Mon, Jan 1, 1990")
).inRoot(RootMatchers.isDialog())
.perform(click())
onView(withId(com.google.android.material.R.id.confirm_button))
.perform(click())
You could expand this answer further to select a different month/day/year by clicking on the appropriate buttons in the Material Date Picker layout. You can go spelunking for the ids in the source code.

calendar setFirstDayOfWeek for entire app

I have a time registration app and some users requested to change to first day of the week according to their preferences. I know I can set the first day of the week on a calendar object, but I'm working with a lot of them and I don't feel like setting the first day on all of them. So is there a way to set the first day for my entire app?
I don't feel like setting the first day on all of them
There's a golden rule on software programming that says "Don't repeat yourself", also known as DRY rule. So you shouldn't write the same code again and again to set the first day of week, but some code still should do it for all of them, so....
On those cases, the common way is to build a factory.
class CalendarFactory {
public static void setFirstDayOfWeek(Context context, int value){
// use context to save value in SharedPreferences
}
public static Calendar newInstance(Context context) {
// read the value from SharedPreferences
// create new GregorianCalendar
// setFirstDayOfWeek
// return
}
}
then it's just make sure to acquire instance of calendar ALWAYS through this method.

Custom DatePicker buttons

I would like to highlight that I am looking for a Xamarin solution. I have found Java solutions, but I can't seem to convert them to Xamarin. I want to have the user set their birthday and then have the app calculate their age, and display it as text. I have everything functioning. All I am looking to do is change the text on the "ok" button to "Calculate age." I am able to change the text, but the button doesn't grab the date from the picker then. This is how I create my datepicker:
DatePickerDialog setDate = new DatePickerDialog(this, onDateSet, date.Year, date.Month-1, date.Day);
then I use this method to change the text
setDate.SetButton("calculate age", EventHandler<DialogClickEventArgs>)
I have created an EventHandler called Age use for the second argument. What code do I put in the EventHandler to make the button function like the "ok" button? if I set handler:null the method works, I can also get the method to do other things, change text in textbox for a example. Any solution is welcomed.
Wow. No love for Xamarin? I discovered the solution was much simpler. Following from what I learned here: Change DatePickerDialog's button texts. I began throwing darts into the code and this is what stuck. Where I set handler to null I simply set listener to the dialog object like so. setDate.SetButton ("calculate age", listener:setDate);

CalendarView Issues when Used Directly (Outside of a DatePicker)

I'm using a CalendarView directly, not a DatePicker, b/c its being used as a drop-down/pop-up style dialog where space is factor ( the user clicks a button located to the right of date field, and the CalendarView drops down, appearing directly below the field, aligned/anchored to the button).
There are two show-stopping issues I've ran into, and spent an entire 10 hour day debugging with no resolve.
Issue #1 - Weeks Missing
Weeks appear to missing, and depending on the date, sometimes the week missing will correspond to the "default"/current date (the date the displayed record contains that was loaded from the DB), and so when I display the CalendarView, and call setDate() to auto-select it, no date will appear to be selected (although the CalendarView will be centered around the missing week).
The way I've been fixing this is to manually scroll down, and then back up a few months, and the refresh usually fixes the display. However, I haven't found a way to automate this scrolling, which could be a potential work around. I tried calling setDate() in succession to do this, but it seems to only work on the first call, which brings me to my next issue.
Issue #2 - setDate() Auto-Scrolling Not Working
It seems that only the first call to setDate() will cause the CalendarView to be centered around the corresponding date. If I choose a new date (and store it in private member) and dismiss the popup, and then bring it back up with another dropdown-button click, which will now call setDate() with this new date, then the CalendarView will be centered around the old/previous date, even though the new date is actually hilited (which can be confirmed my manually scrolling down to it).
I can attach code if required, but before spending the time to do so, I just wanted to see if this is a well known issue.
Thank you.
This is just a work-around (also, I've been working in Monodroid/C#, not raw Android)
By first calling SetDate() with a value for the last day of the month previous to the desired display date, and then calling it with the desired date, it seems to work consistently as expected (make sure this date falls between the Max/Min dates of the CalendarView).
However, the second call needs to be posted as a runnable to the UI thread.
Here is some code (note the boolean flags to SetDate, for centering and scroll-animation, neither of which work, hence this work-around):
View view = _inflater.Inflate( Resource.Layout.CalendarViewPicker, null );
CalendarView cvPicker = view.FindViewById<Android.Widget.CalendarView>( Resource.Id.cv_picker );
DateTime lastDay = new DateTime(_date.Year, _date.Month, 1).AddDays(-1);
long lastDayTicks = Ticks( lastDay.Date );
cvPicker.SetDate( lastDayTicks, true, true );
cvPicker.Post( () => {cvPicker.SetDate(Ticks(_date.Date),true,true)} );
Passing _date.Date to Ticks() passes a DateTime with a Time of 12:00am (basically "zeros out" time element), as opposed to passing just _date, which is also a DateTime. Also, a conversion function is needed, since I'm using C#/.NET, to convert the tick-offset
private long Ticks( DateTime date )
{
DateTime _1970 = (new DateTime(1970, 1, 1)).AddDays(-1);
return (date.Ticks - _1970.Ticks) / 10000;
}
Resource.Layout.CalendarViewPicker.axml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="#dimen/calendar_view_width"
android:layout_height="#dimen/calendar_view_height"
android:background="#FF666666">
<Button
android:id="#+id/btn_picker_exit"
android:layout_width="60dp"
android:layout_height="36dp"
android:layout_alignParentTop="true"
android:background="#drawable/cv_button_background"
android:gravity="center"
android:textColor="#FFFFFFFF"
android:textSize="20sp"
android:text="+"/>
<CalendarView
android:id="#+id/cv_picker"
android:layout_width="#dimen/calendar_view_width"
android:layout_height="#dimen/calendar_view_height"
android:shownWeekCount="6"/>
<RelativeLayout/>
My reasoning for the first call to SetDate() with the last day of the previous month, was b/c many times the calendar would popup with the second week of a given month showing as the first visible week, and when scrolling up to see the first week, I would find it missing. So, I figured setting the date with the last day of the previous month might force the first week (ie, the problematic week) to show.
Now with the first week showing, I call SetDate again, but it only works when posted, b/c when posting a runnable to the UI, the event is scheduled to occur after the associated view is rendered. If 2 SetDate()'s are called consecutively (neither in a runnable), say in OnCreate(), then the effects take place before the view is rendered, and so only the second call would take effect, overwriting the first (and therefore loosing the effect of forcing the first week to show).
These two calls also fix the centering/auto-scrolling issue, which is nice too.

Hours, Minutes selector widget

I am porting one of my iOS apps over to android, and one of the layouts uses the UIDatePicker with the mode UIDatePickerModeCountDownTimer. I have searched and searched and searched for something even close in android, and the only thing I have come across is the TimePicker widget which only seems to do time of day.
Does something like UIDatePickerModeCountDownTimer exist in android, or do I have to fenagle my own solution for entering this type of time.
There is no one stop shop that includes a date picker and a time picker, both are seperate
so basically you need a date field that shows a DatePickerDialog and a time field that show a TimePickerDialog when clicked
As far as I am aware you will either need a 3rd party library or you will need to create a custom version of the TimePicker.
EDIT :
Basically i've done something similar like your requirement, i have a time picker dialog in dialog fragment, without am/pm indicator. You only have to set the DateFormat to 24 hour format in onCreateDialog method. Something similar like this :
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the current time as the default values for the picker
int hour = 0;
int minute = 0;
// Create a new instance of TimePickerDialog without am/pm and return it
return new TimePickerDialog(getActivity(), this, hour, minute,
DateFormat.is24HourFormat(getActivity()));
}
I hope this answer help you :)

Categories

Resources