So I ran into a customer recently that was getting pretty advanced with Coded UI and they were having trouble maintaining the UI Map technology as part of Visual Studio. One the things that I had noticed is that the UI Map does store lots of information including page names, URL's and many other things that are interesting but not needed. This technology is great for beginners that need to get started with the technology.
So as I started thinking about it I was reflecting on some of the technologies that I have used before that included a way to search the DOM or Control Tree if you did not have an items mapped. Being a long time developer and avid Coded UI user I started thinking about how to solve this problem.
As I found quickly there are many ways solve this problem including taking the generated code from UI Map and move it around. It seems pretty simplistic under the covers but sure generates a LOT of code. J
As I started to think about how to condense the code and quickly .Net Generics came to mind. As I started this process I quickly started introducing the dynamic type and some of the cool things about the .Net Framework. So in the end I was left with a Custom Extension Method that extended the UITestControl class so that I could use it in line with my standard testing sequence. The code quickly became compact, readable and most of all it WORKED! I am always happy when those things align. I have included a snippet below of the TestMethod and also the static extension. You could also certainly use this with other web technologies since the base UI Testing framework uses UITestControl for all inherited types. So short story you can use the Class Extension for Windows, Web, WPF or any other automation controls that become part of Coded UI in the future.
Hope this helps someone else solve a very common problem.
Sample Test Method
[TestMethod] public void ThisIsATestUsingCalcWithNoUIMap() { //launch the application var app = ApplicationUnderTest.Launch("C:\\Windows\\System32\\calc.exe", "%windir%\\System32\\calc.exe"); //get all the controls we want to use - this uses dynamic types for serach and filter properties WinWindow calWindow = app.SearchFor<WinWindow>( /* pass search properties */ new { Name = "Calculator" }, /*pass filter properties if needed */ new { ClassName = "CalcFrame" }); WinButton buttonAdd = calWindow.Container.SearchFor<WinButton>(new { Name = "Add" }); WinButton buttonEqual = calWindow.Container.SearchFor<WinButton>(new { Name = "Equals" }); WinButton button1 = calWindow.Container.SearchFor<WinButton>(new { Name = "1" }); WinButton button2 = calWindow.Container.SearchFor<WinButton>(new { Name = "2" }); WinButton button3 = calWindow.Container.SearchFor<WinButton>(new { Name = "3" }); WinText txtResult = calWindow.Container.SearchFor<WinText>(new { Name = "Result" }); //do all the operations Mouse.Click(button2); Mouse.Click(buttonAdd); Mouse.Click(button3); Mouse.Click(buttonEqual); //evaluate the results Assert.AreEqual("5", txtResult.DisplayText); //close the application app.Close(); } |
Custom Extension Method
public static class CodedUIExtension { public static T SearchFor<T>(this UITestControl _this, dynamic searchProperties, dynamic filterProperties = null) where T : UITestControl, new() { T ctrl = new T(); ctrl.Container = _this; IEnumerable<string> propNames = ((object)searchProperties).GetPropertiesForObject(); foreach (var item in propNames) { ctrl.SearchProperties.Add(item, ((object)searchProperties).GetPropertyValue(item).ToString()); } if (filterProperties != null) { propNames = ((object)filterProperties).GetPropertiesForObject(); foreach (var item in propNames) { ctrl.SearchProperties.Add(item, ((object)filterProperties).GetPropertyValue(item).ToString()); } } return ctrl as T; } private static object GetPropertyValue(this object _this, string propName) } |