logo logo

Design Patterns for High-Quality Automated Tests: Fluent API

main post image

In this tutorial, we’ll discuss a strategy to create more maintainable and readable page object models in Selenium WebDriver tests. We will use the Fluent Interface design pattern and achieve maximum API usability. The fluent API is usually implemented through method chaining (method cascading).

Fluent Interface Definition

The Fluent Interface was first defined by Eric Evans and Martin Fowler as an implementation of an OOP API that gives the user a more concise and readable code.

Test Case

We will automate the main Bing page. We will write logic for using the advanced images’ filtering options.

fluent-page-objects-partial-classes

Page Objects using Fluent API Code

For each filter option, we have a dedicated enum- Colors, Dates, Layouts, Licenses, People, Sizes and Types.

public enum Colors
{
    All,
    ColorOnly,
    BlackWhite,
    Red,
    Orange,
    Yellow,
    Green
}

We will use these enums in the primary class of our page. The code of the rest of the enums is identical.

BingMainPage

Most of the differences compared to the other implementations of the pattern are located in this file.

public partial class BingMainPage
{
    private readonly IWebDriver _driver;
    private readonly string _url = @"http://www.bing.com/";

    public BingMainPage(IWebDriver browser)
    {
        _driver = browser;
    }

    public BingMainPage Navigate()
    {
        _driver.Navigate().GoToUrl(_url);
        return this;
    }

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

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

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

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

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

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

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

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

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

Everything stays the same with the difference that each service method now returns the instance of the page itself. This way the fluent syntax is supported.

❗ Note: I put the assertion methods here as well. It is suggested to add them if you are going to use them in more than one test, especially if you have set a detailed exception method.

Also, you will notice that we have a second class called BingMainPageElements and we use it through composition. We initialize it in the constructor, assigning it to a private variable that we use later in the methods to access all the elements.

BingMainPageElements

public partial class BingMainPageElements
{
    private readonly IWebDriver _driver;

    public BingMainPageElements(IWebDriver driver)
    {
        _driver = driver;
    }

    public IWebElement GetSearchBox()
    {
        return _driver.FindElement(By.Id("sb_form_q"));
    }

    public IWebElement GetGoButton()
    {
        return _driver.FindElement(By.Id("sb_form_go"));
    }

    public IWebElement GetResultsCountDiv()
    {
        return _driver.FindElement(By.Id("b_tween"));
    }

    public IWebElement GetImagesLink()
    {
        return _driver.FindElement(By.LinkText("Images"));
    }

    public SelectElement GetSizesSelect()
    {
        return new SelectElement(_driver.FindElement(By.XPath("//div/ul/li/span/span[text() = 'Size']")));
    }

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

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

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

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

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

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

The map does not contain any differences compared to the other versions. Here, the file contains the various elements present in the advanced filtering menu. In the map, I usually use C# properties. However, I converted them to getter methods like Java and other OOP languages that do not have such concepts as this version’s properties. So, you are free to convert these getters to properties if you like. Even if you use the new expression body syntax, you will fit all these elements in one screen, which is much more concise and readable.

Fluent API in Tests

[TestClass]
public class FluentBingTests
{
    private IWebDriver _driver;
    private BingMainPage _bingPage;

    [TestInitialize]
    public void SetupTest()
    {
        _driver = new FirefoxDriver();
        _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(30);
        _bingPage = new BingMainPage(_driver);
    }

    [TestCleanup]
    public void TeardownTest()
    {
        _driver.Quit();
    }

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

As you can observe in the code above, we do not call the methods in separate calls. Instead, we create a single chain of methods to create the test case. Some people believe that this way the writing process is simplified and the code more readable. I am a little bit skeptical, but you can try it 😉

For a more detailed overview and usage of many more design patterns and best practices in automated testing, check my book “Design Patterns for High-Quality Automated Tests, C# Edition, High-Quality Tests Attributes, and Best Practices“.  You can read part of three of the chapters here:

Happy Testing! 🚀
Interested in more? Check out the next chapter about the Singleton design pattern.

Anton S. Angelov

About the author

Anton S. Angelov

CTO and Co-founder of Automate The Planet Ltd, inventor of BELLATRIX Test Automation Framework and MEISSA Distributed Test Runner. I have more than ten years in the field of automated testing. For more than six years I worked as QA architect in two big companies- Telerik (back then the biggest software company in Bulgaria) and later in US company called Progress (similar in size). Part of my job was to design and write scalable test automation framework that more than ten teams had to use. In parallel, I consulted a couple of companies regarding test automation and led several related pieces of training. I was nominated four times for best QA in Bulgaria 2017, 2018 (won), 2019 and 2020.

Author of the book- “Design Patterns for High-Quality Automated Tests, C# Edition, High-Quality Tests Attributes, and Best Practices” (#1 New Release, Best Seller under Quality Control on Amazon)

I am an international conference speaker spoke at events (such as Selenium Conf, Appium Conf, Heisenbug) in Russia, India, Romania, Serbia, Netherlands, Poland, Ukraine and many more. Won a couple of times best paper award. Code Project MVP for 2016-2018 and DZone most valuable blogger with more than 150 articles with over 3 million views. I am writing technical articles each week for Automate The Planet Blog for past five years (250+). Last year the website was mentioned five times as one of the top 10 testing blogs in the world. Articles of mine were published in the previous five editions of the Quality Matters Testing Magazine.

– 280+ Published Articles Automate The Planet
– 120+ Published Articles Code Project
– 60+ Published Articles DZone as Most Valuable Blogger
– 6+ Articles Published in Quality Magazines
– 20+ Given International Conferences Talks
– 2 books published
– 5,000,000+ article views
– 1000 000+ amazing readers for 2020
– Read in 180+ countries

Join TestProject Community

Get full access to the world's first cloud-based, open source friendly testing community. Enjoy TestProject's end-to-end test automation Platform, Forum, Blog and Docs - All for FREE.

Join Us Now  

Leave a Reply

popup image

Selenium for Teams

Sharing and distributing Selenium tests has never been so easy! With TestProject's FREE Selenium based platform, you can finally create awesome tests with the freedom to collaborate with your team effortlessly.
Sign Up Now right arrow
FacebookLinkedInTwitterEmail