In the first article from the series, we discussed a strategy to create more maintainable and readable page object models in Selenium WebDriver tests. We used the Fluent Interface design pattern for achieving maximum API usability. Next, we talked about another API usability pattern called Singleton for creating only once page object models and afterward reusing them. In this one, we will discuss issues causing your tests to fail once in a while, such as opening pages fast and not all elements are ready for interaction. If you try to act on them while they are still loading – an error will occur ❌
A standard way to handle the situation is to wait for an element that takes the most time to load on the page and wait for it to be visible before performing any actions. However, we cannot put this logic in the base class since each page’s unique element is different. To handle this, we can use the Template Method design pattern.
Test Case
We will automate the main Bing page. We will write the logic for using the advanced images’ filtering options.
After that, we will open the detailed page about specific image results and verify that the correct title and URL are displayed.
In the previous article, we had only a single page object called BingMainPage containing all of the logic. However, for this extended scenario, we will create an additional page called ResultDetailedPage. In many cases, we don’t need to navigate to a particular page because it is just part of the workflow, the same way the results page right now is part of the search workflow. We cannot go to it directly only by navigating. This is why we will create two separate base classes in the solution, one for the navigatable pages and another for simpler pages. We will enhance the navigatable pages with the Template Method Design Pattern’s integration so that we are sure the page is fully loaded before we start interacting with it, avoiding any potential errors.
Template Method Design Pattern
Definition:
The Template Method design pattern defines a skeleton/structure of an algorithm in a base class and then leaves its specific implementation to the child classes.
UML Class Diagram:
Participants:
The objects participating in this pattern are:
- Abstract Class – defines the structure of the algorithm, usually uses abstract protected methods and a public method containing the algorithm.
- Concrete Class – inherits the abstract class and implements the abstract methods as part of the defined algorithm.
Template Method Design Pattern Implementation
To incorporate the Template Method design pattern, we can include an abstract method in the navigatable page class. Its child classes can implement it and wait for their specific element to be displayed.
First, I created a base class for all pages that we cannot go to directly. We reuse two components – the Driver instance and the GetElements method.
public abstract class WebPage<TElements> where TElements : WebElements { protected readonly IWebDriver Driver; protected WebPage(IWebDriver driver) { Driver = driver; } protected TElements GetElements() { return (TElements)Activator.CreateInstance(typeof(TElements), new object[] { Driver }); } }
Notice that we marked the class as abstract and as generic. The generic parameter is the type of our elements class, which contains all elements locators. Some people call it an element map. We need the generic parameter so that we can adequately implement the GetElements method. You will notice a piece of more complicated code inside the method’s body. It uses the so-called Reflection API for creating an instance of the elements’ type by calling its constructor and passing the Driver instance as a parameter.
NOTE: Reflection API in C# and Java allows you to examine the objects at runtime, manipulate internal properties, dynamically create an instance of a type, invoking its methods, or accessing its fields or properties no matter of their access modifiers.
In the where clause we said that the elements’ type needs to derive from the base class called WebElements. This statement is called a generic constraint.
public abstract class WebElements { protected readonly IWebDriver Driver; protected WebElements(IWebDriver driver) { Driver = driver; } }
Here is how we use it. We use it, for now, to reuse the IWebDriver instance again. In the future, we can add helper methods for easing the finding of elements.
public partial class MainPageElements : WebElements { public MainPageElements(IWebDriver driver) : base(driver) { } public IWebElement GetSearchBox() { return Driver.FindElement(By.Id("sb_form_q")); } public IWebElement GetResultsCountDiv() { return Driver.FindElement(By.Id("b_tween")); } // rest of the elements }
Shall we discuss now the exciting part about integrating the Template Method Design Pattern? 😉 As we discussed we need a separate base page class for the pages to which we can navigate directly. Such a class will hold a property to the URL of the page and a method for navigating.
public abstract class NavigatableWebPage<TElements> : WebPage<TElements> where TElements : WebElements { private WebDriverWait _webDriverWait; protected NavigatableWebPage(IWebDriver driver) : base(driver) { _webDriverWait = new WebDriverWait(driver, TimeSpan.FromSeconds(30)); } protected abstract string Url { get; } public TPage Open<TPage>() { Driver.Navigate().GoToUrl(Url); WaitForPageLoad(); return (TPage)Activator.CreateInstance(typeof(TPage), new object[] { Driver }); } protected void WaitForElementToExists(By by) { var js = (IJavaScriptExecutor)Driver; _webDriverWait.Until(ExpectedConditions.ElementExists(by)); } protected abstract void WaitForPageLoad(); }
To incorporate the pattern, we can include an abstract method in the navigatable page class. Its child classes can implement it and wait for their specific element to be displayed. After we open the page, we will wait for it to be fully loaded through the abstract WaitForPageToLoad. Each child class will be responsible for specifying what “fully loaded” means.
The usual approach is to wait for a particular element on the page is visible. This is why I added the helper protected method WaitForElementToExists. We can use it later in the concrete page models to wait for their specific web components to appear. Sometimes instead of waiting for an element to show up, you can wait for all AJAX or Angular requests to finish.
Lastly, the navigatable base class is marked again as abstract and generic. To follow the fluent API we developed in the first article from the series, we also use the Reflection API to create an instance of the page itself. We have the page type since I marked the Open method as generic.
This is how the MainPage changed:
public class MainPage : NavigatableWebPage<MainPageElements> { public MainPage(IWebDriver driver) : base(driver) { } protected override string Url => "http://www.bing.com/"; protected override void WaitForPageLoad() { WaitForElementToExists(By.Id("sb_form_q")); } public MainPage Search(string textToType) { GetElements().GetSearchBox().Clear(); GetElements().GetSearchBox().SendKeys(textToType); GetElements().GetSearchBox().SendKeys(Keys.Enter); return this; } // rest of the code }
The important part is that we override the URL and set it to the correct one. We also implemented the WaitForPageLoad, where we wait for the main search box to be displayed before starting acting on the page.
Here is how our test looks:
[TestMethod] public void AssertSearchImageResults() { _bingPage .Open<MainPage>() .Search("automate the planet") .ClickImages() .SetSize(Sizes.Large) .SetColor(Colors.BlackWhite) .SetTypes(Types.Clipart) .SetPeople(People.All) .SetDate(Dates.PastYear) .SetLicense(Licenses.All) .ClickImageResult(1); _resultDetailedPage.AssertResultTitle("Homepage - Automate The Planet") .AssertResultLink("https://www.automatetheplanet.com/") .ClickVisitSiteButton(); Assert.AreEqual("https://www.automatetheplanet.com/", _driver.Url); }
Summary
Separating the base page class into two separate classes made our tests much more maintainable. But why? 🤔 The maintainability is connected with the future costs of fixing broken tests and adding new logic for automating a new website feature. The refactoring that we made will ease the addition of new logic. If we had left the previous implementation, this would mean that each time you need to change the navigation logic, you would change a single base class but at the same time affect all pages that should not be able to be opened directly. The changes helped us to follow the Single Responsibility Principle more closely as well.
In the next part of the series, we will focus on another common problem in automated tests – test workflows.
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! 🚀