Understanding WebDriver Events
Tags: API, Android, Software Testing, Selenium Webdriver, Webdriver

Learn WebDriver Events in this article by Unmesh Gundecha, an Agile, open source, and DevOps evangelist with extensive experience in a diverse set of tools and technologies.

Selenium WebDriver provides an API for tracking the various events that happen when test scripts are executed using WebDriver. Many navigation events get fired before and after a WebDriver internal event occurs (such as before and after navigating to a URL and before and after browser back-navigation), and these can be tracked and captured.

To throw an event, WebDriver gives you a class named EventFiringWebDriver, and to catch that event, it provides the test-script developer with an interface named WebDriverEventListener. The test-script developer should provide its own implementations for the overridden methods from the interface.

Introducing the eventFiringWebDriver and eventListener classes

The EventFiringWebDriver class is a wrapper around the WebDriver that gives the driver the capability to fire events. The EventListener class, on the other hand, waits to listen to EventFiringWebDriver and handles all events that are dispatched. There can be more than one listener waiting to hear from the EventFiringWebDriver class for an event to fire. All event listeners should be registered with the EventFiringWebDriver class to get notified.

The following flow diagram explains what has to be done to capture all of the events raised by EventFiringWebDriver during the execution of test cases:

Creating an instance of EventListener

The EventListener class handles all events that are dispatched by the EventFiringWebDriver class. There are two ways to create an EventListener class:

· By implementing the WebDriverEventListener interface

· By extending the AbstractWebDriverEventListener class provided in the WebDriver library

It is up to you, as a test-script developer, to choose which way to go.

Implementing WebDriverEventListener

The WebDriverEventListener interface has all the event methods declared. The EventFiringWebDriver class, as soon as it realizes an event has occurred, invokes the registered method of WebDriverEventListener .

Here, an IAmTheEventListener named class has been created and WebDriverEventListener has been implemented. Now, you need to provide an implementation for all the methods declared in it. Currently, there are 15 methods in WebDriverEventListener .

The class created with all 15 overridden methods is as follows:

public class IAmTheEventListener implements WebDriverEventListener { @Override public void beforeAlertAccept(WebDriver webDriver) { } @Override public void afterAlertAccept(WebDriver webDriver) { } @Override public void afterAlertDismiss(WebDriver webDriver) { } @Override public void beforeAlertDismiss(WebDriver webDriver) { } @Override public void beforeNavigateTo(String url, WebDriver webDriver) { System.out.println("Before Navigate To " + url); } @Override public void afterNavigateTo(String s, WebDriver webDriver) { System.out.println("Before Navigate Back. Right now I'm at " + webDriver.getCurrentUrl()); } @Override public void beforeNavigateBack(WebDriver webDriver) { } @Override public void afterNavigateBack(WebDriver webDriver) { } @Override public void beforeNavigateForward(WebDriver webDriver) { } @Override public void afterNavigateForward(WebDriver webDriver) { } @Override public void beforeNavigateRefresh(WebDriver webDriver) { } @Override public void afterNavigateRefresh(WebDriver webDriver) { } @Override public void beforeFindBy(By by, WebElement webElement, WebDriver webDriver) { } @Override public void afterFindBy(By by, WebElement webElement, WebDriver webDriver) { } @Override public void beforeClickOn(WebElement webElement, WebDriver webDriver) { } @Override public void afterClickOn(WebElement webElement, WebDriver webDriver) { } @Override public void beforeChangeValueOf(WebElement webElement, WebDriver webDriver, CharSequence[] charSequences) { } @Override public void afterChangeValueOf(WebElement webElement, WebDriver webDriver, CharSequence[] charSequences) { } @Override public void beforeScript(String s, WebDriver webDriver) { } @Override public void afterScript(String s, WebDriver webDriver) { } @Override public void onException(Throwable throwable, WebDriver webDriver) { } }

Extending AbstractWebDriverEventListener

The second way to create a listener class is by extending the AbstractWebDriverEventListene r class. AbstractWebDriverEventListener is an abstract class that implements WebDriverEventListener .

Though it doesn’t really provide any implementation for the methods in the WebDriverEventListener interface, it creates a dummy implementation such that the listener class that you are creating doesn't have to contain all the methods, only the ones that you, as a test-script developer, are interested in.

The following is a class created that extends AbstractWebDriverEventListener and provides implementations for a couple of methods in it. This way, you can override only the methods that you are interested in rather than all of the methods in your class:

package com.example; import org.openqa.selenium.WebDriver; import org.openqa.selenium.support.events.AbstractWebDriverEventListener; public class IAmTheEventListener2 extends AbstractWebDriverEventListener { @Override public void beforeNavigateTo(String url, WebDriver driver) { System.out.println("Before Navigate To "+ url); } @Override public void beforeNavigateBack(WebDriver driver) { System.out.println("Before Navigate Back. Right now I'm at " + driver.getCurrentUrl()); } }

Creating a WebDriver instance

Now that you have created your listener class that listens for all of the events generated, it’s time to create your test script class and let it call IAmTheDriver.java . After the class is created, declare a ChromeDriver instance in it:

WebDriver driver = new ChromeDriver();

The ChromeDriver instance will be the underlying driver instance that drives all the driver events. This is nothing new. The step explained in the next section is where you make this driver an instance of EventFiringWebDriver .

Creating EventFiringWebDriver and EventListener instances

Now that you have the basic driver instance, pass it as an argument while constructing the EventFiringWebDriver instance. You can use this instance of the driver to execute all further user actions.

Using the following code, instantiate the EventListener , IAmTheEventListener.java , or IAmTheEventListener2.java class. This will be the class to which all events are dispatched:

EventFiringWebDriver eventFiringDriver = new EventFiringWebDriver(driver); IAmTheEventListener eventListener = new IAmTheEventListener();

Registering EventListener with EventFiringWebDriver

For the event executions to be notified by EventListener , you have to register EventListener to the EventFiringWebDriver class. Now, the EventFiringWebDriver class will know where to send the notifications. This is done by the following line of code: eventFiringDriver.register(eventListener);

Executing and verifying the events

Now it’s time for your test script to execute events, such as navigation events. Navigate to Google and then Facebook. Use the browser back-navigation to go back to Google. The full code of the test script is as follows:

public class IAmTheDriver { public static void main(String... args){ System.setProperty("webdriver.chrome.driver", "./src/test/resources/drivers/chromedriver"); WebDriver driver = new ChromeDriver(); try { EventFiringWebDriver eventFiringDriver = new EventFiringWebDriver(driver); IAmTheEventListener eventListener = new IAmTheEventListener(); eventFiringDriver.register(eventListener); eventFiringDriver.get("http://www.google.com"); eventFiringDriver.get("http://www.facebook.com"); eventFiringDriver.navigate().back(); } finally { driver.close(); driver.quit(); } } }

In the preceding code, you modify your listener class to record navigateTo and navigateBack before and after events inherited from the AbstractWebDriverEventListener class. The modified methods are as follows:

@Override public void beforeNavigateTo(String url, WebDriver driver) { System.out.println("Before Navigate To: " + url + " and Current url is: " + driver.getCurrentUrl()); } @Override public void afterNavigateTo(String url, WebDriver driver) { System.out.println("After Navigate To: " + url + " and Current url is: " + driver.getCurrentUrl()); } @Override public void beforeNavigateBack(WebDriver driver) { System.out.println("Before Navigate Back. Right now I'm at " + driver.getCurrentUrl()); } @Override public void afterNavigateBack(WebDriver driver) { System.out.println("After Navigate Back. Right now I'm at " + driver.getCurrentUrl()); }

Now if you execute your test script, the output will be as follows:

Before Navigate To: http://www.google.com and Current url is: data:, After Navigate To: http://www.google.com and Current url is: https://www.google.com/?gws_rd=ssl Before Navigate To: http://www.facebook.com and Current url is: https://www.google.com/?gws_rd=ssl After Navigate To: http://www.facebook.com and Current url is: https://www.facebook.com/ Before Navigate Back. Right now I'm at https://www.facebook.com/ After Navigate Back. Right now I'm at https://www.google.com/?gws_rd=ssl

Registering multiple EventListeners

You can register more than one listener with EventFiringWebDriver . Once the event occurs, all of the registered listeners are notified about it. Modify your test script to register both your IAmTheListener.java and IAmTheListener2.java files:

public class RegisteringMultipleListeners { public static void main(String... args){ System.setProperty("webdriver.chrome.driver", "./src/test/resources/drivers/chromedriver"); WebDriver driver = new ChromeDriver(); try { EventFiringWebDriver eventFiringDriver = new EventFiringWebDriver(driver); IAmTheEventListener eventListener = new IAmTheEventListener(); IAmTheEventListener2 eventListener2 = new IAmTheEventListener2(); eventFiringDriver.register(eventListener); eventFiringDriver.register(eventListener2); eventFiringDriver.get("http://www.google.com"); eventFiringDriver.get("http://www.facebook.com"); eventFiringDriver.navigate().back(); } finally { driver.close(); driver.quit(); } } }

Modify the listeners slightly to differentiate the log statements. Now if you execute the preceding code, you’ll see the following output:

Before Navigate To: http://www.google.com and Current url is: data:, Before Navigate To http://www.google.com After Navigate To: http://www.google.com and Current url is: https://www.google.com/?gws_rd=ssl Before Navigate To: http://www.facebook.com and Current url is: https://www.google.com/?gws_rd=ssl Before Navigate To http://www.facebook.com After Navigate To: http://www.facebook.com and Current url is: https://www.facebook.com/ Before Navigate Back. Right now I'm at https://www.facebook.com/ Before Navigate Back. Right now I'm at https://www.facebook.com/ After Navigate Back. Right now I'm at https://www.google.com/?gws_rd=ssl

If you found this article interesting, you can explore Selenium WebDriver 3 Practical Guide — Second Edition for real-world examples of cross-browser, mobile, and data-driven testing with all the latest features of Selenium WebDriver 3. Selenium WebDriver 3 Practical Guide — Second Edition covers all those features along with the source code, including a demo website that allows you to work with an HMTL5 application and other examples throughout the book.