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.
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:
- Defining High-Quality Test Attributes for Automated Tests
- Benchmarking for Assessing Automated Test Components Performance
- Generic Repository Design Pattern- Test Data Preparation
Happy Testing! 🚀
Interested in more? Check out the next chapter about the Singleton design pattern.