Showing posts with label CodedUI. Show all posts
Showing posts with label CodedUI. Show all posts

Monday, August 24, 2015

Do you use CodedUI for testing responsive webdesign?

Responsive web design has been around for quite some time. Most of the websites created nowadays are built with responsiveness as a main design consideration. But still automating responsive website testing is not adopted in software teams very well. Marcel has a nice post on how to resize the browser window to test responsive websites.

In this post, I’ll try to combine his solution with attributes to specify the view port details and then use this information to test responsive websites.


Use extensions methods to resize Window


public static class BrowserWindowExtensions
{
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hwndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    public static void ResizeWindow(this BrowserWindow control, int width, int height)
    {
        SetWindowPos(control.WindowHandle, (IntPtr)(-1), 0, 0, width, height, 0x0002 | 0x0040);
    }
}

Use attributes to specify ViewPort


[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class BrowserViewPortAttribute : Attribute
{
    public BrowserViewPortAttribute(int width, int height)
    {
        Width = width;
        Height = height;
    }

    public int Width { get; private set; }
    public int Height { get; private set; }
}

The BrowserViewPort attribute can be now used in the CodedUI test class or test methods to define the ViewPort.

[TestMethod]
[TestCategory("Lumia 930")]
[BrowserViewPort(320, 525)]
public void Google_ViewPortSample()
{
    PageFactory
        .Create<GooglePage, GoogleMap, GoogleValidator>("http://www.google.com", TestContext)
        .LookFor("nuget")
        .Search()
        .ValidateNugetResultsLastItemAs1("Hosting Your Own NuGet Feeds");
}

Extend the TestContext to expose ViewPort information


public static class TestContextExtensions
{

    public static BrowserViewPortAttribute GetViewPort(this TestContext context)
    {
        var testClassType = Assembly.GetExecutingAssembly().GetType(context.FullyQualifiedTestClassName);
        var classViewPort = testClassType.GetCustomAttribute<BrowserViewPortAttribute>();
        var testMethodInfo = testClassType.GetMethod(context.TestName);
        if (testMethodInfo == null)
        {
            return classViewPort;
        }
        var methodViewPort = testMethodInfo.GetCustomAttribute<BrowserViewPortAttribute>();
        return methodViewPort ?? classViewPort;
    }
}

Use these in the page factory to create PageObjects


private static TPage CreatePage(string url, TestContext context, bool disposeOnPlayback = true) where TPage : BasePage
    where TMap : BaseMap where TValidator : BaseValidator
{
    var window = BrowserWindow.Launch(new Uri(url));
    if (context != null && context.GetViewPort() != null)
    {
        var viewPort = context.GetViewPort();
        window.ResizeWindow(viewPort.Width, viewPort.Height);
    }
    else
    {
        window.Maximized = true;
    }
    if (!disposeOnPlayback)
    {
        window.CloseOnPlaybackCleanup = false;
    }
    var map = Activator.CreateInstance(typeof (TMap), window) as TMap;
    var validator = Activator.CreateInstance();
    var page = Activator.CreateInstance();
    validator.Map = map;
    page.Map = map;
    page.Validator = validator;
    return page;
}


Saturday, July 18, 2015

Whitepaper - Creating CodedUI tests like an automation Ninja

UI tests allows developers to automate important end-to-end scenarios which can replace the manual regression tests and can be used to provide early feedback on the regression set. While these tests are easy to create using frameworks like CodedUI/ Selenium, one of the major challenges teams faces is to ensure that these tests remain resilient and can be easily maintainable along with the application code. The paper focuses on some of the practices/ patterns that can be used while creating UI automated tests using the CodedUI framework for web applications.


Like the actual production code, test code also has to be used for a long term and maintainable. The idea is to use the same object oriented principles and practices used in programming code for test code also.  You should make use of the SOLID principles and other design patterns to create test code if it has to be useful in the long term.  In this paper we’ll see some of the useful techniques that can be used for structuring the code of the automation tests, along with some tips that can be used to make the tests efficient. The examples used are based on CodedUI, but the concepts can be adapted and used in any similar UI automation framework.

The whitepaper addresses topics like

  • Extension methods on CodedUI objects
  • Using page objects pattern for cleaner code
  • Using fluent api for better readability
  • Page factories to create page objects
  • Reusing the browser window object for faster tests
  • Enabling tracing and HtmlLogger for better debugging and error logging
You can download the whitepaper from the link http://1drv.ms/1OcMP69 

Sunday, April 19, 2015

CodedUI - Attribute based page objects and Jquery selectors to find controls

In UI automation Page Object is a popular design pattern which helps you create UI tests that are easy to maintain and help reducing code duplication. The basic idea behind page objects is that you can create a page class that represents the complete HTML page under test in an object oriented way. For e.g. the controls on the HTML page will be represented by properties in the page class and actions as methods. By doing this it’s very easy to use the benefits of OOPS to create composable menus, headers, footers etc. to reuse those parts in the page objects. In short Page objects encapsulate the behaviors of the HTML page by exposing methods that reflects the user actions and hides the details of telling the browser how to do these things.
Attributes in C# provides a powerful method of associating declarative information in code (types, methods, properties, and so forth). Once associated with a program entity, the attribute can be queried at run time and used in many ways. We'll see how to use attributes in Page objects to provide information on how to find a control on the DOM and later use this in the UI tests. Also we'll see how to use the metadata from the attributes to create JQuery selectors and execute them on the page, using the BrowserWindow.ExecuteScript method to find controls on the page much faster.

Step 1: Creating custom attributes.

Page attribute:

The page attribute is used to provide information related to the page under test, for e.g we’ll use this attribute to provide details like the page url, browser information etc. A simple page attribute looks like.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PageAttribute : Attribute
{
    public PageAttribute(string url)
    {
        Url = url;
    }

    public PageAttribute()
    {

    }

    [Required(AllowEmptyStrings = false, ErrorMessage = "Url is a required property and has to be provided")]
    public string Url { get; set; }
}

Control attribute:

Control attributes are used to provide information related to the ui controls on the page, like the id of the control, class, jquery selectors etc.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class ControlAttribute : Attribute
{
    public string Id { get; set; }

    public string Class { get; set; }

    public string Selector { get; set; }

    public PropertyExpressionOperator IdConditionOperator { get; set; }

    public PropertyExpressionOperator ClassConditionOperator { get; set; }
}

Other attributes

Similarly you can define other custom attributes like a SearchAttribute or a LazyLoad attribute that can be used to find controls based on custom data attributes or to denote that the controls are loaded at a later point of time etc.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class SelectorAttribute : Attribute
{
    public SelectorAttribute(string value)
    {
        Value = value;
    }

    [Required(AllowEmptyStrings = false, ErrorMessage = "Value is a required property")]
    public string Value { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class LazyLoadedAttribute : Attribute
{
       
}

Step 2: Creating the Jquery builder class

The JQueryBuilder class is responsible for building the jquery selector based on the properties defined on the control. For e.g the builder will combine multiple attributes like id, class, tag etc. to a valid jquery selector that can be later used to identify the control on the DOM.
A simple jquery builder can be created as
public class JqueryBuilder
{
    public string Selector { get; private set; }

    public void WithId(string id, PropertyExpressionOperator @operator = PropertyExpressionOperator.EqualTo)
    {
        if (String.IsNullOrEmpty(id))
        {
            return;
        }
        if (@operator == PropertyExpressionOperator.EqualTo)
        {
            Selector = String.Concat(Selector, "#", id);
        }
        else
        {
            SearchBy("id*", id);
        }
    }

    public void WithClass(string name, PropertyExpressionOperator @operator = PropertyExpressionOperator.EqualTo)
    {
        if (String.IsNullOrEmpty(name))
        {
            return;
        }
        if (@operator == PropertyExpressionOperator.EqualTo)
        {
            Selector = String.Concat(Selector, ".", name);
        }
        else
        {
            SearchBy("class*", name);
        }
    }

    public void WithSelector(string selector)
    {
        if (String.IsNullOrEmpty(selector))
        {
            return;
        }
        Selector = selector;
    }

    private void SearchBy(string name, string value)
    {
        Selector = String.Concat(Selector, "[", name, "=", value, "]");
    }      
}

Step 3: Extending the BrowserWindow object to use jquery selectors

Use the ExectuteScript method on the BrowserWindow its now easy to find controls on the page by injecting javascript into the page. In this example we’ll be using jquery to find controls on the page using selectors. But before that you should ensure that jquery is available on the page before using the selectors. The EnsureJqueryIsAvailable method will ensure that jquery is available on the page under test.
private static void EnsureJqueryIsAvailable(this BrowserWindow window)
{
    var needsJquery = (bool) window.ExecuteScript("return (typeof $ === 'undefined')");
    if (needsJquery)
    {
        window.ExecuteScript(@"
            var scheme =  window.location.protocol;
            if(scheme != 'https:')
                scheme = 'http:';
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.src = scheme + '//code.jquery.com/jquery-latest.min.js';
            document.getElementsByTagName('head')[0].appendChild(script);
        ");
    }
}
Next we’ll define two extension methods on the BrowserWindow to find controls using a jquery selector. The Generic find method is used to typecast the control to a generic control.
public static HtmlControl FindBySelector(this BrowserWindow window, string selector)
{
    window.EnsureJqueryIsAvailable();
    var uiControl = window.ExecuteScript(string.Format("return $('{0}')", selector));
    if (uiControl == null)
        return null;

    var hasMultipleResults = uiControl.GetType() == typeof (List<object>);
    if (hasMultipleResults)
    {
        return ((List<object>) (uiControl)).First() as HtmlControl;
    }
    return uiControl as HtmlControl;
}

public static T FindBySelector(this BrowserWindow window, string selector) where T : HtmlControl, new()
{
    window.EnsureJqueryIsAvailable();
    var control = new T {Container = window};
    var tag = control.TagName;
    if (!selector.ToUpper().StartsWith(tag.ToUpper()))
    {
        selector = String.Concat(tag, selector);
    }

    var uiControl = window.ExecuteScript(string.Format("return $('{0}')", selector));
    if (uiControl == null)
        return null;

    var hasMultipleResults = uiControl.GetType() == typeof (List<object>);
    if (hasMultipleResults)
    {
        var collection = (List<object>) uiControl;
        if (collection.Any())
        {
            return collection.First() as T;
        }
    }   
    return uiControl as T;
}

Step 4 : Creating the base page object

The base page object will implement the methods to load a page, parse the attributes and load controls on the page that can be later used to perform tests. The base page object defines the static creation method Create, that is responsible for creating a new instance of the page object and populating the properties with controls on the DOM.
public static TPage Create() where TPage : HtmlPage, new()
{
    var page = new TPage();
    var url = LoadAttributePageAttribute>().Url;
    page.Open(url);
    page.InitializeControls();
    return page;
}
The Open method launches a new browser window based on the url property on the page. The initialiaze controls methods is responsible for control finding and populating the page with the controls on DOM.
The complete base page looks like.
public class HtmlPage
{
    internal BrowserWindow Window { get; private set; }
    public void Open(string url)
    {
        var uri = new Uri(url);
        InitilizePlayback();
        Window = BrowserWindow.Launch(uri);
        Window.Maximized = true;
    }

    internal HtmlControl Find(string selector)
    {
        return Window.FindBySelector(selector);
    }

    internal T Find(string selector) where T : HtmlControl, new()
    {
        return Window.FindBySelector(selector);
    }

    private void InitilizePlayback()
    {
        if (!Playback.IsInitialized)
        {
            Playback.Initialize();
        }
        if (!Playback.IsSessionStarted)
        {
            Playback.StartSession();
        }
        SetSearchSettings(Playback.PlaybackSettings);
        Playback.PlaybackSettings.ContinueOnError = true;
        Playback.PlaybackError += (sender, args) =>
        {
            if (args.Result == PlaybackErrorOptions.Skip) return;
            if (Window != null)
            {
                Window.Close();
            }
        };
    }

    static void SetSearchSettings(PlaybackSettings settings)
    {
        settings.SearchInMinimizedWindows = true;
        settings.SearchTimeout = 10000; //10 seconds
        settings.ShouldSearchFailFast = true;
        settings.WaitForReadyLevel = WaitForReadyLevel.UIThreadOnly;
        settings.WaitForReadyTimeout = 5000;
    }

    internal void InitializeControls(bool lazyLoaded = false)
    {
        var map = new PropertyInfoToControlMap();

        //Find all properties on page with the control attribute
        var controlProperties = GetType().GetProperties()
            .Where(prop => prop.IsDefined(typeof(ControlAttribute), false)).ToList();

        var controls = map.From(controlProperties).Where(c => c.IsLazyLoaded == lazyLoaded);
        foreach (var control in controls)
        {
            var property = controlProperties.FirstOrDefault(c => c.Name == control.Name);
            MethodInfo findMethodInfo;
            if (property.PropertyType != typeof (HtmlControl))
            {
                findMethodInfo =
                    typeof (HtmlPage).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
                        .First(m => m.Name == "Find" && m.IsGenericMethod);
                findMethodInfo = findMethodInfo.MakeGenericMethod(property.PropertyType);
            }
            else
            {
                findMethodInfo =
                    typeof(HtmlPage).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
                        .First(m => m.Name == "Find");
            }
               
            var htmlControl = findMethodInfo.Invoke(this, new object[] { control.Selector });
            if (htmlControl == null && !control.IsLazyLoaded)
                throw new ArgumentException(String.Format("Page control {0} does not exist", control.Name));
            if (!htmlControl.GetType().IsAssignableFrom(property.PropertyType)) continue;
            property.SetValue(this, htmlControl);
        }
    }

    public void Refresh()
    {
        InitializeControls(true);
    }

    public static TPage Create() where TPage : HtmlPage, new()
    {
        var page = new TPage();
        var url = LoadAttributePageAttribute>().Url;
        page.Open(url);
        page.InitializeControls();
        return page;
    }

    private static T LoadAttribute()
        where TPage : HtmlPage
        where T : Attribute
    {
        var attribute = Attribute.GetCustomAttribute(typeof(TPage), typeof(T));           
        return attribute as T;
    }
}

Step 5: Translating the attributes to a control object.

The PropertyInfoToControlMap  object is responsible to create a control object with the selector property by parsing the attributes and building a selector using the JqueryBuilder class.
internal class Control
{
    public Control(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
    public string Selector { get; set; }
    public bool IsLazyLoaded { get; set; }
}
internal class PropertyInfoToControlMap : IMap<Control, PropertyInfo>
{
    public IList<Control> From(IList<PropertyInfo> source)
    {
        return source.Select(ToControl).ToList();
    }

    private Control ToControl(PropertyInfo property)
    {
        var control = new Control(property.Name);
        var controlAttribute = property.GetCustomAttribute<ControlAttribute>(false);
        var builder = new JqueryBuilder();
        builder.WithId(controlAttribute.Id);
        builder.WithClass(controlAttribute.Class);
       
        var selectorAttribute = property.GetCustomAttribute<SelectorAttribute>(false);
        if (selectorAttribute != null)
        {
            builder.WithSelector(selectorAttribute.Value);
        }
        control.Selector = builder.Selector;
        control.IsLazyLoaded = property.GetCustomAttribute<LazyLoadedAttribute>(false) != null;
        return control;
    }
}

Step 6: Creating your page object.

Now you are ready to create your page objects using the objects created in the previous steps. Let’s see how a sample page object looks like
[Page(Url = "http://www.blogsprajeesh.blogspot.com/")]
public class BloggerPage : HtmlPage
{
    [Control]
    [Selector("h1 .title")]
    public HtmlControl Title { get; set; }

    [Control(Id = "Image2_img")]
    public HtmlImage ProfilePicture { get; set;  }
       
    public void EnsureTitleOnPage()
    {
        Assert.IsNotNull(Title);
    }

    public LinkedInPage NavigateToLinkedInPage()
    {
        Mouse.Click(ProfilePicture);
        Refresh();
        return new LinkedInPage();
    }

    public BloggerPage EnsureProfilePictureOnPage()
    {
        Assert.IsNotNull(ProfilePicture);
        return this;
    }
}

Step 7: Creating the tests

Using the page objects created, you can now write the tests like.
[CodedUITest]
public class BloggerPageTests
{
    [TestMethod]
    public void BlogHome_ShouldHaveATitleOnPage()
    {
        HtmlPage.Create<BloggerPage>()
            .EnsureTitleOnPage();
    }

    [TestMethod]
    public void BlogProfile_ShouldTakeToLinkedInPage()
    {
        HtmlPage.Create<BloggerPage>()
            .EnsureProfilePictureOnPage()
            .NavigateToLinkedInPage();
    }
}