The following example, will be using Coded UI Tests (CUIT) and Visual Studio 2012 Premium to test the login window for a small WPF application.
The Login Test Application is quite simple and composed of two WPF windows. The sole purpose of the first window is to create and show the Login window.
The Login window is composed of a user name, a password (clear text), a login button and an error message.
The first step to building maintainable Coded UI Tests is to build a UIMap per application screen. The goal here is to keep the UIMaps as simple as possible. It’s strongly recommended to regularly prune UIMaps by removing unused controls from the UI Control Map and unused UI Actions. Doing this will greatly simplify future modifications and additions to the UIMap.
Get the code @ https://github.com/brisebois/Coded.UI.Test.Demoware
NOTE Before you start, be sure that Visual Studio is running as Administrator
Building The UIMaps
Right click on your newly created Coded UI Test Project and select Add –> New Item… then from the Visual C# Items select Test. At this point the possible options will contain Coded UI Test Map, select this item and give it a meaningful name before clicking on Add. Doing so will automatically start the Coded UI Test Builder.
The Coded UI Test Builder is by far your best friend when it comes to building UIMaps. It provides you with a way to record your actions, the same what that Excel allows you to record Macros.
To record a new test, click on the red record button, from this point on, all of your actions will be recorded and prepared for playback. The icon the right of the record button is extremely useful. It allows you to visualize the actions that have been recorded. Furthermore, it allows you to remove unwanted actions. The next button to the right, is also quite important in your test workflow. The bulls eye, empowers you to select, inspect, add and assert individual controls which are visible on the screen. Finally, the fourth button takes all that was recorded, adjusted, asserted and generates the XML and Code necessary to playback your tests.
Be sure to keep your recorded UI Actions as simple as possible. Microsoft recommends a maximum of 10 steps per UI Action. By splitting up your actions, you are able to compose your tests by executing specific actions as we will be doing in the test scenarios below.
I created the following UIMaps using the Coded UI Test Builder. Creating one UIMap for each window and making sure that I don’t have duplicate controls in my UI Control Map.
Once the first UIMaps was created, I repeated the same process for the second one. This time, I added an assert to validate the error message and I added other actions to type and click on the login button.
By creating my UIMaps this way, I do not generate code directly in my Coded UI Test. I am able to prepare the UIMaps, the UI Actions and UI Control Maps, which I will then compose my tests with.
Creating the initial CodedUITest
But first! I must configure the UIMaps to point to the same root UI Control. To accomplish this I used a UIMapContainer. Mapping the root UI Controls from your UIMaps will speed up your test execution and may resolve odd behavior and delay issues.
The following will wire up the UIMaps for the subsequent tests.
[CodedUITest] public class LoginCodedUITests { private UIMapContainer<LoginWpfApplicationUIMap> container; public LoginCodedUITests() { container = new UIMapContainer<LoginWpfApplicationUIMap>(); container.Configure<LoginWindowIUMap>(m => m.UILoginTestApplicationWindow, r => r.UILoginTestApplicationWindow); } }
In the LoginCodedUITests constructor I created a UIMapContainer using LoginWpfApplicationUIMap as a base UIMap. This UIMap is accessible through the container.UIMap property. Then I configured the LoginWindowUIMap to point to the same UILoginTestApplicationWindow instance as the root LoginWpfApplicationUIMap UIMap.
With the newly created LoginCodedUITests class and configured UIMapContainer we are now ready to compose our tests.
Test Scenarios
In the following scenarios, I will be testing for positives. Testing for positives is extremely important because your tests then describe with accuracy your requirements and specifications. Pay extra attention to each scenario. They test a single known outcome. Tests should not test for unknowns or side effects because the goal of our tests is to validate expected behavior.
Scenario 1
Given that I have a valid username and password when I Login I expect that I will be redirected to the proper screen.
This scenario tests for a specific outcome. It’s important that tests do not break for more than One reason. They must fail gracefully with a meaningful error message.
The test will launch the application, so that the test can run on a clean instance, uninhibited by side effects created by earlier tests it will be much easier to reproduce and diagnose errors.
/// <summary> /// Given that I have a valid username and password when I /// Login I expect that I will be redirected to the proper screen. /// </summary> [TestMethod] public void GivenValidCredentialsWhenILoginIExpectThatTheLoginWindowWillDisapear() { string fileName = string.Format(@"{0}LoginWpfApplication.exe", path); using (ApplicationUnderTest.Launch(fileName)) { container.UIMap.ClickOnLogin(); var loginWindow = container.Get<LoginWindowIUMap>(); loginWindow.TypeUsername(); loginWindow.TypeUserPassword(); loginWindow.ClickLogin(); UILoginWindow uiLoginWindow = loginWindow.UILoginWindow; bool loginWindowNotExit = uiLoginWindow.WaitForControlNotExist(1000); Assert.IsTrue(loginWindowNotExit); } }
In this scenario, we test for the golden path. A successful login with valid credentials. To assert that we have successfully logged in, we observe the UILoginWindow and wait for it to disappear, at that moment we know that the login was successful.
Scenario 2
Given that I have a valid username and an invalid password when I Login I expect that the error message will be “The username and password combination was invalid”.
In this scenario, we can copy the code from the first test. Thanks to our small UI Actions we can compose multiple tests based on the same UI Actions without having to record new UI Actions for each test.
One of the most important things about Coded UI Tests is that you can override values for your UI Actions. The .designer should never be modified by hand. The example below demonstrates how to change the values that are used by the UI Actions. Changing these values can be achieved through the Params of each UI Action. This also allows you to create Data Driven tests.
/// <summary> /// Given that I have a valid username and an invalid password when I Login /// I expect that the error message will be /// “The username and password combination was invalid”. /// </summary> [TestMethod] public void GivenValidUserNameAndInvalidPasswordWhenILoginIExpectAnErrorMessage() { string fileName = string.Format(@"{0}LoginWpfApplication.exe", path); using (ApplicationUnderTest.Launch(fileName)) { container.UIMap.ClickOnLogin(); var loginWindow = container.Get<LoginWindowIUMap>(); loginWindow.TypeUsernameParams.UIUserNameTextboxEditText = "alexandre"; loginWindow.TypeUsername(); loginWindow.TypeUserPasswordParams.UIPasswordTextboxEditText = "none"; loginWindow.TypeUserPassword(); loginWindow.ClickLogin(); const string msg = "The username and password combination was invalid"; loginWindow.AssertValidErrMsgExpectedValues.ErrorMsgDisplayText = msg; loginWindow.AssertValidErrMsg(); } }
This scenario tests for a specific error message. This ensures that the user is well informed and guided when an error occurs.
Scenario 3
Given that I have an invalid username and a valid password when I Login I expect that the error message will be “The username and password combination was invalid”.
For this scenario, we can copy the previous test and alter the values being used . This permutation is important, because it validates authentication rules. It’s also important because we need to test for all the eventual outcomes. In this case we cover these possibilities with scenarios 2 through 4.
/// <summary> /// Given that I have an invalid username and a valid password when I Login /// I expect that the error message will be /// “The username and password combination was invalid”. /// </summary> [TestMethod] public void GivenInvalidUserNameAndValidPasswordWhenILoginIExpectAnErrorMessage() { string fileName = string.Format(@"{0}LoginWpfApplication.exe", path); using (ApplicationUnderTest.Launch(fileName)) { container.UIMap.ClickOnLogin(); var loginWindow = container.Get<LoginWindowIUMap>(); loginWindow.TypeUsernameParams.UIUserNameTextboxEditText = "none"; loginWindow.TypeUsername(); loginWindow.TypeUserPasswordParams.UIPasswordTextboxEditText = "P@ssword"; loginWindow.TypeUserPassword(); loginWindow.ClickLogin(); const string msg = "The username and password combination was invalid"; loginWindow.AssertValidErrMsgExpectedValues.ErrorMsgDisplayText = msg; loginWindow.AssertValidErrMsg(); } }
This scenario tests for a specific error message. This ensures that the user is well informed and guided when an error occurs.
Scenario 4
Given that I have an empty username and an empty password when I Login I expect that the error message will be “The username and password are required”.
Once again, we can reuse the previous test code to build this permutation. For this test, we want to test for empty fields and the proper error message. To accomplish this, we can remove the UI Actions that type values into the fields and assert for the proper error message.
/// <summary> /// Given that I have an empty username and an empty password when I Login /// I expect that the error message will be /// “The username and password are required”. /// </summary> [TestMethod] public void GivenNoCredentialsWhenILoginIExpectAnErrorMessage() { string fileName = string.Format(@"{0}LoginWpfApplication.exe", path); using (ApplicationUnderTest.Launch(fileName)) { container.UIMap.ClickOnLogin(); var loginWindow = container.Get<LoginWindowIUMap>(); loginWindow.ClickLogin(); const string msg = "The username and password are required"; loginWindow.AssertValidErrMsgExpectedValues.ErrorMsgDisplayText = msg; loginWindow.AssertValidErrMsg(); } }
This scenario tests for a specific error message. This ensures that the user is well informed and guided when an error occurs.
Summing things up
In the test scenarios present in this post, we have observed how we can record once and reuse UI Actions. Furthermore, by exploiting reuse, we are effectively simplifying the code and UIMaps resulting in better maintainability. If for some reason we need to add or remove logic, steps or actions from our tests, we can do so without affecting unrelated tests.
Great post, Alexandre. I’m doing hand-coded CUIT, which I blog about over at burdettelamar.wordpress.com.
LikeLike
Question. Is there a way to make the handling of data grid rows more maintainable?
For instance, in my windows based application, adding a new client should create a number of new datagrid rows, one of which is as follows:
Date Rate Amount Type Months Status
10/13/2014 1.23 0.00 Maximum 12 Unconfirmed
I need to verify that this row was properly created and also select it, open it and further test it and finally delete it.
In the code behind automatically created is as follows:
_
Public Class UIRow0Row
Inherits WinRow
Public Sub New(ByVal searchLimitContainer As UITestControl)
MyBase.New(searchLimitContainer)
Me.SearchProperties.Add(New PropertyExpression(WinRow.PropertyNames.Value, “10/13/2004;1.23;0.00;Maximum;12;Unconfirmed”, PropertyExpressionOperator.Contains))
Me.SearchConfigurations.Add(SearchConfiguration.AlwaysSearch)
Me.WindowTitles.Add(“Premium Reduction System”)
End Sub
As you can see, for each row, a constructor is created using the row values at the time of the original recording. Given that the date changes, this test would fail the following day. Is there any way to pass those values instead?
LikeLike
I haven’t done much in regards to this control. I’m not familiar with the best practices around this scenario.
LikeLike