Click here to Skip to main content
15,881,172 members
Articles / DevOps / Testing

Fluent Page Object Pattern in Automation Testing

Rate me:
Please Sign up or sign in to vote.
4.83/5 (8 votes)
25 Feb 2016Ms-PL4 min read 18.3K   14  
Overview how to achieve better readability of page objects via fluent page object pattern. Use chaining methods to access the business test logic.

Introduction

In my previous articles from the series “Design Patterns in Automation Testing“, I explained in details how to improve your test automation framework through the implementation of Page Objects, Facades and Singletons. Here I am going to extend further the ideas of the Page Object Pattern. More efficient usage and improved readability are achievable through the incorporation of the Page Objects with Fluent API. The result will be Fluent Page Objects or Fluent Page Object Pattern.

Image 1

Fluent Interface

Definition by Wikipedia

In software engineering, a fluent interface (as first coined by Eric Evans and Martin Fowler) is an implementation of an object-oriented API that aims to provide the most readable code.

A fluent interface is typically implemented by using method cascading (concretely method chaining) to relay the instruction context of a subsequent call (but a fluent interface entails more than just method chaining [1]). The context is

  • Defined through the return value of a called method-self-referential, where the new context is equivalent to the last context
  • Self-referential, where the new context is equivalent to the last contest terminated by the return of a void context.
  • Terminated by the return of a void context.

UML Class Diagram

Image 2

Participants

The classes and objects participating in this pattern are:

  • Page Objects (BingMainPage)- Holds the actions that can be performed on the page like Search and Navigate. It exposes an easy access to the Page Validator through the Validate() method. The best implementations of the pattern hide the usage of the Element Map, wrapping it through all action methods.
  • BasePage<S, M> – Gives access to the child’s page element map class and defines a standard navigation operation.
  • BasePage<S, M, V> – Adds an instance to the child page’s validator class through the Validate method.
  • BaseSingleton – This is an abstract class that contains a static property of its child instance BaseSingleton – This is an abstract class that holds a static property of its child instance.
  • BaseElementMap – Provides easier access to current browser and functions to switch between different frames.
  • BasePageValidator<S, M, V> – Gives all child validators instance to the current element map and the page object itself.

Fluent Page Object Pattern C# Code

Test’s Test Case

The primary goal of the example test for the Fluent Page Object Pattern is going to be to search for images in Bing with different settings.

1. Navigate to Bing site

2. Search for Term

3. Switch to Images tab

4. Change query settings

Image 3

Fluent Page Objects Implementation Code

If we don’t use Fluent Page Objects, our test looks like the code below.

C#
[TestClass]
public class FluentBingTests
{ 
    [TestInitialize]
    public void SetupTest()
    {
        Driver.StartBrowser();
    }

    [TestCleanup]
    public void TeardownTest()
    {
        Driver.StopBrowser();
    }

    [TestMethod]
    public void SearchForImageNotFuent()
    {
        P.BingMainPage bingMainPage = new P.BingMainPage();
        bingMainPage.Navigate();
        bingMainPage.Search("facebook");
        bingMainPage.ClickImages();
        bingMainPage.SetSize(Sizes.Large);
        bingMainPage.SetColor(Colors.BlackWhite);
        bingMainPage.SetTypes(Types.Clipart);
        bingMainPage.SetPeople(People.All);
        bingMainPage.SetDate(Dates.PastYear);
        bingMainPage.SetLicense(Licenses.All);
    }
}

The primary goal of the Fluent Page Object Pattern is to enable you to use the power of method chaining. To achieve it, the BingMainPage should be slightly modified.

C#
public class BingMainPage : BaseFluentPageSingleton<BingMainPage, BingMainPageElementMap, BingMainPageValidator>
{
    public BingMainPage Navigate(string url = "http://www.bing.com/")
    {
        base.Navigate(url);
        return this;
    }

    public BingMainPage Search(string textToType)
    {
        this.Map.SearchBox.Clear();
        this.Map.SearchBox.SendKeys(textToType);
        this.Map.GoButton.Click();
        return this;
    }

    public BingMainPage ClickImages()
    {
        this.Map.ImagesLink.Click();
        return this;
    }

    public BingMainPage SetSize(Sizes size)
    {
        this.Map.Sizes.SelectByIndex((int)size);
        return this;
    }

    public BingMainPage SetColor(Colors color)
    {
        this.Map.Color.SelectByIndex((int)color);
        return this;
    }

    public BingMainPage SetTypes(Types type)
    {
        this.Map.Type.SelectByIndex((int)type);
        return this;
    }

    public BingMainPage SetLayout(Layouts layout)
    {
        this.Map.Layout.SelectByIndex((int)layout);
        return this;
    }

    public BingMainPage SetPeople(People people)
    {
        this.Map.People.SelectByIndex((int)people);
        return this;
    }

    public BingMainPage SetDate(Dates date)
    {
        this.Map.Date.SelectByIndex((int)date);
        return this;
    }

    public BingMainPage SetLicense(Licenses license)
    {
        this.Map.License.SelectByIndex((int)license);
        return this;
    }
}

The most important part of the above code is that all methods return the current instance of the page.

C#
return this;

Not quietly related to the pattern itself but interesting to point here is the way of choosing the different options. All of them are of type SelectElement. Here are the different settings elements that you can discover in the BingMainPageMap.

C#
public SelectElement Sizes
{
    get
    {
        return new SelectElement(this.browser.FindElement(By.XPath("//div/ul/li/span/span[text() = 'Size']")));
    }
}

public SelectElement Color
{
    get
    {
        return new SelectElement(this.browser.FindElement(By.XPath("//div/ul/li/span/span[text() = 'Color']")));
    }
}

public SelectElement Type
{
    get
    {
        return new SelectElement(this.browser.FindElement(By.XPath("//div/ul/li/span/span[text() = 'Type']")));
    }
}

public SelectElement Layout
{
    get
    {
        return new SelectElement(this.browser.FindElement(By.XPath("//div/ul/li/span/span[text() = 'Layout']")));
    }
}

public SelectElement People
{
    get
    {
        return new SelectElement(this.browser.FindElement(By.XPath("//div/ul/li/span/span[text() = 'People']")));
    }
}

public SelectElement Date
{
    get
    {
        return new SelectElement(this.browser.FindElement(By.XPath("//div/ul/li/span/span[text() = 'Date']")));
    }
}

public SelectElement License
{
    get
    {
        return new SelectElement(this.browser.FindElement(By.XPath("//div/ul/li/span/span[text() = 'License']")));
    }
}

All of them use the same location technique- XPath expression that finds the div by its inner text.

I believe that it is a poor decision to switch settings through text variable, so in my implementation I use enums.

Sample Settings Enum

C#
public enum Dates
{
    All,
    Past24Hours,
    PastWeek,
    PastMonth,
    PastYear
}

This way the chosen enum value represents an integer from 0-4 that is the same as the index of the same values in the select element.

C#
public BingMainPage SetDate(Dates date)
{
    this.Map.Date.SelectByIndex((int)date);
    return this;
}

Create Fluent Page Validator

In order to keep the method chaining available even after a usage of validation methods, a modification of the BasePageValidator is mandatory too.

Image 4

Not Fluent Version

C#
public class BasePageValidator<M>
    where M : BasePageElementMap, new()
{
    protected M Map
    {
        get
        {
            return new M();
        }
    }
}

Fluent Version

C#
public class BasePageValidator<S, M, V>
    where S : BaseFluentPageSingleton<S, M, V>
    where M : BasePageElementMap, new()
    where V : BasePageValidator<S, M, V>, new()
{
    protected S pageInstance;

    public BasePageValidator(S currentInstance)
    {
        this.pageInstance = currentInstance;
    }

    public BasePageValidator()
    {
    }

    protected M Map
    {
        get
        {
            return new M();
        }
    }
}

All changes are applied to provide us access to the current Page instance, in order to be able to return it every time in our validation methods.

Not Fluent BingMainPageValidator

C#
public class BingMainPageValidator : BasePageValidator<BingMainPageElementMap>
{
    public void ResultsCount(string expectedCount)
    {
        Assert.IsTrue(this.Map.ResultsCountDiv.Text.Contains(expectedCount), "The results DIV doesn't contains the specified text.");
    }
}

Fluent BingMainPageValidator

C#
public class BingMainPageValidator : BasePageValidator<BingMainPage, BingMainPageElementMap, BingMainPageValidator>
{
    public BingMainPage ResultsCount(string expectedCount)
    {
        Assert.IsTrue(this.Map.ResultsCountDiv.Text.Contains(expectedCount), "The results DIV doesn't contains the specified text.");
        return this.pageInstance;
    }
}

Because of the changes in the BasePageValidator the BagePage classes should be modified to support the new fluent base validator.

C#
public abstract class BaseFluentPageSingleton<S, M> : ThreadSafeNestedContructorsBaseSingleton<S>
    where M : BasePageElementMap, new()
    where S : BaseFluentPageSingleton<S, M>
{
    protected M Map
    {
        get
        {
            return new M();
        }
    }

    public virtual void Navigate(string url = "")
    {
        Driver.Browser.Navigate().GoToUrl(string.Concat(url));
    }
}

public abstract class BaseFluentPageSingleton<S, M, V> : BaseFluentPageSingleton<S, M>
    where M : BasePageElementMap, new()
    where S : BaseFluentPageSingleton<S, M, V>
    where V : BasePageValidator<S, M, V>, new()
{
    public V Validate()
    {
        return new V();
    }
}

Fluent Page Objects Usage in Tests

C#
[TestClass]
public class FluentBingTests
{ 
    [TestInitialize]
    public void SetupTest()
    {
        Driver.StartBrowser();
    }

    [TestCleanup]
    public void TeardownTest()
    {
        Driver.StopBrowser();
    }

    [TestMethod]
    public void SearchForImageFuent()
    {
        P.BingMainPage.Instance
                            .Navigate()
                            .Search("facebook")
                            .ClickImages()
                            .SetSize(Sizes.Large)
                            .SetColor(Colors.BlackWhite)
                            .SetTypes(Types.Clipart)
                            .SetPeople(People.All)
                            .SetDate(Dates.PastYear)
                            .SetLicense(Licenses.All);
    }
}

The fluent page objects significantly improve the readability of tests. Also, it is quite easy to write tests, thanks to the method chaining.

So Far in the "Design Patterns in Automated Testing" Series

  1. Page Object Pattern
  2. Advanced Page Object Pattern
  3. Facade Design Pattern
  4. Singleton Design Pattern
  5. Fluent Page Object Pattern
  6. IoC Container and Page Objects
  7. Strategy Design Pattern
  8. Advanced Strategy Design Pattern
  9. Observer Design Pattern
  10. Observer Design Pattern via Events and Delegates
  11. Observer Design Pattern via IObservable and IObserver
  12. Decorator Design Pattern- Mixing Strategies
  13. Page Objects That Make Code More Maintainable
  14. Improved Facade Design Pattern in Automation Testing v.2.0
  15. Rules Design Pattern
  16. Specification Design Pattern
  17. Advanced Specification Design Pattern

 

If you enjoy my publications, feel free to SUBSCRIBE
Also, hit these share buttons. Thank you!

Source Code

References

All images are purchased from DepositPhotos.com and cannot be downloaded and used for free. License Agreement

The post- Fluent Page Object Pattern in Automation Testing appeared first on Automate The Planet.

This article was originally posted at http://automatetheplanet.com/fluent-page-object-pattern

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
CEO Automate The Planet
Bulgaria Bulgaria
CTO and Co-founder of Automate The Planet Ltd, inventor of BELLATRIX Test Automation Framework, author of "Design Patterns for High-Quality Automated Tests: High-Quality Test Attributes and Best Practices" in C# and Java. Nowadays, he leads a team of passionate engineers helping companies succeed with their test automation. Additionally, he consults companies and leads automated testing trainings, writes books, and gives conference talks. You can find him on LinkedIn every day.

Comments and Discussions

 
-- There are no messages in this forum --