//
you're reading...
WebDriver

Eavesdropping into WebDriver

 We all know that WebDriver already gives us a mechanism so that we can eavesdrop into WebDriver originated events. The way you get this done is by making use of EventFiringWebDriver. There are a lot of blog posts that explain to you how you can make use of EventFiringWebDriver to listen to events. Darrell Grainger has written up a very good post which explains how to work with EventFiringWebDriver.

So you might be wondering what is this post all about ? No.. am not going to be talking about EventFiringWebDriver at all, but I am going to be talking about how you can listen to events originating from the WebDriver using alternate mechanisms.

Not many people know that EventFiringWebDriver has 1 small short-coming. It cannot tap into events that arise out of the Actions class.  When I asked about why doesn’t EventFiringWebDriver not support events arising out of Actions class, in the 2014’s Selenium conference, Simon Stewart chuckled and said.. I guess when EventFiringWebDriver was created Actions class never even existed. Later when Actions class was created, we didn’t bother to go update EventFiringWebDriver. So why don’t you send us a pull request.

I have always had this itch to somehow see my name also in the committer’s list of the Selenium codebase. So I took this up as a challenge and decided to send a pull request which plugs in this gap.

I eventually managed to build this support and raised it as a pull request [ #262 ] . Only after the pull request was raised and submitted for review did I get to know from the Selenium Dev Community that there are other plans for EventFiringWebDriver and it may be on the path of deprecation.

So here are a couple of alternatives that basically still would let you tap into events from WebDriver but not use EventFiringWebDriver [ The source of inspiration for this idea has been my very good friend and colleague Fakrudeen Shahul Hameed ].

If you are using only FirefoxDriver (or) ChromeDriver etc., here’s how you can get this done.

The first thing we would need to do is to create a CommandExecutor. As you can see, here we are basically wrapping an existing CommandExecutor into our class and just delegating calls to the actual executor. The reason why we are doing this here is because there are different CommandExecutors that can be associated with different WebDriver implementations. So we are kind of “decorating” the actual command executor with our added logic.
The below command executor specifically adds “before” and “after” customisations only when loading a URL.
You can refer to the javadocs of DriverCommand to learn about what other commands are available.

public static class MyFunkyExecutor implements CommandExecutor {
    private CommandExecutor executor;

    public MyFunkyExecutor(CommandExecutor executor) {
        this.executor = executor;
    }

    @Override
    public Response execute(Command command) throws IOException {
        //Simulating a before event
        if (DriverCommand.GET.equals(command.getName())) {
            System.out.println("before() loading URL");
        }

        // This is where the actual command gets executed resulting in whatever event we wanted to trigger.
        Response response = executor.execute(command);

        //Simulating an after event
        if (DriverCommand.GET.equals(command.getName())) {
            System.out.println("after() loading URL");
        }
        return response;
    }
}

Lets see how we can append the a CommandListener into a WebDriver instance. Unlike RemoteWebDriver (the mother of all WebDriver implementations), ChromeDriver/FirefoxDriver etc., don’t have an easy way in which the CommandExecutor can be injected into a WebDriver. That is because the method via which we set the CommandExecutor is a protected method and so its only available within a child class.
We could essentially sub-class every WebDriver implementation, but that doesn’t add too much value apart from causing an explosion of new classes. So we are going to be be leveraging Reflection in Java to get this done.

Here’s a sample method that does uses reflection to inject a CommandExecutor into a WebDriver implementation.

private void appendListenerToWebDriver(RemoteWebDriver rwd) {
    CommandExecutor executor = new MyFunkyExecutor(rwd.getCommandExecutor());
    Class clazz = rwd.getClass();
    while (! RemoteWebDriver.class.equals(clazz)) {
    // iterate repeatedly till we get to a RemoteWebDriver reference
        clazz = clazz.getSuperclass();
    }
    try {
        //setCommandExector is the protected method within RemoteWebDriver that is
        //responsible for accepting a command exector.
        Method m = clazz.getDeclaredMethod("setCommandExecutor", CommandExecutor.class);
        //This method is a protected method. So we have to make it accessible.
        m.setAccessible(true);
        m.invoke(rwd, executor);
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        //The above multi catch syntax will work only with JDK 1.7
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

Below is a sample test that leverages all of this and attempts at eavesdropping into the web driver originating events.

public class EavesdropEventsUsingWebDriver {
    WebDriver fd;

    @BeforeClass
    public void setup() {
        fd = new FirefoxDriver();
    }

    @AfterClass
    public void cleanup() {
        fd.quit();
    }

    @Test
    public void testMethod() throws NoSuchMethodException {
        //Lets inject our CmmandExecutor into the newly created WebDriver instance.
        appendListenerToWebDriver((RemoteWebDriver) fd);
        fd.get("http://www.yahoo.com");
        System.out.println(fd.getTitle());
    }
}

So how do we tap into events if we are working with a RemoteWebDriver ?

We first go about sub-classing RemoteWebDriver wherein we inject our CustomListener that we created earlier through our custom created sub-class. Here’s how a simple implementation can look like.

public class EavesdroppingWebDriver extends RemoteWebDriver {
    public EavesdroppingWebDriver(URL remoteAddress, Capabilities desiredCapabilities) {
        super(remoteAddress, desiredCapabilities);
        setCommandExecutor(new MyFunkyExecutor(new HttpCommandExecutor(remoteAddress)));
    }
}

As we did before, here again we are basically decorating an actual CommandExecutor with the one that we created before.

Here’s a test class that uses our custom RemoteWebDriver that we just created and eavesdrops into the events.

public class EavesDropEvents {
    WebDriver rwd;

    @BeforeClass
    public void setup() throws MalformedURLException {
        rwd = new EavesdroppingWebDriver(new URL("http://localhost:4444/wd/hub"), DesiredCapabilities.firefox());
    }

    @Test
    public void testMethod() {
        rwd.get("http://www.yahoo.com");
        System.out.println("Title : " + rwd.getTitle());
    }

    @AfterClass
    public void cleanup() {
        rwd.quit();
    }
}

Here’s the output

[TestNG] Running:
  /Users/krmahadevan/Library/Caches/IntelliJIdea14/temp-testng-customsuite.xml

before() loading URL
after() loading URL
Title : Yahoo

This is what we resorted to when we had to build event handling capabilities into SeLion.
You can read more about this on the SeLion documentation portal.

Hope that gives you a good starting perspective in working with WebDriver events.

Discussion

Trackbacks/Pingbacks

  1. Pingback: Testcase specific logs for a Selenium project | Misadventures of a castaway coder! - November 18, 2016

Leave a comment