UITest - selecting an item by value in an iOS Picker

JamesLaveryJamesLavery GBBeta, University ✭✭✭✭✭

(Posting here because UITest forum now closed - I've also put a question to the support team in App Center)

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?

Tagged:

Answers

  • JamesLaveryJamesLavery GBBeta, University ✭✭✭✭✭

    Ok... I've got something which works.

    The premise is that we select rows in the underlying list of items, and then check whether the Picker is displaying the value we want.

    Notes
    1. I've got a UIElement helper class which allows me to get at either the underlying Control or AutomationId.
    2. The two methods below are used so that I can call SelectPickerValue directly from my TestPages or use TapAndSelectPickerValue - I should probably tidy this up.
    3. I haven't catered for the ability to scroll the other way if the current value is in the middle of available values.
    4. If the item is in view, it's possible to just Tap it - but if it's not we have to do scrolling/row selection.

           internal static void TapAndSelectPickerValue(UIElement element, string v, int maxRows = 10)
            {
                app.Tap(element.UIControl);
                SelectPickerValue(v, maxRows, element.AutomationId);
            }
    
            internal static void SelectPickerValue(string v, int maxItems = 10,string pickerId = "")
            {
                if (_platform == Platform.iOS)
                {
                    bool managedToSelect = false;
    
                    // Check that we haven't already got the value we want selected.
                    Xamarin.UITest.Queries.AppResult[] valueCheckResult = app.Query(x => x.Text(v).Id(pickerId));
    
                    if (valueCheckResult.Length == 0)
                    {
                        int rowNumber = 0;
    
                        // Select rows until we either reach the max to try, or we get the one we want selected
                        for (rowNumber = 0; rowNumber < maxItems; rowNumber++)
                        {
                            app.Query(x => x.Class("UIPickerView").Invoke("selectRow", rowNumber, "inComponent", 0, "animated", true));
    
                            // This is a hack to move the item so that Xamarin Forms will generate the event to update the UI
                            var rect = app.Query(x => x.Class("UIPickerTableView").Index(0).Child()).First().Rect;
                            app.DragCoordinates(rect.CenterX, rect.CenterY, rect.CenterX, rect.CenterY + 22);
                            app.DragCoordinates(rect.CenterX, rect.CenterY, rect.CenterX, rect.CenterY - 22);
    
                            // check to see if the Picker is now displaying the value we want
                            valueCheckResult = app.Query(x => x.Text(v).Id(pickerId));
                            if (valueCheckResult.Length == 1)
                            {
                                managedToSelect = true;
                                break;
                            }
    
                        }
                    }
    
                    // If we couldn't select the value we wanted, raise an exception
                    if (!managedToSelect)
                    {
                        throw new Exception($"Could not select value '{v}' for picker '{pickerId}'");
                    }
    
                    // Close the Picker
                    app.Tap(c => c.Text("Done"));
                }
                else
                {
                    app.ScrollDownTo(m => m.Text(v), x => x.Id("select_dialog_listview"));
                    app.Tap(x => x.Text(v));
                }
            }
    
Sign In or Register to comment.