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();
    }
}

No comments:

Post a Comment