Select 使用 Xamarin UITest 的 iOS 选择器中的项目?

Select an item in an iOS Picker using Xamarin UITest?

我们正在使用 UITest 测试我们的 Xamarin.Forms 应用程序,并且需要 select iOS 上的选择器的值。

可以 select 按 collection 项中的行号,如下所示:

app.Query(x => x.Class("UIPickerView").Invoke("selectRow", 1, "inComponent", 0, "animated", true))

我们在上面的示例中 select 第 1 行。

但是,我们需要能够 select 按值 - 例如,使用具有标题列表的选择器,select "Mrs" 标题,我们需要做类似的事情:

app.Query(x => x.Class("UIPickerView").Invoke("selectValue", "Mrs", "inComponent", 0, "animated", 真))

但是,UIPickerView 上没有 selectValue(或类似)方法(参考 here)。

一个想法是获取项目中的行数,并遍历它们 - 但是当我尝试调用 getNumberOfRows 时,出现以下错误:

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])

错误的原因很清楚 - 该方法需要一对特定的参数,但我看不出如何正确地制定查询。

那么 thoughts/pointers 如何 select 按值而不是行号 and/or 如何获取项目数?

好的...我有一些有用的东西。

前提是我们在底层列表中select行,然后检查Picker是否显示了我们想要的值。

注释 1. 我有一个 UIElement 助手 class,它允许我获取底层 Control 或 AutomationId。

  1. 使用以下两种方法,以便我可以直接从我的 TestPages 调用 SelectPickerValue 或使用 TapAndSelectPickerValue - 我应该整理一下。

  2. 如果当前值在可用值的中间,我还没有考虑到向另一方向滚动的能力。

  3. 如果项目在视图中,则可以轻按它 - 但如果不在视图中,我们必须执行 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));
            }
        }

更好的方案是写一个UITest后门,然后在后门中找到Picker的Renderer,直接控制它。

解决方案的核心由 Xamarin 论坛上的@LandLu 提供给我:

您应该先获取当前视图控制器。然后迭代子视图以获得您的选择器渲染器。 这是我为您提供的依赖服务:

[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;
        }
    }
}

完成后,我可以通过渲染器的元素 属性 select 我想要的 index/item。

编辑 我已经把它变成了一个 GitHub repository,它实现了一组后门,使 iOS Picker selection 更容易

从 Xamarin.UITest v3.2.2 iOS 15 开始,我注意到您可以在 selectRow Invoke 方法中插入一个字符串:

app.Query(x => x.Class("UIPickerView").Invoke("selectRow", "[ITEM_NAME]", "inComponent", 0, "animated", false));

[ITEM_NAME]替换为一行的确切字符串。您仍然需要通过轻推 Picker (DragCoordinates) 来触发更改委托来解决问题,如 :

所示
// 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);

您可以在这个官方文档中找到 DatePickers 和 TimePickers 的最新代码:https://docs.microsoft.com/en-us/appcenter/test-cloud/frameworks/uitest/features/date-time-pickers