TestStack / White

DEPRECATED - no longer actively maintained
Other
1.02k stars 486 forks source link

TestStack.White TreeNode.Click() and TreeNode.Select() for leaf node does not work #672

Open jeffhuckins opened 2 years ago

jeffhuckins commented 2 years ago

I have a WPF window containing a TreeView. I am able to automate traversing the tree and can successfully select or click (either one) nodes with children, but it doesn't work for leaf nodes.

jeffhuckins commented 2 years ago

Update: I've discovered an issue with TestStack.White for TreeNodes

First of all, the Tree.node() method has never worked for our testing. It was necessary to implement our own traversal code:

` private TreeNode FindNode(TreeNode node, string sVal, bool bExpandNode) { LogEntry.Debug(log, "Entering"); TreeNode result = null;

        foreach (TreeNode tmpNode in node.Nodes)
        {
            LogEntry.Debug(log, $"Found node \"{FfiTreeNode.Text(tmpNode)}\"");

            if (FfiTreeNode.Text(tmpNode).Contains(sVal))
            {
                LogEntry.Debug(log, $"Found target node \"{sVal}\"");
                result = tmpNode;

                if (bExpandNode)
                {
                    FfiTreeNode.Expand(result, bExpandNode);
                }

                FfiTreeNode.Select(result);
                break;
            }

            Thread.Sleep(500);
        }

        LogEntry.Debug(log, "Leaving");
        return result;
    }

    public TreeNode SelectAppearanceItem(params string[] path)
    {
        LogEntry.Debug(log, "Entering");
        Tree treeAppearance = window.Get<Tree>(SearchCriteria.ByControlType(ControlType.Tree).AndAutomationId("treeViewAppearance"));
        TreeNode resultNode = null;

        if (treeAppearance != null)
        {
            resultNode = treeAppearance.Nodes[0];
            LogEntry.Debug(log, $"First node = \"{FfiTreeNode.Text(resultNode)}\"");

            for (int i = 0; i < path.Length; i++)
            {
                resultNode = FindNode(resultNode, path[i], i < path.Length - 1);

                if (resultNode == null)
                {
                    LogEntry.Debug(log, $"Appearance Tree Item, {path[i]}, not found!");
                    break;
                }
            }
        }

        LogEntry.Debug(log, "Leaving");
        return resultNode;
    }

`

I also had to create a static wrapper class in order to wrap calls to TreeNode action methods in try/catch due to erroneous Exceptions being thrown:

`namespace FormFactor.WinCal.Utilities { ///

/// Robust TreeNode /// internal static class FfiTreeNode { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Click a TreeNode
    /// </summary>
    /// <param name="node"></param>
    public static void Select(TreeNode node)
    {
        LogEntry.Debug(log, $"Entering - selecting \"{Text(node)}\"");
        try
        {
            Mouse.Instance.Location = node.ClickablePoint;
            Mouse.Instance.Click();
        }
        catch { }
        LogEntry.Debug(log, $"Leaving - selecting \"{Text(node)}\"");
    }

    /// <summary>
    /// The DataView treeview items have custom headers with images, so checking TreeNode.Text property doesn't work
    /// </summary>
    /// <param name="node"></param>
    public static string Text(TreeNode node)
    {
        return node.HelpText;
    }

    /// <summary>
    /// Expand a node, if desired
    /// </summary>
    /// <param name="node"></param>
    /// <param name="expand"></param>
    public static void Expand(TreeNode node, bool expand)
    {
        LogEntry.Debug(log, $"Entering - Expanding \"{Text(node)}\"");
        try
        {
            if (node.Nodes.Count > 0)
            {
                node.Expand();
            }
        }
        catch { }
        LogEntry.Debug(log, $"Leaving - Expanding \"{Text(node)}\"");
    }
}

/// <summary>
/// Robust Menu
/// </summary>
internal static class FfiMenu
{
    private static readonly log4net.ILog log =
        log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Click menu
    /// </summary>
    /// <param name="menu"></param>
    /// <param name="path"></param>
    public static void Click(MenuBar menu, params string[] path)
    {
        LogEntry.Debug(log, "Entering");
        try
        {
            menu.MenuItem(path).Click();
        }
        catch(Exception e) 
        {
            string menuItems = string.Empty;

            for (int i = 0; i < path.Length; i++)
            {
                menuItems += $"\"{path[i]}\"";

                if (i < path.Length - 1)
                {
                    menuItems += ",";
                }
            }

            LogEntry.Debug(log, $"Menu Exception {menuItems}: {e.Message}");
        }

        LogEntry.Debug(log, "Entering");
    }
}

/// <summary>
/// Robust UiItem
/// </summary>
internal static class FfiUiItem
{
    private static readonly log4net.ILog log =
        log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Catch erroneous exceptions
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    public static bool Enabled(UIItem item)
    {
        bool isEnabled = false;

        try
        {
            isEnabled = item.Enabled;
        }
        catch { }

        return isEnabled;
    }

    /// <summary>
    /// Click a UIItem
    /// </summary>
    /// <param name="item"></param>
    public static void Click(UIItem item)
    {
        try
        {
            item.Click();
        }
        catch { }
    }
}

/// <summary>
/// A robust button that uses exception handling because TestStack.White button clicks often erroneously cause exceptions
/// </summary>
internal static class FfiButton
{
    private static readonly log4net.ILog log =
        log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Click() to catch erroneous exceptions
    /// </summary>
    public static void Click(Button button)
    {
        try
        {
            button.Click();
        }
        catch { }
    }
}

/// <summary>
/// Robust RadioButton
/// </summary>
internal static class FfiRadioButton
{
    private static readonly log4net.ILog log =
        log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Click() to catch erroneous exceptions
    /// </summary>
    public static void Select(RadioButton button)
    {
        try
        {
            button.Select();
        }
        catch { }
        LogEntry.Debug(log, $"Selected '{button.Id}'");
    }

    /// <summary>
    /// IsSelected catch erroneous exceptions
    /// </summary>
    /// <param name="button"></param>
    /// <returns></returns>
    public static bool IsSelected(RadioButton button)
    {
        bool isSelected = false;

        try
        {
            isSelected = button.IsSelected;
        }
        catch { }

        return isSelected;
    }
}

/// <summary>
/// Robust ListBox
/// </summary>
internal static class FfiListBox
{
    private static readonly log4net.ILog log =
        log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Select() to catch erroneous exceptions
    /// </summary>
    /// <param name="listBox"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static ListBox Select(ListBox listBox, string value)
    {
        try
        {
            listBox.Select(value);
        }
        catch { }

        LogEntry.Debug(log, $"Selected '{value}' in '{listBox.Id}'");
        return listBox;
    }

    /// <summary>
    /// Select by index
    /// </summary>
    /// <param name="listBox"></param>
    /// <param name="idx"></param>
    /// <returns></returns>
    public static ListBox Select(ListBox listBox, int idx)
    {
        try
        {
            listBox.Select(idx);
        }
        catch { }

        LogEntry.Debug(log, $"Selected '{idx}' in '{listBox.Id}'");
        return listBox;
    }

    /// <summary>
    /// Double-Click the selected item - ONE OF THE ABOVE Select() methods MUST BE CALLED BEFORE THIS
    /// </summary>
    /// <param name="listBox"></param>
    public static void DoubleClick(ListBox listBox)
    {
        try
        {
            listBox.SelectedItem.DoubleClick();
        }
        catch { }
    }
}

/// <summary>
/// Robust combo box to handle erroneous exceptions from Select()
/// </summary>
internal static class FfiComboBox
{
    private static readonly log4net.ILog log =
        log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Select() to catch erroneous exceptions
    /// </summary>
    /// <param name="combo"></param>
    /// <param name="value"></param>
    public static ComboBox Select(ComboBox combo, string value)
    {
        try
        {
            combo.Select(value);
        }
        catch { }

        LogEntry.Debug(log, $"Selected '{value}' in '{combo.Id}'");
        return combo;
    }
}

/// <summary>
/// Robust Tab
/// </summary>
internal static class FfiTab
{
    private static readonly log4net.ILog log =
        log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

    /// <summary>
    /// Select() to catch erroneous exceptions
    /// </summary>
    /// <param name="combo"></param>
    /// <param name="value"></param>
    public static Tab SelectTabPage(Tab tab, string value)
    {
        try
        {
            tab.SelectTabPage(value);
        }
        catch { }

        LogEntry.Debug(log, $"Selected '{value}' in '{tab.Id}'");
        return tab;
    }

    /// <summary>
    /// Select() to catch erroneous exceptions
    /// </summary>
    /// <param name="combo"></param>
    /// <param name="value"></param>
    public static Tab SelectTabPage(Tab tab, int value)
    {
        try
        {
            tab.SelectTabPage(value);
        }
        catch { }

        LogEntry.Debug(log, $"Selected '{value}' in '{tab.Id}'");
        return tab;
    }
}

/// <summary>
/// Robust CheckBox
/// </summary>
internal static class FfiCheckBox
{
    /// <summary>
    /// Check or uncheck
    /// </summary>
    /// <param name="checkBox"></param>
    /// <param name="bCheck"></param>
    public static void SetCheck(CheckBox checkBox, bool bCheck)
    {
        try
        {
            if (bCheck)
            {
                Check(checkBox);
            }
            else
            {
                UnCheck(checkBox);
            }
        }
        catch { }
    }

    /// <summary>
    /// Check the checkbox
    /// </summary>
    /// <param name="checkBox"></param>
    public static void Check(CheckBox checkBox)
    {
        try
        {
            checkBox.Checked = true;
        }
        catch { }
    }

    /// <summary>
    /// UnCheck the checktox
    /// </summary>
    /// <param name="checkBox"></param>
    public static void UnCheck(CheckBox checkBox)
    {
        try
        {
            checkBox.Checked = false;
        }
        catch { }
    }
}

} `

Finally, TreeNode.Click() and TreeNode.Select() do not work to completely traverse a Tree:

The only method that PARTIALLY works is: Mouse.Instance.Location = node.ClickablePoint; Mouse.Instance.Click(); However, I discovered that this method doesn't work to click ALL child nodes of a node because TestStack.White clicks outside of the TreeNode header except for the child node that is widest.