Pick Date/Time in Xamarin UITest

For the past few weeks, I have been busy writing automated UI tests for a Xamarin.Forms app. At some point, I ran into the limitation that there was no apparent way to pick date and times. This post will provide you with the code to do just that. Possibly this post is more a “note to self” than an actual post…

The Date Picker Control

For our app, we implemented a simple date picker. In Xamarin.Forms, this translates to an input field which, when tapped, shows the platform native control to pick a date. You can see it in action on iOS in the screenshot underneath.

Pick date on iOS
Pick date on iOS

However, because a native control is spawned to let you pick the date (or time for that matter), you do not have any control over how to access that control programmatically. You could come up with all kinds of workarounds like doing something with coordinates, but that feels yucky.

Pick Date/Time Extension Methods

One of the top hits in Google was a GitHub gist by richseviora. By the way, there is a gist for a mean teriyaki sauce on his account as well, but I’m drifting off.

This gist, which you can see embedded underneath, gives you a couple of methods that simply allow you to pick a date and a time. The method signatures for UpdateDatePicker and UpdateTimePicker are quite simple.

Since they are extension methods, you will call them on the IApp object, which represents the app you are running the tests on. The second parameter takes in the date of time you want to select. The last two parameters can be ignored unless you know what you’re doing.

// Tested with Xamarin.UITest V1.3.7;
using System;
using Xamarin.UITest;
namespace {APPNAMEHERE}.UITests
{
/// <summary>
/// This extension class extends the <see cref="IApp"/> interface to add extension methods for selecting values from the default Date/Time pickers in iOS and Android.
/// </summary>
public static class AppPickerExtensions
{
/// <summary>
/// Gets the standard timeout period.
/// </summary>
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
#region Android Configuration
private const string AndroidDatePickerClass = "DatePicker";
private const string AndroidTimePickerClass = "TimePicker";
private const string AndroidDatePickerUpdateDateMethod = "updateDate";
/// <summary>
/// Gets the Android Set Minute method for the TimePicker class. As of API v 23 this is deprecated and should be replaced with "setMinute".
/// </summary>
private const string AndroidTimePickerSetMinuteMethod = "setCurrentMinute";
/// <summary>
/// Gets the Android Set Hour method for the TimePicker class. As of API v 23 this is deprecated and should be replaced with "setHour".
/// </summary>
private const string AndroidTimePickerSetHourMethod = "setCurrentHour";
#endregion
#region iOS Configuration
private const string iOSDatePickerClass = "UIDatePicker";
private const string iOSTableViewClass = "UIPickerTableView";
private const int iOSDateMonthColumn = 0;
private const int iOSDateDayColumn = 3;
private const int iOSDateYearColumn = 6;
private const int iOSTimeHourColumn = 0;
private const int iOSTimeMinuteColumn = 3;
private const int iOSTimePeriodColumn = 6;
#endregion
/// <summary>
/// Updates the presented date picker with the DateTime provided.
/// Does not request or dismiss the picker; you will need to do so yourself.
/// </summary>
/// <param name="app">App.</param>
/// <param name="platform">Platform being tested.</param>
/// <param name="dateTime">Date to be selected. Time component is ignored.</param>
/// <param name="pickerClass">Android picker class if subclassed.</param>
/// <param name="timeout">Timeout for picker scrolling.</param>
public static void UpdateDatePicker(this IApp app, Platform platform, DateTime dateTime, string pickerClass = AndroidDatePickerClass, TimeSpan? timeout = null)
{
if (platform == Platform.Android)
{
app.WaitForElement(x => x.Class(AndroidDatePickerClass), timeout: DefaultTimeout);
// Note that the month provided (4 selects it from a 0-based index, whereas the date and year are both the standard values)
app.Query(x => x.Class(pickerClass).Invoke(AndroidDatePickerUpdateDateMethod, dateTime.Year, dateTime.Month 1, dateTime.Day));
}
else if (platform == Platform.iOS)
{
app.WaitForElement(x => x.Class(iOSDatePickerClass), timeout: DefaultTimeout);
ScrollToPickerColumn(app, iOSDateMonthColumn, dateTime.ToString("MMM"), timeout);
ScrollToPickerColumn(app, iOSDateDayColumn, dateTime.ToString("d"), timeout);
ScrollToPickerColumn(app, iOSDateYearColumn, dateTime.ToString("yyyy"), timeout);
}
}
/// <summary>
/// Updates the presented time picker with the time provided.
/// Does not request or dismiss the picker; you will need to do so yourself.
/// </summary>
/// <param name="app">App.</param>
/// <param name="platform">Platform being tested.</param>
/// <param name="time">Time to be selected. Date component is ignored.</param>
/// <param name="pickerClass">Android picker class if subclassed.</param>
/// <param name="timeout">Timeout for picker scrolling.</param>
public static void UpdateTimePicker(this IApp app, Platform platform, DateTime time, string pickerClass = AndroidTimePickerClass, TimeSpan? timeout = null)
{
var hours = time.Hour;
var minute = time.Minute;
if (platform == Platform.Android)
{
app.WaitForElement(x => x.Class(AndroidTimePickerClass), timeout: DefaultTimeout);
app.Query(c => c.Class(pickerClass).Invoke(AndroidTimePickerSetHourMethod, hours));
app.Query(c => c.Class(pickerClass).Invoke(AndroidTimePickerSetMinuteMethod, minute));
}
else if (platform == Platform.iOS)
{
// Assumes 12 Hour Clock
app.WaitForElement(x => x.Class(iOSDatePickerClass), timeout: DefaultTimeout);
var period = time.Hour >= 12 ? "PM" : "AM";
ScrollToPickerColumn(app, iOSTimeHourColumn, time.ToString("h"), timeout);
ScrollToPickerColumn(app, iOSTimeMinuteColumn, time.ToString("m"), timeout);
ScrollToPickerColumn(app, iOSTimePeriodColumn, period, timeout);
}
}
/// <summary>
/// Scrolls to the designated picker column (for iOS only).
/// </summary>
/// <param name="app">App instance.</param>
/// <param name="columnIndex">Column index to scroll to.</param>
/// <param name="marked">Marked content to scroll to.</param>
/// <param name="timeout">Timeout for scrolling.</param>
private static void ScrollToPickerColumn(IApp app, int columnIndex, string marked, TimeSpan? timeout = null)
{
timeout = timeout ?? DefaultTimeout;
app.ScrollDownTo(z => z.Marked(marked), x => x.Class(iOSTableViewClass).Index(columnIndex), timeout: timeout, strategy: ScrollStrategy.Auto);
app.Tap(x => x.Text(marked));
}
}
}

Summary

You will probably just want to take this code and copy/paste it into your project and use it for the better. I would recommend to read through it and try to understand what is going on here. It might benefit you later on if you need to fix something, or maybe another scenario comes up where you have to do something similar.

If you want to know more about Xamarin UITest, please let me know, I’ll be happy to write a post about it. Or maybe you want to incorporate it into your buildpipeline, you could have a great start reading this post here. Although the post is quite old, you will get the idea.

4 thoughts on “Pick Date/Time in Xamarin UITest”

  1. Hello ,
    Thank you for all the contribution to the community. I want to get started with Xamarin.UITests. It will be very helpful if you can post on that. Integrating test, writing test and testing on device. Complete tutorial. May be on simple Page but complete procedure.

Comments are closed.