iospickerxamarin.uitest

Select an item in an iOS Picker using Xamarin UITest?


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.Func2[T,TResult] typedQuery) [0x0000c] in <2a16c16730a54859bda72c6bc1c728f7>:0 at Xamarin.UITest.iOS.iOSApp+<>c__DisplayClass17_01[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?


Solution

  • 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