Wednesday, June 10, 2015

Coded UI Fluent Syntax

I have been looking into the CodedUI project template in Visual Studio for testing some web applications. I had a requirement to automate a browser without using Visual Studio / CodedUI and investigated Selenium directly. I really, really don't like the Selenium syntax for automation, and while I do like CodedUI's syntax better, I still do not like it. I have created some simple extensions to allow a more fluent searching syntax along with some helper classes that can be used to quickly create Page Models.  These extensions are similar to CUITe.

In this article, I assume that you are familiar with UI testing and the page model approach (see my previous post on the topic). I will discuss how I've applied page modeling to SPA-type sites and how the fluent syntax can make searching for elements way more terse.

To begin, I would like to stress that the type of testing that should be done in the UI layer should be scoped to testing the UI functionality and behavior. It should not be used to test a system's business logic or any other logic which is outside the scope of the UI. Business logic tests should use Unit Testing. Some of the things we should be testing are:
  1. Does an element show or hide as a response to some user action. Eg, when clicking a close button on a dialog, does the dialog close within 3 seconds.
  2. When an input has an invalid value, does the validation show the user an expected message.
  3. Does a information screen have a valid value for each property. Eg, suppose we have a readonly display of a user's Address information. Do all of the fields have a non-blank value?
  4. Does the navigation follow expected flow? Eg, In a wizard, after completing step 1 and clicking the Save & Continue button with valid form values, does the page transition to step 2?
Things that we will not test include:
  1. After clicking save, did the entry appear in the database?
  2. Did some business-calculated field calculate correctly based on some input?
Of course these are just a few examples of the things we will and will not be testing. So, here's a simple example site:

<html>
  <head>
    <title>Simple automation site</title>
  </head>
  <body>
    <div id="mainBodyContainer">
        <button id="openButton">Open</button>
    </div>
    <div id="hiddenDialog" style="position: absolute; left: 0px; top: 0px; width: 200px; height:200px; display:none; background-color: #666;">
      <span>this is a hidden div</span>
      <button id="closeButton">Close</button>
    </div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script>
        $(function(){
           $('#openButton').click(function(){
               $('#hiddenDialog').show();
               $('#openButton').hide();
            });
           $('#closeButton').click(function(){
               $('#hiddenDialog').hide();
               $('#openButton').show();
            });
         });
    </script>
  </body>
</html>

The test will check that the page loads with the hidden div display:none (not visible on the page). Then, it will search for the open button, click it, and verify that the div becomes visible. Then we'll close the div and check that it disappears. Very simple.

We'd write a test like this using the page model pattern (discussion of the used models afterward): 

[TestMethod]
public ToggleHiddenDivTest()
{
    PageBaseModel pageModel = new PageBaseModel(BrowserWindow.Launch("http://path/to/simple/page"));
    Assert.IsFalse(pageModel.ToggleArea.IsVisible(), "The toggle area should start hidden.");
    ToggleAreaModel toggleModel = pageModel.OpenButton.Click();
    Assert.IsTrue(toggleModel.IsVisible(), "After clicking open, the toggle area should be shown.");
    Assert.IsFalse(String.IsNullOrWhitespace(toggleModel.DisplayText), "The toggle area should have non-empty display text.");
    toggleModel.CloseButton.Click();
    Assert.IsFalse(pageModel.ToggleArea.IsVisible(), "The toggle area should hide after pressing the close button.");
}


The models we'll use represent the various 'controls' on the page. Page modeling originated when web sites were more or less a page to page transition to accomplish some task. Now-a-days, we find SPA like sites where a single page may be used to accomplish a complex task without page transitions; instead we hide and show regions of the page. Any region on a page which represents a well defined control will have page model. You could think of this as 'control' modeling instead, but we'll use the term Page Modeling as it is the standard terminology for this approach.

To start, I usually have a model that represents the entire page and call that the PageBaseModel or something similar to indicate it is a base model for the entire page.

When forming page models, I typically take the approach of protected properties for the elements used to implement some behavior on the control and expose public properties and methods to interact with the control and get state information or transition models.

In this example, the HtmlDocument is protected as it represents some technology specific container for this control. The ClickOpenButton method is used to transition between models and returns a ToggleAreaModel as the result of clicking. This represents the most likely next model that the user will interact with. Finally, there are properties that get the toggle area inner page model as well as the currently displayed text in the toggle box.

public class PageBaseModel
{
    protected readonly BrowserWindow Parent;
    protected HtmlDocument DocumentWindow
    {
      get { return new HtmlDocument(this.Parent); }
    }


    protected HtmlButton OpenButton
    {
        get
        {
            HtmlButton ret = new HtmlButton(this.DocumentWindow);
            ret.SearchProperties.Add(HtmlControl.PropertyNames.Id, "openButton", PropertyExpressionOperator.EqualTo);
            return ret;
        }
    }

    public PageBaseModel(BrowserWindow bw) { this.Parent = bw; }

    public ToggleAreaModel ToggleArea
    {
        get 

        {
            return new ToggleAreaModel(this.Parent);
        }
    }

    public ToggleAreaModel ClickOpenButton() // need another method to test if the button is visible / enabled / has a clickable point....
    {
        Mouse.Click(this.OpenButton);
        return new ToggleAreaModel(this.Parent);
    }

}

public class ToggleAreaModel : PageBaseModel
{
    protected HtmlDiv Me
    {
        get
        {
            var ret = new HtmlDiv(this.DocumentWindow);
            ret.SearchProperties.Add("id", "hiddenDialog", PropertyExpressionOperator.EqualTo);
            return ret;
        }
    }

    protected HtmlSpan DisplayTextSpan
    {
        get
        {
            return new HtmlSpan(this.Me); // only span in the toggle area
        }
    }

    protected HtmlButton CloseButton
    {
        get
        {
            HtmlButton ret = new HtmlButton(this.Me);
            ret.SearchProperties.Add(HtmlControl.PropertyNames.Id, "closeButton", PropertyExpressionOperator.EqualTo);
            return ret;

/*
  Assume there was no Me and you'd like to do the same thing...
*/
            var container = new HtmlDiv(this.DocumentWindow);
            container.SearchProperties.Add("id", "hiddenDialog", PropertyExpressionOperator.EqualTo);

            HtmlButton ret = new HtmlButton(container);
            ret.SearchProperties.Add(HtmlControl.PropertyNames.Id, "closeButton", PropertyExpressionOperator.EqualTo);
            return ret;

        }
    }

    public ToggleAreaModel(BrowserWindow bw):base(bw)
    {
    }

    public string DisplayText
    {
        get { return this.DisplayTextSpan.InnerText; }
    }

    public bool IsVisible()
    {
        return this.Me.TryFind();
    }

    public PageBaseModel ClickCloseButton()
    {
        Mouse.Click(this.CloseButton);
        return new 
PageBaseModel(this.parent);
    }
}


As you can see, most of the properties require at least three lines of code and a variable assignment to return.  If the elements required additional search properties, you'd have lots of typing as each additional property would require it's own statement.  If you need to find elements 'along they way', you have create a variable and the new object and set the search properties so that you can use it in the constructor in the new object........ yikes.

Further, this feels 'backwards' to me as I'm creating the element from which to search and passing in the scope and searching criteria.  I'd prefer to start with a parent scope and create a search tree with the ability to chain elements along the way.  I've created a github repository containing the extensions and they are also available on nuget (under pre-release at time of writing).

With the extensions, I can now write the model as:

    public class ToggleAreaModel : PageBaseModel
    {
        protected HtmlDiv Me
        {
            get { return this.DocumentWindow.Find<htmldiv>("hiddenDialog"); }
        }
        protected HtmlSpan DisplayTextSpan
        {
            get
            {
                 return this.Me.Find<htmlspan>();// only span in the toggle area
            }
        }

        protected HtmlButton CloseButton
        {
            get
            {
                return this.Me.Find<htmlbutton>("closeButton");
/*
  Assume there was no inheritance and you'd like to do the same thing...
*/

                return this.DocumentWindow

                           .Find<htmldiv>("hiddenDialog")
                           .Find<htmlbutton>("closeButton");
            }
        }

        public ToggleAreaModel(BrowserWindow bw):base(bw)
        {
        }

        public string DisplayText
        {
            get { return this.DisplayTextSpan.InnerText; }
        }

        public bool IsVisible()
        {
            return this.Me.TryFind();
        }

        public IClickablePageModel<PageModelBase> CloseButton
        {

            get { return this.CloseButton.AsPageModel(return new PageModelBase(this.parent)); }
        }
    }


Notice how compact it becomes even when chaining additional properties.

There is more to the extension library, but here's the fluent syntax portion.  I'll be diving into the Page Modeling and Wrapper classes in another post.

No comments:

Post a Comment