We're testing our Xamarin.Forms app using UITest, and need to select the value of a Picker on iOS.
It's possible to select by the row number in the collection of items like this:
app.Query(x => x.Class("UIPickerView").Invoke("selectRow", 1, "inComponent", 0, "animated", true))
where we're selecting row 1 in the above example.
However, we need to be able to select by value - so for example, with a Picker which has a list of titles, to select the "Mrs" title, we need to do something like:
app.Query(x => x.Class("UIPickerView").Invoke("selectValue", "Mrs", "inComponent", 0, "animated", true))
However, there isn't a selectValue (or similar) method available on the UIPickerView (reference here).
One thought is to get the number of rows in the items, and iterate through them - but when I try to call getNumberOfRows
, I get the following error:
app.Query(x => x.Class("UIPickerView").Invoke("numberOfRows", "inComponent", 0))
Error while performing Query([unknown]) Exception: System.Exception: Invoking an iOS selector requires either 0 or an uneven number of arguments (they have to match up pairwise including method name). at Xamarin.UITest.Queries.InvokeHelper.AppTypedSelector (Xamarin.UITest.Queries.AppQuery appQuery, Xamarin.UITest.Queries.ITokenContainer tokenContainer, System.Object[] queryParams, System.String methodName, System.Object[] arguments, System.Boolean explicitlyRequestedValue) [0x0011a] in <2a16c16730a54859bda72c6bc1c728f7>:0 at Xamarin.UITest.Queries.InvokeHelper.Invoke (Xamarin.UITest.Queries.AppQuery appQuery, System.String methodName, System.Object[] arguments) [0x00000] in <2a16c16730a54859bda72c6bc1c728f7>:0 at Xamarin.UITest.Queries.AppQuery.Invoke (System.String methodName, System.Object arg1, System.Object arg2) [0x00000] in <2a16c16730a54859bda72c6bc1c728f7>:0 at .m__0 (Xamarin.UITest.Queries.AppQuery x) [0x0000b] in <0de9804cff324d049415e25573e8da8a>:0 at Xamarin.UITest.SharedApp.Expand[T] (System.Func
2[T,TResult] typedQuery) [0x0000c] in <2a16c16730a54859bda72c6bc1c728f7>:0 at Xamarin.UITest.iOS.iOSApp+<>c__DisplayClass17_0
1[T].b__0 () [0x00000] in <2a16c16730a54859bda72c6bc1c728f7>:0 at Xamarin.UITest.Utils.ErrorReporting.With[T] (System.Func`1[TResult] func, System.Object[] args, System.String memberName) [0x0000e] in <2a16c16730a54859bda72c6bc1c728f7>:0 Exception: Error while performing Query([unknown])
The cause of the error is quite clear - the method is expecting a particular pairing of parameters, but I can't see how to formulate the query correctly.
So any thoughts/pointers on how I can select by value, not row number, and/or how to get the number of items?
A better solution is to write a UITest backdoor, and then in the backdoor find the Renderer for the Picker and control it directly.
The core to the solution was provided to me by @LandLu on the Xamarin forums:
You should obtain the current view controller first. Then iterate the subview to get your picker renderer. Here is my dependency service for you referring to:
[assembly: Dependency(typeof(FindViewClass))]
namespace Demo.iOS
{
public class FindViewClass : IFindView
{
public void FindViewOfClass()
{
UIViewController currentViewController = topViewController();
getView(currentViewController.View);
}
List<PickerRenderer> pickerList = new List<PickerRenderer>();
void getView(UIView view)
{
if (view is PickerRenderer)
{
pickerList.Add((PickerRenderer)view);
}
else
{
foreach (UIView subView in view.Subviews)
{
getView(subView);
}
}
}
UIViewController topViewController()
{
return topViewControllerWithRootViewController(UIApplication.SharedApplication.KeyWindow.RootViewController);
}
UIViewController topViewControllerWithRootViewController(UIViewController rootViewController)
{
if (rootViewController is UITabBarController)
{
UITabBarController tabbarController = (UITabBarController)rootViewController;
return topViewControllerWithRootViewController(tabbarController.SelectedViewController);
}
else if (rootViewController is UINavigationController)
{
UINavigationController navigationController = (UINavigationController)rootViewController;
return topViewControllerWithRootViewController(navigationController.VisibleViewController);
}
else if (rootViewController.PresentedViewController != null)
{
UIViewController presentedViewController = rootViewController.PresentedViewController;
return topViewControllerWithRootViewController(presentedViewController);
}
return rootViewController;
}
}
}
Once I've done that, I can select the index/item I want via the Element property of the Renderer.
Edit I've worked this up into a GitHub repository which implements a set of Backdoors to make iOS Picker selection easier