Self-Logging Selenium WebDriver Tests

Alex Siminiuc
Nov 2 · 13 min read
Photo by Markus Spiske on Unsplash

One of the characteristics of a good Selenium test automation project is the ability of troubleshooting quickly potential problems.

This means finding fast

  • the method (or assertion) that caused the issue
  • what happened before the code execution stopped
  • what is the last visited web page
  • what are the exception details.

The method that caused the issue is visible in the stack trace generated when an assertion fails or an unexpected exception is thrown.

Stack trace shows also the name of the failed test and all methods that lead to the failed method.

Screenshots help seeing the page where the execution stopped.

In addition to all these details, it is useful to have a log that shows everything that the automated test did from the moment it started until it stopped.

Which brings us to logs.

There are multiple types of logs that can be used in a Selenium project.

  • Selenium Logs
  • Exception Logs
  • Test Execution Logs

I will cover the Selenium and exception logs in other posts.

The type of log that I will focus on here is the test execution log.


The execution logs are useful for seeing everything that the automated test executed before it finished successfully or with an error.

There are multiple ways of generating them:

  • by adding a logging call to each page class method
  • by using an EventFiringDriver
  • by using HtmlElement classes

We will go over each option separately with a very simple automated test for the following test case:

  • open the home page of www.autotrader.ca
  • select a vehicle make
  • select a vehicle model
  • select a price
  • enter a postal code
  • execute the search
  • verify that on results page, the title is correct

Add a logging call to each page class method

This type of logging is done at the page object method level.

This is the code of the test class:

using Logging.basic;using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System;
namespace Logging.Basic
{
[TestFixture]
public class TestClass_basic_logging {
private IWebDriver driver;

private const String MAKE = “Audi”;
private const String MODEL = “A4”;
private const String PRICE = “$40,000”;
private const String POSTAL_CODE = “V6J5M9”;
ILogger logger = new Logger();

[SetUp]
public void setUp() {
driver = new ChromeDriver();
driver.Manage().Window.Maximize();
}
[TearDown]
public void tearDown() {
driver.Quit();
}
[Test]
public void Search_Returns_Results() {
HomePage homePage = new HomePage(driver, logger);
homePage.open();
homePage.selectMake(MAKE);
homePage.selectModel(MODEL);
homePage.selectPrice(PRICE);
homePage.typePostalCode(POSTAL_CODE);
ResultsPage resultsPage = homePage.executeSearch(); new CustomAssert().IsTrue(“results page title is not empty”,
resultsPage.title().Length > 0);
}
}
}

There are 2 things to notice in this test class.

First, the logger object (of the Logger class) is injected in the homePage object which passes it later to the resultsPage object. homePage and resultsPage take care of their own logging.

Second, a CustomAssert class is used so that assertion info is logged before each assertion executes.

The Logger class is very basic. It implements an interface that defines 3 methods:

using System;namespace Logging.basic {  interface Logger_I {
void Log(String text);
void Info(String text);
void Error(String text);
}
}

This is the Logger class implementation:

using System;namespace Logging.basic {  class Logger : Logger_I {  public Logger()
{
}
public void Info(String text) {
Console.WriteLine(“INFO — “ + text);
}
public void Log(String text) {
Console.WriteLine(“LOG — “ + text);
}
public void Error(String text) {
Console.WriteLine(“ERROR — “ + text);
}
}}

This simple logger logs text to the console. I could have implemented it in many other ways, by relying on logging frameworks, but this is not what I am interested in.

What I want to do is to implement the execution logging in the simplest way with no code duplication.

The HomePage class follows:

using Logging.basic;using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Linq;
namespace Logging.Basic { class HomePage { private IWebDriver driver;
private WebDriverWait wait;
private const String URL = “https://www.autotrader.ca"; private By LOCATION_ID = By.Id(“locationAddress”);

private By SEARCH_BUTTON_ID = By.Id(“SearchButton”);
private By MAKES_ID = By.Id(“rfMakes”);

private By MODELS_ID = By.Id(“rfModel”);

private By PRICE_ID = By.Id(“rfPriceHigh”);
private Logger logger; public HomePage(IWebDriver driver, Logger logger) {
this.driver = driver;
this.wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
this.logger = logger;
}
public void open() {
driver.Navigate().GoToUrl(URL);
Boolean isDisplayed = wait.Until(d =>
{ return d.Url.Contains(“autotrader.ca”); });

if (isDisplayed == false)
throw new ApplicationException(“Home Page-not displayed!”);
logger.Info(String.Format(“open Home Page ({0})”, URL));
}
public void selectMake(String make) {
selectOptionInList(MAKES_ID, make);
logger.Info(String.Format(“select make ({0})”, make));
}
public void selectModel(String model) {
selectOptionInList(MODELS_ID, model);
logger.Info(String.Format(“select model ({0})”, model));
}
public void selectPrice(String price) {
selectOptionInList(PRICE_ID, price);
logger.Info(String.Format(“select price ({0})”, price));
}
private void selectOptionInList(By by, String value) {
SelectElement list = getList(by);
list.SelectByText(value);
wait.Until(d => {
list = getList(by);
return isOptionEqualTo(list, value);
});
}
private Boolean isOptionEqualTo(SelectElement list, String value)
{
return list.AllSelectedOptions.ElementAt(0).Text.Equals(value);
}
public void typePostalCode(String value) {
IWebElement locationBox = findElement(LOCATION_ID);
locationBox.SendKeys(value);
logger.Info(String.Format(“type postal code ({0})”, value));
}
public ResultsPage executeSearch() {
IWebElement searchButton = findElement(SEARCH_BUTTON_ID);
searchButton.Click();
logger.Info(“execute search”); return new ResultsPage(this.driver, this.logger);
}
private SelectElement getList(By by) {
IWebElement element = findElement(by);
return new SelectElement(element);
}
private IWebElement findElement(By by) {
this.wait.Until(d => {
IWebElement e = d.FindElement(by);
return e.Displayed && e.Enabled;
});
return driver.FindElement(by);
}
}
}

The logging is done in each public method of the HomePage class.

Since there is logging to be done as well in the ResultsPage class, the logger object is also injected in the resultsPage object in the executeSearch() method.

ResultsPage is similar to HomePage:

using Logging.basic;using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
namespace Logging.Basic { class ResultsPage { private IWebDriver driver;
private WebDriverWait wait;
private By TITLE_ID = By.Id(“rfTitle”); private Logger logger; public ResultsPage(IWebDriver driver, Logger logger) {
this.driver = driver;
this.wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
this.logger = logger; if (isDisplayed() == false)
throw new ApplicationException(
“Results Page-not displayed!”);
}
public Boolean isDisplayed() {
Boolean isDisplayed = wait.Until(d =>
{ return d.Url.Contains(“loc=”); });
return isDisplayed;
}
public String title() {
IWebElement titleLabel = findElement(TITLE_ID);
return titleLabel.Text;
}
private IWebElement findElement(By by)
{
this.wait.Until(d => {
IWebElement e = d.FindElement(by);
return e.Displayed && e.Enabled;
});
return driver.FindElement(by);
}
}
}

The page classes log the executed methods.

CustomAssertion does the same for the assertions:

using Logging.basic;using NUnit.Framework;
using System;
namespace Logging.Basic {

class CustomAssert : Assert {

public CustomAssert() {
} public void IsTrue(String conditionName, bool condition) {
new Logger().Info(“ASSERTION — “ + conditionName);
Assert.IsTrue(condition, “FAILED — “ + conditionName);
}
}
}

After running the automated test, the results look as follows:

INFO — open Home Page (https://www.autotrader.ca)
INFO — select make (Audi)
INFO — select model (A4)
INFO — select price ($40,000)
INFO — type postal code (V6J5M9)
INFO — execute search
INFO — get results page title
INFO — ASSERTION — results page title is not empty

This is the most obvious way of logging everything for the automated test execution.

It is not efficient to add the logging() code in each page object method. This is code duplication which we should avoid as much as possible.

Ideally, we would have logging done with no logging code added to the page classes.

This is hard to do for the page methods, but possible for the interactions with the web elements.

The second option relies on a class from the Selenium WebDriver API.


Logging with EventFiringDriver

This type of logging is done at the action level (find element, click element, test started, test stopped, etc).

The test class is a bit simpler in this case because the SetUp and TearDown methods are moved to a BaseTest class:

using NUnit.Framework;
using System;
namespace Logging.EventFiringDriver
{
[TestFixture]
public class TestClass_Event_Firing_Driver : BaseTest {
private const String MAKE = “Audi”;
private const String MODEL = “A4”;
private const String PRICE = “$40,000”;
private const String POSTAL_CODE = “V6J5M9”;
[Test]
public void Search_Returns_Results() {
HomePage homePage = new HomePage(base.Driver());
homePage.open();
homePage.selectMake(MAKE);
homePage.selectModel(MODEL);
homePage.selectPrice(PRICE);
homePage.typePostalCode(POSTAL_CODE);
ResultsPage resultsPage = homePage.executeSearch(); new CustomAssert().IsTrue(“results page title is not empty”,
resultsPage.title().Length > 0);
}
}
}

No logging object is used in this case as a parameter for the homePage object.

The BaseTest class follows:

using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.Events;
using System;
namespace Logging.EventFiringDriver {

public class BaseTest
{
private IWebDriver driver; public IWebDriver Driver()
{
return this.driver;
}
[SetUp]
public void setUp()
{
this.driver = new ChromeDriver();
this.driver.Manage().Window.Maximize();
var firingDriver = new EventFiringWebDriver(driver); firingDriver.ExceptionThrown +=
new EventHandler<WebDriverExceptionEventArgs>
(firingDriver_ExceptionThrown);
firingDriver.ElementClicked +=
new EventHandler<WebElementEventArgs>
(firingDriver_ElementClicked);
firingDriver.ElementValueChanged +=
new EventHandler<WebElementValueEventArgs>
(firingDriver_ElementValueChanged);
firingDriver.FindElementCompleted +=
new EventHandler<FindElementEventArgs>
(firingDriver_FindElementCompleted);
firingDriver.Navigated +=
new EventHandler<WebDriverNavigationEventArgs>
(firingDriver_Navigate);
firingDriver.ScriptExecuting +=
new EventHandler<WebDriverScriptEventArgs>
(firingDriver_ScriptExecuting);
firingDriver.ScriptExecuted +=
new EventHandler<WebDriverScriptEventArgs>
(firingDriver_ScriptExecuted);
driver = firingDriver; } [TearDown]
public void tearDown()
{
driver.Quit();
}
void firingDriver_ExceptionThrown(object sender,
WebDriverExceptionEventArgs e)
{
Console.WriteLine(e.ThrownException.Message);
}
void firingDriver_ElementValueChanged(object sender,
WebElementValueEventArgs e)
{
Console.WriteLine(“element value changed: “ + e.Element);
}
void firingDriver_ElementClicked(object sender,
WebElementEventArgs e)
{
Console.WriteLine(“clicked element: “ + e.Element);
}
void firingDriver_FindElementCompleted(object sender,
FindElementEventArgs e)
{
Console.WriteLine(“found element: “ + e.FindMethod);
}
void firingDriver_Navigate(object sender,
WebDriverNavigationEventArgs e)
{
Console.WriteLine(“navigate to: “ + e.Url);
}
void firingDriver_Quit(object sender, WebDriverScriptEventArgs e)
{
Console.WriteLine(“quit driver: “ + e.ToString());
}
void firingDriver_ScriptExecuted(object sender,
WebDriverScriptEventArgs e)
{
Console.WriteLine(“script finished: “ + e.ToString());
}
void firingDriver_ScriptExecuting(object sender,
WebDriverScriptEventArgs e)
{
Console.WriteLine(“script is executing: “ + e.ToString());
}
}
}

The EventFiringDriver object is created and configured in the SetUp() method by wrapping the original driver object. Event handlers are added to the EventFiringDriver object for each event that EventFiringDriver understands:

  • ExceptionThrown
  • ElementClicked
  • ElementValueChanged
  • FindElementCompleted
  • Navigated
  • ScriptExecuting
  • ScriptExecuted

Each event handler gets a method as a parameter:

void firingDriver_ExceptionThrown(object sender, 
WebDriverExceptionEventArgs e)
void firingDriver_ElementValueChanged(object sender,
WebElementValueEventArgs e)
void firingDriver_ElementClicked(object sender,
WebElementEventArgs e)
void firingDriver_FindElementCompleted(object sender,
FindElementEventArgs e)
void firingDriver_Navigate(object sender,
WebDriverNavigationEventArgs e)
void firingDriver_Quit(object sender, WebDriverScriptEventArgs e)void firingDriver_ScriptExecuted(object sender,
WebDriverScriptEventArgs e)
void firingDriver_ScriptExecuting(object sender,
WebDriverScriptEventArgs e)

When Selenium code throws an event (example:ElementClicked), the method associated with the handler of the event is executed automatically.

These methods do the logging to the console based on their parameters.

These parameters can be a locator, an element, an exception, a test or an exception.

Since all logging is done by the event handler, the page classes do not have any logging code:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Linq;
namespace Logging.EventFiringDriver {

class HomePage {
private IWebDriver driver;
private WebDriverWait wait;
private const String URL = “https://www.autotrader.ca"; private By LOCATION_ID = By.Id(“locationAddress”); private By SEARCH_BUTTON_ID = By.Id(“SearchButton”);

private By MAKES_ID = By.Id(“rfMakes”);

private By MODELS_ID = By.Id(“rfModel”);

private By PRICE_ID = By.Id(“rfPriceHigh”);
public HomePage(IWebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
}
public void open() {
driver.Navigate().GoToUrl(URL);
Boolean isDisplayed = wait.Until(d => {
return d.Url.Contains(“autotrader.ca”); });

if (isDisplayed == false)
throw new ApplicationException(
“Home Page is not displayed!”);
}
public void selectMake(String make) {
selectOptionInList(MAKES_ID, make);
}
public void selectModel(String model) {
selectOptionInList(MODELS_ID, model);
}
public void selectPrice(String price) {
selectOptionInList(PRICE_ID, price);
}
private void selectOptionInList(By by, String value) {
SelectElement list = getList(by);
list.SelectByText(value);
wait.Until(d => {
list = getList(by);
return isOptionEqualTo(list, value);
});
}
private Boolean isOptionEqualTo(SelectElement list, String value)
{
return list.AllSelectedOptions.ElementAt(0).Text.Equals(value);
}
public void typePostalCode(String value)
{
IWebElement locationBox = findElement(LOCATION_ID);
locationBox.SendKeys(value);
}
public ResultsPage executeSearch()
{
IWebElement searchButton = findElement(SEARCH_BUTTON_ID);
searchButton.Click();
return new ResultsPage(this.driver);
}
private SelectElement getList(By by)
{
IWebElement element = findElement(by);
return new SelectElement(element);
}
private IWebElement findElement(By by)
{
this.wait.Until(d => {
IWebElement e = d.FindElement(by);
return e.Displayed && e.Enabled;
});
return driver.FindElement(by);
}
}
}

There is no logging in the HomePage class.

There is no logging in the ResultsPage class either:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
namespace Logging.EventFiringDriver {

class ResultsPage {
private IWebDriver driver;
private WebDriverWait wait;
private By TITLE_ID = By.Id(“rfTitle”); public ResultsPage(IWebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
if (isDisplayed() == false)
throw new ApplicationException(
“Results Page is not displayed!”);
}
public Boolean isDisplayed()
{
Boolean isDisplayed = wait.Until(d =>
{ return d.Url.Contains(“loc=”); });
return isDisplayed;
}
public String title()
{
IWebElement titleLabel = findElement(TITLE_ID);
return titleLabel.Text;
}
private IWebElement findElement(By by)
{
this.wait.Until(d =>
{
IWebElement e = d.FindElement(by);
return e.Displayed && e.Enabled;
});
return driver.FindElement(by);
}
}
}

No logging code is used in ResultsPage either.

All logging is done by the EvenFiringDriver based on the argument of the event handlers.

The result of executing the test is below:

navigate to: https://www.autotrader.ca
found element: By.XPath: //select[@id = ‘rfMakes’]
found element: By.XPath: //select[@id = ‘rfMakes’]
found element: By.XPath: .//option[normalize-space(.) = “Audi”]
clicked element: Element (id = bdf000dd-be3c-4b02–931a-4e469d28401e)
found element: By.XPath: //select[@id = ‘rfMakes’]
found element: By.XPath: //select[@id = ‘rfMakes’]
found element: By.TagName: option
found element: By.XPath: //select[@id = ‘rfModel’]
found element: By.XPath: //select[@id = ‘rfModel’]
found element: By.XPath: .//option[normalize-space(.) = “A4”]
clicked element: Element (id = cde71be1-e986–4406–9660–6b0dcf94e993)
found element: By.XPath: //select[@id = ‘rfModel’]
found element: By.XPath: //select[@id = ‘rfModel’]
found element: By.TagName: option
found element: By.XPath: //select[@id = ‘rfPriceHigh’]
found element: By.XPath: //select[@id = ‘rfPriceHigh’]
found element: By.XPath: .//option[normalize-space(.) = “$40,000”]
clicked element: Element (id = 2f5f8b0b-83b9–438e-b9af-03fe68b92c31)
found element: By.XPath: //select[@id = ‘rfPriceHigh’]
found element: By.XPath: //select[@id = ‘rfPriceHigh’]
found element: By.TagName: option
found element: By.XPath: //input[@id = ‘locationAddress’]
found element: By.XPath: //input[@id = ‘locationAddress’]
element value changed: Element (id = 71f2d43d-201d-4874–8570-f84931d1b58e)
found element: By.XPath: //a[@id = ‘SearchButton’]
found element: By.XPath: //a[@id = ‘SearchButton’]
clicked element: Element (id = 175bfcea-96af-443d-a3de-48a2c291fd47)
found element: By.XPath: //h1[@id = ‘rfTitle’]
found element: By.XPath: //h1[@id = ‘rfTitle’]
INFO — ASSERTION — results page title is not empty

This result is better and worse than the previous one.

Better because there is no logging code in the page classes.

Worse because the report is hard to use.

When elements are clicked or they change value, the element id is displayed which is totally obscure.

When elements are found, the element locator is displayed which may or may not be useful to point to the element, depending on how complicated the locator is.

So this leads us to the third solution.


Use HtmlElement classes

This type of logging happens as well at the action level.

Instead of using the EventFiringDriver, we create custom Html classes for each type of element (list, button, link, textbox, etc) and make them responsible for logging their methods.

For interacting with a listbox, we need a custom select class:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Linq;
namespace Logging.HtmlElements
{
public class CustomSelect : SelectElement
{
private IWebElement element;
private SelectElement list;
private String name;
public CustomSelect(String name, IWebElement element) :
base(element)
{
this.element = element;
this.list = new SelectElement(element);
this.name = name;
}
public void SelectByText(String text)
{
list.SelectByText(text);
new Logger().Info(“selected “ + text +
“ in “ +
this.name + “ list”);

}
public string Name()
{
return this.name;
}
public Boolean isSelectedOptionEqualTo(String value)
{
return
this.list
.AllSelectedOptions
.ElementAt(0)
.Text
.Equals(value);
}
public IWebElement WrappedElement()
{
return this.element;
}
}
}

The CustomSelect class extends the Select class.

It encapsulates a web element and the name of the element.

It creates a Select object from the web element and relies on it for all list operations.

After each operation takes place, logging is being done.

For all other web elements, a custom html element class is used by implementing the IWebElement interface:

using OpenQA.Selenium;
using System;
using System.Collections.ObjectModel;
using System.Drawing;
namespace Logging.HtmlElements
{

public class HtmlElement : IWebElement {
private IWebElement element;
private string name;
public HtmlElement(String name, IWebElement element)
{
this.element = element;
this.name = name;
}

public string TagName => element.TagName;
public string Text => element.Text; public bool Enabled => element.Enabled; public bool Selected => element.Selected; public Point Location => element.Location; public Size Size => element.Size; public bool Displayed => element.Displayed; public void Clear()
{
new Logger().Info(“clear “ + name);
element.Clear();
}
public void Click()
{
new Logger().Info(“click on “ + name);
element.Click();
}
public IWebElement FindElement(By by)
{
new Logger().Info(“find element " + name);
return element.FindElement(by);
}
public ReadOnlyCollection<IWebElement> FindElements(By by)
{
return element.FindElements(by);
}
public string GetAttribute(string name)
{
new Logger().Info(“get attribute " + name);
return element.GetAttribute(name);
}
public string GetCssValue(string name)
{
new Logger().Info(“get css value “ + name);
return element.GetCssValue(name);
}
public string GetProperty(string name)
{
return element.GetProperty(name);
}
public void SendKeys(string text)
{
new Logger().Info(“type “ + text + “ in “ + name);
element.SendKeys(text);
}
public void Submit()
{
element.Submit();
}
public IWebElement WrappedElement()
{
return this.element;
}
public String Name()
{
return this.name;
}
}
}

The page classes then rely on the custom element classes:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
namespace Logging.HtmlElements {

public class HomePage
{
private IWebDriver driver;
private WebDriverWait wait;
private const String URL = “https://www.autotrader.ca"; private By LOCATION_ID = By.Id(“locationAddress”); private By SEARCH_BUTTON_ID = By.Id(“SearchButton”); private By MAKES_ID = By.Id(“rfMakes”);

private By MODELS_ID = By.Id(“rfModel”);

private By PRICE_ID = By.Id(“rfPriceHigh”);
public HomePage(IWebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
} public void open()
{
driver.Navigate().GoToUrl(URL);
Boolean isDisplayed = wait.Until(d =>
{ return d.Url.Contains(“autotrader.ca”); });

if (isDisplayed == false)
throw new ApplicationException(
“Home Page is not displayed!”);
}
public void selectMake(String make)
{
selectOptionInList(“makes”, MAKES_ID, make);
}
public void selectModel(String model)
{
selectOptionInList(“models”, MODELS_ID, model);
}
public void selectPrice(String price)
{
selectOptionInList(“price”, PRICE_ID, price);
}
private void selectOptionInList(String listName, By by,
String value)
{
HtmlElement htmlElement = findElement(listName, by);

CustomSelect list = new CustomSelect(listName,
htmlElement.wrappedElement());
list.SelectByText(value); wait.Until(d => {
HtmlElement e = findElement(listName, by);
CustomSelect l = new CustomSelect(listName,
e.wrappedElement());
return l.isSelectedOptionEqualTo(value);
});
}
public void typePostalCode(String value)
{
HtmlElement locationBox = findElement(“Location”,
LOCATION_ID);
locationBox.SendKeys(value);
}
public ResultsPage executeSearch()
{
HtmlElement searchButton = findElement(“Search Button”,
SEARCH_BUTTON_ID);

searchButton.Click();
return new ResultsPage(this.driver);
}
private HtmlElement findElement(String name, By by)
{
this.wait.Until(d =>
{
IWebElement e = d.FindElement(by);
return e.Displayed && e.Enabled;
});
return new HtmlElement(name, driver.FindElement(by));
}
}
}

All methods use HtmlElement instead of IWebElement and CustomSelect instead of Select to that logging is done before element methods are executed.

ResultsPage is similar:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
namespace Logging.HtmlElements
{

class ResultsPage
{
private IWebDriver driver;
private WebDriverWait wait;
private By TITLE_ID = By.Id(“rfTitle”);

public ResultsPage(IWebDriver driver)
{
this.driver = driver;
this.wait = new WebDriverWait(driver,
TimeSpan.FromSeconds(30));
if (isDisplayed() == false)
throw new ApplicationException(
“Results Page is not displayed!”);
}
public Boolean isDisplayed()
{
Boolean isDisplayed = wait.Until(d =>
{ return d.Url.Contains(“loc=”); });
return isDisplayed;
}
public String title()
{
HtmlElement titleLabel = findElement("page title",
TITLE_ID);

return titleLabel.Text;
}
private IWebElement findElement(String name, By by)
{

this.wait.Until(d =>
{
IWebElement e = d.FindElement(by);
return e.Displayed && e.Enabled;
});
return new HtmlElement(name, driver.FindElement(by));
}
}
}

In this case, we do not have any explicit logging in the page methods and no EventFiringDriver.

There is no code duplication either.

The result of executing the test is below:

INFO — selected Audi in makes list
INFO — selected A4 in models list
INFO — selected $40,000 in price list
INFO — type V6J5M9 in Location
INFO — click on Search Button
INFO — ASSERTION — results page title is not empty

The logging information is much easier to understand and we still do not have any logging code in the page classes.

Using custom classes for each type of html element helps creating self-logging Selenium tests.

These tests log their execution automatically, without any logging code added to the test or to the page classes.

Alex Siminiuc

Written by

Blogs about Selenium and Java at https://seleniumjava.com.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade