The official source of product insight from the Visual Studio Engineering Team
This is the sixth article in the WPF in Visual Studio 2010 series. This week, guest author Phil Price gives us his view of what it took to test the new WPF UI. - Paul Harrington
This post covers an overview of techniques that we used to create and maintain automated user interface regression tests for Visual Studio. Regression tests are a type of software test that, collectively, aim to be an oracle of expected functionality for the target application, run often against new builds of product – they aim to uncover regressions in behavior introduced in a new build.
Throughout Visual Studio 2005 and Visual Studio 2008 we built a sizable UI automation framework that abstracted the different facets of the user interface into atomic classes. A concrete example of this would be a class named “ToolWindow” which would have methods such as “ClickClose” which would click the “X” button on the tool window to close it, and “DragTo(x, y)” which would drag a tool window to the specified screen coordinate. Using this framework a vast suite of regression tests was built to exercise the functionality within Visual Studio. Prior to Visual Studio 2010, the UI automation framework would largely use HWND search techniques to find a particular HWND of interest. A trivial example would be to find the status bar and get its current caption*:
// Find visual studio main window IntPtr visualStudioHwnd = NativeMethods.FindWindowEx( IntPtr.Zero, IntPtr.Zero, "wndclass_desked_gsk", null); // Find the Status Bar IntPtr statusBar = NativeMethods.FindWindowEx( visualStudioHwnd, IntPtr.Zero, "VsStatusBar", null); // Wraps SendMessage of WM_GETTEXTLENGTH and WM_GETTEXT string statusBarText = NativeMethods.GetWindowText(statusBar);
* Please note that these examples are designed to illustrate the APIs we use to achieve UI automation. A test case would not do this directly, but go through a framework.
For some controls HWNDs wouldn’t expose enough information. In this case, one could retrieve the MSAA IAccessible from the HWND and party on that. IAccessible is the same interface that screen readers and other assistive technology may use to gather information about an application’s user interface and capabilities. It just so happens that it provides great information for consumption from UI tests too. An example of this would be to click each of the visible top level menu items.
IntPtr dock = NativeMethods.FindWindowEx( visualStudioHwnd, IntPtr.Zero, null, "MsoDockTop"); IntPtr mainMenu = NativeMethods.FindWindowEx( dock, IntPtr.Zero, null, "Menu Bar"); // Get the IAccessible. // Wraps ole32!AccessibleFromWindow IAccessible menuBarAccessible = NativeMethods.GetAccessibleFromWindow( mainMenu, (uint)NativeMethods.OBJID.CLIENT); // Wraps ole32!AccessibleChildren object[] children = NativeMethods.GetAccessibleChildren(menuBarAccessible); foreach (IAccessible child in children .Where(child => child is IAccessible).Cast<IAccessible>()) { // Skip invisible children (hidden) if ((uint)child.accState == ((uint)child.accState | NativeMethods.STATE_SYSTEM_INVISIBLE)) { continue; } //// Click each item int top, left, width, height = 0; child.accLocation(out left, out top, out width, out height, 0); NativeMethods.SetCursorPos(left + width / 2, top + height / 2); NativeMethods.SendInput( NativeMethods.MOUSEINPUT.FromFlags(NativeMethods.MOUSEEVENTF_LEFTDOWN)); NativeMethods.SendInput( NativeMethods.MOUSEINPUT.FromFlags(NativeMethods.MOUSEEVENTF_LEFTUP)); Thread.Sleep(100); }
By the end of the Visual Studio 2008 product cycle, HWND searching, various User32 calls and IAccessible were the techniques used to execute our UI automation. It had been successful and we’d shipped two products using it. But, as you’ve probably already guessed, that was about to change.
Before we move on to UI testing for Visual Studio 2010, I’d like to share a few numbers that represent the scale of automated UI regression testing for Visual Studio 2008.
With the introduction of the WPF shell the previously used technique of zipping around the HWND tree became more difficult, along with calls to APIs like SendMessage and GetWindowLong which were used quite extensively. There are still HWNDs within the WPF shell for some content – but many HWNDs were removed in place of WPF UI elements, and the structure up to the HWND you may care about (for example Solution Explorer) changed. We had built up assumptions about the structure of the HWND tree within the UI automation framework for Visual Studio. These assumptions no longer held true.
So what to do? It’s clear that relying on a HWND structure isn’t going to cut it, but we’d like to continue to use the majority of our old regression tests from the previous release of Visual Studio because it would be very expensive to re-write them all.
Enter Microsoft UI Automation, aka UIA. UIA is, to quote MSDN, “the new accessibility framework for Microsoft Windows, available on all operating systems that support Windows Presentation Foundation (WPF).” UIA and WPF were developed hand in hand (since the first release of WPF). That is to say that the support for WPF UI elements through UIA clients is better than when using MSAA. To enable us to keep our automated UI regression tests, we had to re-write large parts, especially in the navigational portions of the framework that dealt with finding elements, to use UIA instead.
Taking the previous examples, the UIA equivalent would look something like this:
Status Bar example: var mainWindow = AutomationElement.FromHandle(visualStudioHwnd); var statusBar = mainWindow.FindFirst( TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.StatusBar)); // For VS2010 Text for status bar is now within the first child var firstStatusBarCell = statusBar.FindFirst(TreeScope.Children, Condition.TrueCondition); string statusBarText = firstStatusBarCell.Current.Name;
Status Bar example:
var mainWindow = AutomationElement.FromHandle(visualStudioHwnd); var statusBar = mainWindow.FindFirst( TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.StatusBar)); // For VS2010 Text for status bar is now within the first child var firstStatusBarCell = statusBar.FindFirst(TreeScope.Children, Condition.TrueCondition); string statusBarText = firstStatusBarCell.Current.Name;
Menu item example: // Get the main menu bar var menuBar = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuBar)); // Enumerate each child menu item foreach (AutomationElement child in menuBar.FindAll( TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuItem))) { // Click it Point clickable = child.GetClickablePoint(); NativeMethods.SetCursorPos((int)clickable.X, (int)clickable.Y); NativeMethods.SendInput( NativeMethods.MOUSEINPUT.FromFlags(NativeMethods.MOUSEEVENTF_LEFTDOWN)); NativeMethods.SendInput( NativeMethods.MOUSEINPUT.FromFlags(NativeMethods.MOUSEEVENTF_LEFTUP)); Thread.Sleep(100); }
Menu item example:
// Get the main menu bar var menuBar = mainWindow.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuBar)); // Enumerate each child menu item foreach (AutomationElement child in menuBar.FindAll( TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuItem))) { // Click it Point clickable = child.GetClickablePoint(); NativeMethods.SetCursorPos((int)clickable.X, (int)clickable.Y); NativeMethods.SendInput( NativeMethods.MOUSEINPUT.FromFlags(NativeMethods.MOUSEEVENTF_LEFTDOWN)); NativeMethods.SendInput( NativeMethods.MOUSEINPUT.FromFlags(NativeMethods.MOUSEEVENTF_LEFTUP)); Thread.Sleep(100); }
It is also worth noting that UIA supports a concept called patterns, which are, to quote MSDN, “a way to categorize and expose a control's functionality independent of the control type or the appearance of the control.” IAccessible supported “DoDefaultAction”, whereas UIA supports many different types of actions. Taking the example above, instead of a mouse click one could get the “ExpandCollapsePattern” and call the “Expand” method to expand each menu item.
foreach (AutomationElement child in ...) { object pattern; if (child.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out pattern)) { ExpandCollapsePattern expandPattern = (ExpandCollapsePattern)pattern; expandPattern.Expand(); } }
In the process of migrating to the WPF shell we learned many lessons. I’d like to share a few key ones in regards to automated UI testing.
If you feel inspired to create automated UI regression tests for your product you may find the following set of tools useful.
Phil Price, Software Design Engineer in Test, Visual Studio Platform
Bio: Phil has been an SDET at Microsoft for 4 years. Previously working on the project system for Visual Studio 2008 and more recently on the WPF shell window management features.
A big thank you to Phil for his expert insights. Next time, Part 7 will conclude the WPF in Visual Studio 2010 series with a short round-up of WPF 4 topics that we didn’t have time to cover elsewhere - Paul Harrington
Previous posts in the series:
A great alternative to Spy++ in WinCheat: http://alinconstantin.homeip.net/Download/WinCheat/Default.htm
Anybody getting started with UI Automation might find my article interesting: An Introduction to UI Automation - with spooky spirographs (http://blog.functionalfun.net/2009/06/introduction-to-ui-automation-with.html)
@DanielRose
Yes the person that wrote WinCheat (Alin) is actually on our team :) It is a very hand tool.
AFAICT, you can use OfType<IAccessible> rather than having to separately Where and Cast.
Hello All,
I am facing problem to test one of my website through Coded UI Testing. I am using MS Studio 2010. And I found problem during testing through Different Browser. Also sometimes facing the problem of Fatal Error and Unexpected Error need to restart studio 2010. Can you please help me out how to resolve this issue.
I get this error frequently : fatal error : debugging information corrupt; recompile module" need to restart visual studio.
Also any one knows how to do i create test for all browser like Firefox and Internet explorer
Also Sometimes during automated Test. Its keep running which is never ending. Any one knows why this problem appears during testing ( Coded UI Testing for Web Site).
I am using Visual Studio 2010. and testing one website through Automated Coded UI test and having some problems
as per below
Issue No : 1 How can i record the event for FireFox through Coded UI test. Cause When i try to record the Events from Firefox its record successfully. but when i tried to run that Automated Test its Giving Fatal Error : Debugging information Currupt. Recompile module" and giving message to restart the Visual studio 2010.
Issue no 2 : Its working fine with Internet Explorer. but sometimes its keep running which is never end.
Issue no 3 : Is there any way i create all test for Internet Explored and it will work with Firefox? Like i record events through Internet explorer for my website and after that any small change or function that will work with Firefox too.
If any one solve it i will rewarded him 50 to 100 USD.
contact : [email protected]
Thanks
Looks great Phil!
We have been using White (white.codeplex.com) for a while now (see www.marcusoft.net/.../using-bdd-with-specflow-wpf-and-white_14.html and www.marcusoft.net/.../whitestephelper-small-step-toward.html).
As I understand it White is an OO-interface on top of UI Automation.
What would be the great advantage for me to go to or use UI Automation instead of white?
/Marcus
How to hook event of UI Controls like Button, Checkbox, etc...?
We are writing an application which should listen the events of selected windows application. For this we choosed White Framework Recorder DLL's in the background.
But, we coudn't find any API or Documentation of Recoder to implement this solution.
Could you please provide some sample applications / code snippet to write event handlers ?
Please tell me the tool name and the URL where i can find UI Automation Testing Tool for WinForms/WPF forms