WPF automation – locating and structure of WPF elements

Last Updated on by

Post summary: Guide how to locate WPF elements with Telerik Testing Framework.

References

This post is part of Automation of WPF applications with Telerik Testing Framework and TestStack White series. The sample application can be found in GitHub SampleApp repository.

MainWindow Page Object

MainWindow.cs class is a representation of the main window of our sample application which is WPF. Note that code here is shortened. The full code can be found in GitHub repository.

using ArtOfTest.WebAii.Controls.Xaml.Wpf;
using ArtOfTest.WebAii.Silverlight;
using ArtOfTest.WebAii.TestTemplates;
using System.Threading;

namespace SampleApp.Tests.Framework.Elements
{
	public class MainWindow : XamlElementContainer
	{
		public static string WINDOW_NAME = "MainWindow";
		private string mainPath =
			"XamlPath=/Border[0]/AdornerDecorator[0]/ContentPresenter[0]/Grid[0]/";
		public MainWindow(VisualFind find) : base(find) { }

		private Button Button_Browse
		{
			get
			{
				return Get<Button>(mainPath + "Button[0]");
			}
		}

		public void ClickBrowseButton()
		{
			Button_Browse.User.Click();
			Thread.Sleep(500);
		}
	}
}

MainWindow is following Page Object design pattern. Elements are private Properties. Actions on elements are public methods that are the actual building blocks of the tests. Do not just add action methods for the sake of adding them. Add them if only the tests need such action.

If the element is used in only one action then it could be inside the action method, but if the element is used more than one it is mandatory to keep it as separate property. The main idea is to avoid duplications in order to improve maintainability. Remember to stay DRY: one element must be defined in only one place.

MainWindow class extends XamlElementContainer which comes from the framework. The constructor takes VisualFind object and passes it to XamlElementContainer‘s constructor. This allows us to use framework’s methods for location of elements. In our case

public TControl Get<TControl>(params string[] clauses)
	where TControl : ArtOfTest.WebAii.Controls.Xaml.IFrameworkElement;

This is the strategy used by the Test Studio generated tests and I prefer it.

A different approach for MainWindow

Another element structure could be not to extend XamlElementContainer but pass a VisualFind object through MainWindow’s constructor and use it internally to locate elements.

using ArtOfTest.WebAii.Controls.Xaml.Wpf;
using ArtOfTest.WebAii.Silverlight;
using System.Threading;

namespace SampleApp.Tests.Framework.Elements
{
	public class MainWindow
	{
		public static string WINDOW_NAME = "MainWindow";
		private string mainPath =
			"XamlPath=/Border[0]/AdornerDecorator[0]/ContentPresenter[0]/Grid[0]/";
		private VisualFind visualFind;
		public MainWindow(VisualFind find)
		{
			visualFind = find;
		}

		private Button Button_Browse
		{
			get
			{
				return visualFind.ByExpression(
					new XamlFindExpression(mainPath + "Button[0]")).
					CastAs<Button>();
			}
		}

		public void ClickBrowseButton()
		{
			Button_Browse.User.Click();
			Thread.Sleep(500);
		}
	}
}

How to locate elements is described in finding page elements article.

XamlPath

As you can see I’m using exact XamlPath find expression in order to locate my elements because I find it consistent and easy to maintain. XamlPath syntax is pretty similar to XPath. The difference is its indexes are zero-based (Button[0] is the first element) and it doesn’t provide predicates and axes. The hard part is to get the XamlPath. I have found this lovely tool WPF Inspector. It gives a lot of information about the structure of WPF application.

WPF Inspector

WPF Inspector

This is a screenshot of the XamlPath for Browse button. In more complex UIs even with this great tool, it is not that easy to get the XamlPath. If you find it hard you may consider other find expressions.

All elements on MainWindow are only WPF thus an instance of White is not needed. In case of WinForms content hosted inside WPF, you can pass an instance of White through elements’ constructor and use it in actions inside.

public MainWindow(VisualFind find, Application applicationWhite) : base(find)
{
	appWhite = applicationWhite;
}

Having an instance of White inside the class you can work with it as explained in WPF automation – locating and structure of WinForms elements post.

Related Posts