Using CodedUI testautomation without UIMap files

*Moved to: http://fluentbytes.com/using-codedui-testautomation-without-uimap-files/

One of the things I hear a lot from developers working on test automation is that they dislike the way CodedUI tests in visual studio work with the UIMap files. I can understand that most developers will assume this is the required way of work, since it is strongly advertised when you create a new codedUI test project. I mean the first dialog they show is if you want to create tests based on existing action recording in MTM or record new actions with the UI test builder.

image

In my opinion this dialog should have one other obvious option, that is Hand code the tests using the provided CodedUI object model.

In this post I want to introduce you to the way I like to organize my CodedUI tests so I get a robust and maintainable set of test automation scripts.

As an example I will take an ASP.NET MVC web application as the system under test. I have picked an sample MVP project called MVC Music Store, that you can download here if you want to try things for yourself and you need an app to test it on.

Page objects

The way I like to create my tests is by applying the page object pattern that is commonly used in the UI test automation discipline. With the page object pattern you analyze the application and create an UI abstraction class for each UI component you find in the application. For MVC this maps mostly to views, for XAML apps this will mostly map to XAML pages and for windows forms to actual forms you have in the project. Page objects don’t have to represent a full page, they can also represent a commonly used user element.

Lets take the MVC music project as an example. Here you see the home screen of the application under test.

image

Here you can see the MVC project structure with the different views.

image

After a bit of analysis you can determine what the set of classes should be to abstract the user interface in page objects we can use later to code our tests. Based on my analysis I would create the following set of classes to abstract this application:

  • AccountLogon
  • AccountChangePassword
  • AccountRegister
  • Checkout (One object, since complete only contains one message)
  • Home
  • ShoppingCart (One object since cart summary only contains one link)
  • StoreDetail
  • StoreBrowse
  • StoreManager
  • StoreManagerDetail

Let’s dive a little bit deeper and look at the home page and the Shopping cart page to give you a sense how to decompose this and then write the appropriate test abstractions.

The home page can be dissected into multiple area’s. Many of the things on the home page are generic and can be interacted with from any other view. In the picture below I highlighted the area’s that I would expose as actions (Red) and area’s that contain controls (blue) that I will abstract in my page object as controls you can interact with.

image

From this I would create a base class where all my page objects inherit from. this base class exposes the following actions:

  • HomePage NavigateHome()
  • ShoppingCart NavigateShoppingCart()
  • StoreManager NavigateStoreManager()
  • StoreBrowse NavigateStore()
  • StoreBrowse SelectCategory()

Shared Elements

  • HtmlList CategoryList

You can see that each action will return a new Page object. You will see how this enables a very nice way to describe scenario’s later in the actual test methods.

I would create a HomePage page object that inherits from the base class and would add the following action:

  • StoreDetail SelectProduct()

and one additional control:

  • HtmlList FresshOfThegrillList

Now let’s implement this class without the use of the UIMap files

How CodedUI finds controls

CodedUI finds controls by searching and filtering. First it will start a search starting from a root element you provide. for a browser based test, this is normally the browser window. this means we need to pass out page object this browser window. this can be done in the constructor.

If you don’t use the UIMaps you can find a specific control by using one of the control types provided in the HtmlContols namespace. If I want to find an HyperLink based on the id you do this as follows:

var productHyperlink = new HtmlHyperlink(_browserWIndow);
productHyperlink.SearchProperties.Add(HtmlHyperlink.PropertyNames.Id, 
        "HyperlinkId");

In this sample I assume you have a private variable _browserWindow that is of type BrowserWindow.

If I want to click that hyperlink the only thing I need to do is call the mouse click on that control

Mouse.Click(productHyperlink);

One thing you might notice is that there is no explicit call to the Find() method on the control. A call to Find() is optional since any access to the control will trigger a lazy evaluation of the search and will result in the search for the object in the browser.

In some scenario’s you might not be able to locate a control by it’s ID. Although this provides the most robust way of finding the control even after restyling of the application, this is not always under your control. For this you can use many other search options like searching on css class name, inner text values, etc. If you use this way of allocating the controls I would recommend to slightly alter the search by using an operation contains in stead of an exact match. this way the test will be a little bit more change resilient. Below you see a search for the same hyperlink, but now based on it’s inner text and the fact that it is part of the innerText, not an exact match.

var productHyperlink = new HtmlHyperlink(_browserWindow);
productHyperlink.SearchProperties.Add(HtmlHyperlink.PropertyNames.InnerText, 
       "My text to search for", PropertyExpressionOperator.Contains);

Building the page classes

Now that we know how to locate controls, let create the actual shared class and the home page page objects. the Shared class will become something like this:

public class SharedActionsAndElements
{
   
    protected BrowserWindow _browserWindow;

    public SharedActionsAndElements(BrowserWindow browserWindow)
    {
        _browserWindow = browserWindow;
    }

    public HomePage NavigateHome()
    {
        var homeHyperlink = new HtmlHyperlink(_browserWindow);
        homeHyperlink.SearchProperties[HtmlHyperlink.PropertyNames.Id] = "current";
        homeHyperlink.SearchProperties[HtmlHyperlink.PropertyNames.InnerText] = "Home";
        Mouse.Click(homeHyperlink);
        return new HomePage(_browserWindow);
    }

    public ShoppingCart NavigateShoppingCart()
    {
        var shoppingcartHyperlink = new HtmlHyperlink(_browserWindow);
        shoppingcartHyperlink.SearchProperties[HtmlHyperlink.PropertyNames.Id] = "cart-status";
        Mouse.Click(shoppingcartHyperlink);
        return new ShoppingCart(_browserWindow);
    }


    public StoreBrowse NavigateStore()
    {
        var storeHyperlink = new HtmlHyperlink(_browserWindow);
        storeHyperlink.SearchProperties[HtmlHyperlink.PropertyNames.InnerText] = "Store";
        Mouse.Click(storeHyperlink);
        return new StoreBrowse(_browserWindow);
    }

    public StoreBrowse Selectcategory(string categoryName)
    {
        // find the hyperlink in the list of categories
        var categoriesList = this.CategoryList;
        var categoryHyperlink = new HtmlHyperlink(categoriesList);
        categoryHyperlink.SearchProperties.Add(HtmlHyperlink.PropertyNames.InnerText, categoryName, PropertyExpressionOperator.Contains);
        Mouse.Click(categoryHyperlink);
        return new StoreBrowse(_browserWindow);
    }

    public HtmlCustom CategoryList
    {
        get
        {
            var categoryList = new HtmlCustom(_browserWindow);
            categoryList.SearchProperties.Add(HtmlCustom.PropertyNames.Id, "categories");
            categoryList.SearchProperties.Add(HtmlCustom.PropertyNames.TagName, "UL");
            categoryList.Find();
            return categoryList;
        }
    }
}

the home page class looks like this:

public partial class HomePage : SharedActionsAndElements
{
    
    public HomePage(BrowserWindow browserWindow):base(browserWindow)
    {
    }
    public StoreDetail SelectProduct(string productName)
    {
        var productList = GetProductList();
        var productHyperlink = new HtmlHyperlink(productList);
        productHyperlink.SearchProperties.Add(HtmlHyperlink.PropertyNames.InnerText, productName, PropertyExpressionOperator.Contains);
        Mouse.Click(productHyperlink);
        return new StoreDetail(_browserWindow);
    }

    private HtmlControl GetProductList()
    {
        var AlbumList = new HtmlControl(_browserWindow);
        AlbumList.SearchProperties.Add(HtmlList.PropertyNames.Id, "album-list");
        AlbumList.SearchProperties.Add(HtmlControl.PropertyNames.TagName, "UL");
        AlbumList.Find();
        return AlbumList;
    }

    public HtmlList FresshOfThegrillList
    {
        get
        {
            var fressOfGrillList = new HtmlList();
            fressOfGrillList.SearchProperties.Add(HtmlList.PropertyNames.Id, "album-list");
            fressOfGrillList.Find();
            return fressOfGrillList;
        }
    }
}
}

Creating the actual tests

Once we have created these page objects to represent the user interface, you can write very nice readable scenario’s.

[TestMethod]
public void ShopForAlbumViaCategory()
{
    var browserWindow = BrowserWindow.Launch(new Uri("http://localhost:26641/"));
    HomePage siteHome = new HomePage(browserWindow);
    Assert.IsTrue(
    siteHome.Selectcategory("Alternative")
        .SelectProduct("Carry On")
        .AddItemToCart()
        .CheckOut()
        .IsCurrentPageValid()
        ,"Buy Alternative album 'Carry On' dit not return to the logon page");
}

I hope I this article showed you that the CodedUI syntax to find controls is very easy to use and that you don’t need to use the CodedUI UIMap files. CodedUI maps have a place and are great for getting started fast. In general these files are less optimal for larger automation projects. You can still use them, but then I would advise to build your page objects using the UIMap files and have one UIMap file per page object. this way you can mostly avoid nasty merge conflicts when working in a team.

Using the page object pattern in general, provides you with a very nice abstraction of the UI and the ability to write fluent scenario based tests that you can read almost as you read a test case in text. this way you create very maintainable nicely layered test code.

Follow my new blog on http://fluentbytes.com