TestStack White performance slow and speed up - c#

I am using TestStack White to automate a test scenario in a Telerik Winforms application.
The application has plenty of elements and if I directly search for an element, the run would just stale and never end.
So I did the manual hierarchy search to dig into element levels to save the performance and make it work.
Then the code looks in a not neat way as I heavily use foreach to do the loop myself.
Is this the proper way to cope with the performance or we have better ways to
have both neat code and good performance when using TestStack White?
Cheers:
[TestMethod]
public void TestMethod1()
{
CoreAppXmlConfiguration.Instance.RawElementBasedSearch = true;
CoreAppXmlConfiguration.Instance.MaxElementSearchDepth = 2;
var applicationDirectory = "D:\\Software\\ABC Handling System";
var applicationPath = Path.Combine(applicationDirectory, "ABCClient.GUI.exe");
Application application = Application.Launch(applicationPath);
Thread.Sleep(5000);
Window window = application.GetWindow("[AB2] - ABC Handling System 2 - [Entity Search]", InitializeOption.NoCache);
ListBox listbox = window.Get<ListBox>();
ListItem dispatchButton = listbox.Items.Find(i=>i.Name.Equals("Display"));
dispatchButton.Click();
Thread.Sleep(5000);
SearchCriteria sc = SearchCriteria.ByControlType(ControlType.Pane).AndIndex(3);
UIItemContainer groupbox = (UIItemContainer)window.MdiChild(sc);
UIItemContainer pane1 = null;
foreach (IUIItem automationElement in groupbox.Items)
{
if (automationElement.Name == "radSplitContainer1")
{
pane1 = (UIItemContainer)automationElement;
break;
}
}
UIItemContainer pane2 = null;
foreach (IUIItem automationElement in pane1.Items)
{
if (automationElement.Name == "splitPanel1")
{
pane2 = (UIItemContainer)automationElement;
break;
}
}
Table table = null;
foreach (IUIItem automationElement in pane2.Items)
{
if (automationElement.Name == "Telerik.WinControls.UI.RadGridView ; 247;14")
{
table = (Table)automationElement;
break;
}
}
TableRow row = table.Rows[9];
string s = row.ToString();
}
Update on 05/11/2019:
It turned out TestStack White is not the best solution for my multi-row grid app regarding its performance.
Actually we managed to have developed something from the grid side by developers, and we hook those functions up. So we were using AutoIt + Customized Application side hooks. Some really good controls there.
Yes, we succeeded in this approach.

Yeah instead of doing the looping yourself you would just use the Get on each of the retrieved elements. Oddly when I dig down through the code it is using AutomationElement.FindFromPoint which I am not sure how it traverses the tree to find it's elements. I would give the follow code a try and see if it is more or less performant in your application.
CoreAppXmlConfiguration.Instance.RawElementBasedSearch = true;
CoreAppXmlConfiguration.Instance.MaxElementSearchDepth = 2;
var applicationDirectory = "D:\\Software\\ABC Handling System";
var applicationPath = Path.Combine(applicationDirectory, "ABCClient.GUI.exe");
Application application = Application.Launch(applicationPath);
Thread.Sleep(5000);
Window window = application.GetWindow("[AB2] - ABC Handling System 2 - [Entity Search]", InitializeOption.NoCache);
ListBox listbox = window.Get<ListBox>();
ListItem dispatchButton = listbox.Get<ListItem>(SearchCriteria.ByText("Display"));
dispatchButton.Click();
Thread.Sleep(5000);
UIItemContainer groupbox = window.MdiChild(SearchCriteria.ByControlType(ControlType.Pane).AndIndex(3));
UIItemContainer pane1 = groupbox.Get<UIItemContainer>(SearchCriteria.ByText("radSplitContainer1"));
UIItemContainer pane2 = pane1.Get<UIItemContainer>(SearchCriteria.ByText("splitPanel1"));
Table table = pane2.Get<Table>(SearchCriteria.ByText("Telerik.WinControls.UI.RadGridView ; 247;14")); ;
TableRow row = table.Rows[9];
string s = row.ToString();
I am getting everything from the previous element in an attempt to limit the portion of the tree it is scanning. From the looks of White's code this may have no affect other than reducing the amount of boiler plate on the front end depending on the performance of AutomationElement.FindFromPoint.
Also the ByText search criteria maps to the name property on a AutomationElement by calling CreateForName which is why I replaced all the checks against name with ByText.

Related

Dynamically Generate Groupboxes

I'm working on an inventory program and have finished the main functionality as a command line console app. I am now working on a version for winforms. I want to enable it to dynamically generate a Groupbox that holds some textboxes. I'd rather not design 50+ lines of multiple textboxes. Keep in mind I'm rather new to programming, having started with C# a year ago. I know next to nothing on Winforms.
I've tried to use dynamic item = new Groupbox();as a similar method allowed generation of objects at runtime. In the command line app, the way it works is that based on information given, a certain amount of objects are passed into the list _AllItems. I was thinking of generating the Groupboxes by using:
private void InitializeGroupBox()
{
foreach (Product product in Product._AllItems)
{
dynamic Item = new GroupBox();
}
}
But I have the feeling I'm nowhere near the correct method. Thanks to anybody who helps.
You will need to learn a bit more, but here is what I usually do to achieve what you asked.
internal class DynamicForm : Form
{
private FlowLayoutPanel mFlowLayoutPanel;
public DynamicForm()
{
mFlowLayoutPanel = new FlowLayoutPanel();
mFlowLayoutPanel.Dock = DockStyle.Fill;
// Add to this Form
this.Controls.Add(mFlowLayoutPanel);
InitializeGroupBox();
}
private void InitializeGroupBox()
{
mFlowLayoutPanel.SuspendLayout(); // Performance
for (int i = 1; i <= 20; i++) {
var groupBox = new GroupBox();
groupBox.Text = "GroupBox #" + i;
groupBox.Size = new Size(200, 50);
var textBox = new TextBox();
textBox.Dock = DockStyle.Fill;
// Add the TextBox to GroupBox
groupBox.Controls.Add(textBox);
// Add to this Form
mFlowLayoutPanel.Controls.Add(groupBox);
}
mFlowLayoutPanel.ResumeLayout(); // after suspend, resume!
}
}

Allow a ListBox to overlap a TableLayoutPanel (C# .NET)

I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.

Attach new Timer to every Object

I am using WPF and created a Window with informations about the computer.
It stores informations like Network connectivity, IP's, Subnet masks, Network devices and other stuff.
To track changes in the system I want to add an timer on an object to refresh itself. I don't want to refresh the hole form because I had HttpWebRequests in it and it will freeze the programm for a few seconds. It should be easier to see changes and to highlight them.
For example:
StComputerInf.Children.Add(new Label { Content = "2. Domain: \t\t" + System.Environment.UserDomainName });
I want to add here an timer to refresh itself.
And for every TreeViewItem in a TreeView:
public TreeView CreatTVConnection()
{
List<CAdapter> LAdapter = new List<CAdapter>();
List<TreeViewItem> lConnectedDevices = new List<TreeViewItem>();
List<TreeViewItem> lDisconnectedDevices = new List<TreeViewItem>();
LAdapter = ReadAdapter();
TreeView tv_Adapter = new TreeView();
tv_Adapter.Name = "Adapter";
tv_Adapter.Background = System.Windows.Media.Brushes.Transparent;
tv_Adapter.BorderThickness = new Thickness(0);
TreeViewItem Connected = new TreeViewItem();
TreeViewItem Disconnected = new TreeViewItem();
lConnectedDevices = LoadTV(true, LAdapter);
if (lConnectedDevices.Count > 0)
{
Connected.Header = "Connected:";
Connected.FontWeight = FontWeights.Bold;
Connected.Foreground = System.Windows.Media.Brushes.Green;
Connected.Name = "Connected";
foreach (TreeViewItem tvi in lConnectedDevices)
{
tvi.FontWeight = FontWeights.Normal;
Connected.Items.Add(tvi);
}
}
....
And is there a way to see if an object have changed? So I can highlight the affected object?
Use a factory to create your objects.
So for the label example you'd use something like
LabelFactory.Create(any useful parameters here) and as part of that method you can include a timer etc.
Also, look into using async/await to update your forms as an easier way to update them without freezing the forms. Once you are comfortable with the pattern you should be able to remove the dependency on timers.

Can't find controls that don't have enough defined properties in Coded UI

This may be a noobish question, but in my records in Coded UI Tests, I have recorded a lot of controls that don't have enough defined properties to be found in playback.
For exemple:
public HtmlEdit UIItemEdit
{
get
{
if ((this.mUIItemEdit == null))
{
this.mUIItemEdit = new HtmlEdit(this);
#region Search Criteria
this.mUIItemEdit.SearchProperties[HtmlEdit.PropertyNames.Id] = null;
this.mUIItemEdit.SearchProperties[HtmlEdit.PropertyNames.Name] = null;
this.mUIItemEdit.SearchProperties[HtmlEdit.PropertyNames.LabeledBy] = null;
this.mUIItemEdit.SearchProperties[HtmlEdit.PropertyNames.Type] = "SINGLELINE";
this.mUIItemEdit.FilterProperties[HtmlEdit.PropertyNames.Title] = null;
this.mUIItemEdit.FilterProperties[HtmlEdit.PropertyNames.Class] = null;
this.mUIItemEdit.FilterProperties[HtmlEdit.PropertyNames.ControlDefinition] = "type=\"text\" value=\"\"";
this.mUIItemEdit.FilterProperties[HtmlEdit.PropertyNames.TagInstance] = "5";
this.mUIItemEdit.WindowTitles.Add("http://cms.home.psafe.com/");
#endregion
}
return this.mUIItemEdit;
}
In this post, I learned about SearchProperties, but it doesn't look to be an appropriate solution in this case.
Is there any other way to wrap these controls properly?
You might be able to find it if its containing element can be found. You can use the containing element to scope the search. So, find that element's parent, then find an input type=text within it:
var container = new HtmlControl(bw); //where bw is the browser window
HtmlDiv parentDiv = new HtmlDiv(container);
parentDiv.SearchProperties[HtmlDiv.PropertyNames.Id] = "theIdOfYourDiv";
HtmlEdit edt = new HtmlEdit(parentDiv); //the search scope is narrowed down to the div only. This may be enough to find your control with the search property.
edt.SearchProperties[HtmlEdit.PropertyNames.Type] = "SINGLELINE";
You have two options:
Try crowcoder's solution of searching in the parent. The problem with this solution is when you move a control around you're going to be changing code a lot.
Add an Id property to all your controls in the HTML, this will make your Coded UI more robust and responsive to changes in the UI.

C# save entire UI control values to XML

I have a lot of control values in my C# app. I'm wondering if there is a possibility to store and read the entire available control values at once, instead of the code below.
Please note, the code below is only for 1 groupBox. I've 8 of them, thus this will generate a lot of code to implement it (8 times the code below).
groupBox are copies of each other
the code below works
I store the data using serializing the configData class (works).
private void uiStateWriteToData()
{
configData.datSensor4mA1 = (int)uiSensor4mA1.Value;
configData.datSensor20mA1 = (int)uiSensor20mA1.Value;
configData.datSensorPidP1 = (int)uiSensorPidP1.Value;
configData.datSensorPidI1 = (int)uiSensorPidI1.Value;
configData.datSensorPidD1 = (int)uiSensorPidD1.Value;
configData.datSensorPidS1 = (int)uiSensorPidS1.Value;
configData.datSensor1InToOutput1 = uiSensor1Out1.Enabled;
configData.datSensor1InToOutput2 = uiSensor1Out2.Enabled;
configData.datSensor1InToOutput3 = uiSensor1Out3.Enabled;
}
private void uiStateUpdateFromData()
{
uiSensor4mA1.Value = configData.datSensor4mA1;
uiSensor20mA1.Value = configData.datSensor20mA1;
uiSensorPidP1.Value = configData.datSensorPidP1;
uiSensorPidI1.Value = configData.datSensorPidI1;
uiSensorPidD1.Value = configData.datSensorPidD1;
uiSensorPidS1.Value = configData.datSensorPidS1;
uiSensor1Out1.Enabled = configData.datSensor1InToOutput1;
uiSensor1Out2.Enabled = configData.datSensor1InToOutput2;
uiSensor1Out3.Enabled = configData.datSensor1InToOutput3;
}
I don't know if there are any frameworks that can do that, but maybe you could come up with your own utility that writes control values into xml file based on the control hierarchy, types and names.
Here is how it may look (rather in pseudo-code):
private static void WriteControlValuesToXml(XmlDocument document, XmlNode parentNode, Control currentControl)
{
// if current control is container, writing its children values
if (currentControl.Controls.Count > 0)
{
var addedNode = AddNode(parentNode, currentControl.Name);
foreach (Control childControl in currentControl.Controls)
WriteControlValuesToXml(document, addedNode, childControl);
}
else
{
// if current control is not container, writing control values
if (currentControl is CheckBox)
{
var checkBox = (CheckBox)control;
AddNode(parentNode, currentControl.Name, "CheckBox", checkBox.Checked);
}
if (currentControl is TextBox)
{
var textBox = (TextBox)control;
AddNode(parentNode, currentControl.Name, "TextBox", textBox.Text);
}
// ... other known controls
}
}
And following the same logic, you can implement reading the same control values from file.

Categories

Resources