locked
Coded UI Tests: How do I get a strongly typed WpfList control collection?

    Question

  • How do I get a strongly typed WpfList control collection?

    Our application has a WPF ListBox that generates ListItems from a data source according to a Data Template. If I view the ListBox in the Coded UI Test Builder, I see a List, ListItems, and one custom child control per ListItem. I have modified the XAML for the custom control to bind the AutomationId to a unique identifier, so that's not a problem.

    I don't always know which control I want (which I could access by Automation Id). Sometimes I need to access them by index or as an entire collection. However, the generated WpfList class's Items property indexes the ListItems, not the custom child controls.

    What's the best way to get at the child control in each ListItem and still use it like a control collection (position and name indexing, IEnumerable<T> implementation, etc.)?
    Monday, January 18, 2010 5:00 PM

Answers

  • I've managed to cobble together a poor solution...

    The first thing I did was create this extension to get the single custom child control from a WpfListItem, but due to limitations of the WpfList Items property , the extension accepts all UITestControls.

        public static T GetChildControl<T>(this UITestControl parent)
            where T : WpfCustom
        {
            return (T)Activator.CreateInstance(typeof(T), parent);
        }

    I created a new control collection class, this one will extract the custom control from any list item and provide indexers.

        public class WpfCustomCollection<T>
            where T : WpfCustom
        {
            private readonly WpfList parent;

            public virtual T this[int index]
            {
                get
                {
                    var listItemParent = parent.Items[index];
                    return listItemParent.GetChildControl<T>();
                }
            }

            public virtual T this[string automationId]
            {
                get
                {
                    var child = parent.GetChildControl<T>();
                    child.SearchProperties[WpfProperties.Common.AutomationId] = automationId;
                    return child;
                }
            }

            public virtual int Count
            {
                get { return parent.Items.Count; }
            }

            public WpfCustomCollection(WpfList parent)
            {
                this.parent = parent;
            }

            public virtual List<T> ToList()
            {
                return parent.Items.ToList().ConvertAll(x => x.GetChildControl<T>());
            }
        }

    There is no caching, so every access to the indexers or ToList() will produce another copy of the custom control(s). I could add caching (an internal List<T>) but the performance is acceptable for now. I'm only ever asking for one list item (by indexer) or getting the list once or twice per test (by ToList()).

    The final touch was an extended WpfList class that takes a custom control type and provides a new WpfCustomCollection<T> property.

        public class WpfCustomList<T> : WpfList
            where T : WpfCustom
        {
            public virtual WpfCustomCollection<T> Controls { get; private set; }

            public WpfCustomList()
                : this(null)
            {

            }

            public WpfCustomList(UITestControl parent)
                : base(parent)
            {
                Controls = new WpfCustomCollection<T>(this);
            }
        }

    The use would look like this... I have a ListBox that contains CaseListItems. A CaseListItem is a WpfCustom control that contains just a few textboxes and buttons.

        public class CaseListBox : WpfCustomList<CaseListItem>
        {
            public CaseListBox(UITestControl parent)
                : base(parent)
            {

            }
        }

    And a test...

        [TestMethod]
        public void CanExpandFirstCaseItem()
        {
            var expandButton = window.CaseListBox.Controls[0].ExpandButton;
            Mouse.Click(expandButton);
            Assert.IsTrue(expandButton.Pressed);
        }

        [TestMethod]
        public void DisplayedCaseIdMatchesAutomationId()
        {
            var id = "DEMO1";
            var item = window.CaseListBox.Controls[id];
            Assert.AreEqual(id, item.CaseIdText.DisplayText);
        }
    Wednesday, January 20, 2010 4:36 PM

All replies

  • Hi,

    You can do something like this :-
    WpfCustom c = new WpfCustom(listBox);
    // You will get all the Custom Control inside the listbox.
    UITestControlCollection collection = c.FindMatchingControls();
    

    You can even specify search properties for these if you know about them.
    Hope this helps

    Thanks
    Siddhartha_
    Monday, January 18, 2010 5:52 PM
  • What I want is some new control collection that exposes the items in the ListBox as an ICollection<MyCustomControl>. (This is the way I see it. There may be another way. See the examples below.) Consider the following class as my custom child control.

        public class CaseListItem : WpfCustom
        {
            ctor(UITestControl searchLimitContainer) { //set search class name }
            public WpfText Id { //set search auto id }
            public WpfText Name { ... }
            public WpfToggleButton Star { ... }
            public WpfToggleButton Expand { ... }
        }

    For certain tests, I want to access a custom control by position.

        var starButton = window.CaseList.Items[0].Star;
        Mouse.Click(starButton);

    For others, it's a matter of accessing all the custom controls as a collection.

        var expandAllButton = window.ToolsPane.ExpandAllButton;
        Mouse.Click(expandAllButton);
        foreach(var item in window.CaseList.Items)
            Assert.IsTrue(item.Expand.Pressed);

    I can't cast the ListItem's child control (a UITestControl) to CaseListItem. The shortest way I know of to get a CaseListItem instance from its parent is

        var parent = window.CaseList.Items[index];
        var child = new CaseListItem(parent);

    And I'm not comfortable getting my custom control collection using the following code snippet (nor do I know how it will work due to all the lazy loading). It has a code smell.

       var customItemCollection = window.CaseList.Items.ToList().ConvertAll(x => new CaseListItem(x));
    Monday, January 18, 2010 7:09 PM
  • I've managed to cobble together a poor solution...

    The first thing I did was create this extension to get the single custom child control from a WpfListItem, but due to limitations of the WpfList Items property , the extension accepts all UITestControls.

        public static T GetChildControl<T>(this UITestControl parent)
            where T : WpfCustom
        {
            return (T)Activator.CreateInstance(typeof(T), parent);
        }

    I created a new control collection class, this one will extract the custom control from any list item and provide indexers.

        public class WpfCustomCollection<T>
            where T : WpfCustom
        {
            private readonly WpfList parent;

            public virtual T this[int index]
            {
                get
                {
                    var listItemParent = parent.Items[index];
                    return listItemParent.GetChildControl<T>();
                }
            }

            public virtual T this[string automationId]
            {
                get
                {
                    var child = parent.GetChildControl<T>();
                    child.SearchProperties[WpfProperties.Common.AutomationId] = automationId;
                    return child;
                }
            }

            public virtual int Count
            {
                get { return parent.Items.Count; }
            }

            public WpfCustomCollection(WpfList parent)
            {
                this.parent = parent;
            }

            public virtual List<T> ToList()
            {
                return parent.Items.ToList().ConvertAll(x => x.GetChildControl<T>());
            }
        }

    There is no caching, so every access to the indexers or ToList() will produce another copy of the custom control(s). I could add caching (an internal List<T>) but the performance is acceptable for now. I'm only ever asking for one list item (by indexer) or getting the list once or twice per test (by ToList()).

    The final touch was an extended WpfList class that takes a custom control type and provides a new WpfCustomCollection<T> property.

        public class WpfCustomList<T> : WpfList
            where T : WpfCustom
        {
            public virtual WpfCustomCollection<T> Controls { get; private set; }

            public WpfCustomList()
                : this(null)
            {

            }

            public WpfCustomList(UITestControl parent)
                : base(parent)
            {
                Controls = new WpfCustomCollection<T>(this);
            }
        }

    The use would look like this... I have a ListBox that contains CaseListItems. A CaseListItem is a WpfCustom control that contains just a few textboxes and buttons.

        public class CaseListBox : WpfCustomList<CaseListItem>
        {
            public CaseListBox(UITestControl parent)
                : base(parent)
            {

            }
        }

    And a test...

        [TestMethod]
        public void CanExpandFirstCaseItem()
        {
            var expandButton = window.CaseListBox.Controls[0].ExpandButton;
            Mouse.Click(expandButton);
            Assert.IsTrue(expandButton.Pressed);
        }

        [TestMethod]
        public void DisplayedCaseIdMatchesAutomationId()
        {
            var id = "DEMO1";
            var item = window.CaseListBox.Controls[id];
            Assert.AreEqual(id, item.CaseIdText.DisplayText);
        }
    Wednesday, January 20, 2010 4:36 PM