Saturday, July 26, 2014

Web UI Testing Part 4: Extension methods in Page Object Model

When I started my next project I switched from WatiN to Selenium, and I incorporated the PageObjectModel.  I had recently watched John Somnez's video's Pluralsight videos around this topic (http://simpleprogrammer.com/2013/09/28/creating-automated-testing-framework-selenium/) , so a lot of his ideas were shining through.  There was a Pages class which had static properties to all of the Page objects.

Here are some of the highlights of that solution.  We created some additional extension methods for any web element to be able to perform some common functions.  Because Selenium's FindElement normally only looks under an element, and we needed a way of looking above an element, we modified this hack using XPath parent axis. Another really useful function is the ability to extract table information.
   
    public static class WebElementExtensions
    {
        public static IWebElement GetParent(this IWebElement element)
        {
            return element.FindElement(By.XPath("parent::*"));
        }

       public static IWebElement FindParentByClassName(this IWebElement element, string className)
        {
            if (element == null)
            {
                return null;
            }

            var classValue = element.GetAttribute("class");
            if (classValue.Contains(className))
            {
                return element;
            }

            return FindParentByClassName(element.GetParent(), className);
        }

        public static List<string[]> ToTable(this IWebElement element)
        {
            var rows = new List<string[]>();
            foreach (var tr in element.FindElements(By.TagName("tr")))
            {
                var thOrTds = tr.FindElements(By.TagName("th")).Union(tr.FindElements(By.TagName("td")));
                rows.Add(thOrTds.Select(c => c.Text).ToArray());
            }

            return rows;
        }

In addition to the normal page object model there are often times menus, or toolbars, that cross pages.  The original way we did this was just to use the Base classes, but we soon started needing the base classes for things like steps in a wizard.  So instead we moved those to extensions as well, based off the BasePage.  So when we created a new page that used an exiting menu partial we could use the extension methods to call those the methods easily without any modifications.  We found the easiest way to do this was based off empty interfaces, because extension methods don't really support attributes and we needed someway of describing which extension methods were legal on which objects.

public interface IHaveAdminMenu
{
}

public static class AdminMenuExtensions
{
    public static void AdminMenuClickItems(this IHaveAdminMenu adminMenu)
    {
        var basePage = (BasePage) adminMenu;
        basePage.Driver.FindElement(By.Id("itemsLink")).Click();
    }
}

Sunday, July 6, 2014

Web UI Testing Part 3: The Page Object Model

Whether you end up WatiN or Selenium for automating the browser actually doesn't matter that much.  Whichever mechanism you use should be hidden behind a Page Object Model.  This actually took me a while to discover because it wasn't really in your face on the WatiN and Selenium forums.  In fact even once I knew about the pattern I didn't feel the need for it at first.  It was similar to having a domain controller for a couple of computers.  However, as the sites I was writing and testing got more complicated, I needed a way of organizing the methods to manipulate the pages into a logical grouping.  It makes sense to make an object model that encapsulates the ID, classes, tags, etc. inside a page so that they can be reused easily.  Let's look at a simple example in WatiN, prior to putting in the Page Object Model.

[Given(@"I am on an item details page")]
public void GivenIAmOnAnItemDetailsPage()
{
browser = new IE("http://localhost:12345/items/details/1?test=true");
}

[When(@"I update the item information")]
public void WhenIUpdateTheItemInformation()
{
browser.TextField(Find.ByName("Name")).TypeTextQuickly("New item name");
browser.TextField(Find.ByName("Description")).TypeTextQuickly("This is the new item description");
var fileUpload = browser.FileUpload(Find.ByName("pictureFile"));
string codebase = new Uri(GetType().Assembly.CodeBase).AbsolutePath;
string baseDir = Path.GetDirectoryName(codebase);
string path = Path.Combine(baseDir, @"..\..\DM.png");
fileUpload.Set(Path.GetFullPath(path));

The ?test=true in the first method is interesting, but the subject of another blog post.  Instead Notice the Find.ByName("Name") in the second method.  Now what if there is another method where I need to check the name to see what is there.  And yet another where I need to both check it *and* update it.  So I would have three places and four lines where that Find.ByName("Name") would be used.

What happens when I change the element to have a different name?  Every test where I have used Find.ByName("Name") breaks.  I have to go through and find them all and update them. 

Let's look at the same two methods, but this time with a PageObject model.

[Given(@"I am on an item details page")]
public void GivenIAmOnAnItemDetailsPage()
{
browser = new IE(Pages.ItemDetails.Url);
}

[When(@"I update the item information")]
public void WhenIUpdateTheItemInformation()
{
Pages.ItemDetails.SetName("New item name");
Pages.ItemDetails.SetDetails("This is the new item description");
Pages.ItemDetails.SetPictureFile("DM.png");

A couple of interesting things happened.  The first is that the test is a lot more readable.  The second is that I now have a central place to change when something from the page changes.  I fix one line, and now all of the tests are running again.


So to recap, Page Object Models are great when either the pages are volatile or the same pages are being used for lots of different tests.