com.salesforce.selenium.support.event.EventFiringWebDriver.java Source code

Java tutorial

Introduction

Here is the source code for com.salesforce.selenium.support.event.EventFiringWebDriver.java

Source

//Licensed to the Software Freedom Conservancy (SFC) under one
//or more contributor license agreements.  See the NOTICE file
//distributed with this work for additional information
//regarding copyright ownership.  The SFC licenses this file
//to you under the Apache License, Version 2.0 (the
//"License"); you may not use this file except in compliance
//with the License.  You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing,
//software distributed under the License is distributed on an
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//KIND, either express or implied.  See the License for the
//specific language governing permissions and limitations
//under the License.
package com.salesforce.selenium.support.event;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.HasInputDevices;
import org.openqa.selenium.interactions.HasTouchScreen;
import org.openqa.selenium.interactions.Keyboard;
import org.openqa.selenium.interactions.Mouse;
import org.openqa.selenium.interactions.TouchScreen;
import org.openqa.selenium.interactions.internal.Coordinates;
import org.openqa.selenium.interactions.internal.Locatable;
import org.openqa.selenium.internal.WrapsDriver;
import org.openqa.selenium.internal.WrapsElement;
import org.openqa.selenium.logging.Logs;

import com.salesforce.selenium.support.event.Step.Cmd;
import com.salesforce.selenium.support.event.Step.Type;
import com.salesforce.selenium.support.event.internal.EventFiringKeyboard;
import com.salesforce.selenium.support.event.internal.EventFiringMouse;
import com.salesforce.selenium.support.event.internal.EventFiringTouch;

/**
 * A wrapper around an arbitrary {@link WebDriver} instance which supports
 * registering of a {@link WebDriverEventListener}, e.g. for logging
 * purposes.
 * 
 * This is an extended version of org.openqa.selenium.support.events.EventFiringWebDriver. See
 * https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/events/EventFiringWebDriver.html
 * for more information.
 * 
 * @since 2.0.0
 */
public class EventFiringWebDriver
        implements WebDriver, JavascriptExecutor, TakesScreenshot, WrapsDriver, HasInputDevices, HasTouchScreen {

    private final WebDriver driver;
    private final WebDriverEventListener defaultEventListener;
    private final List<WebDriverEventListener> eventListeners = new ArrayList<>();
    private final WebDriverEventListener dispatcher = (WebDriverEventListener) Proxy.newProxyInstance(
            WebDriverEventListener.class.getClassLoader(), new Class[] { WebDriverEventListener.class },
            new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    try {
                        method.invoke(defaultEventListener, args);
                        for (WebDriverEventListener eventListener : eventListeners) {
                            method.invoke(eventListener, args);
                        }
                        return null;
                    } catch (InvocationTargetException e) {
                        throw e.getTargetException();
                    }
                }
            });

    private Step currentStep = null;
    private int stepNumber = 1;

    public EventFiringWebDriver(final WebDriver driver, String testName) {
        Class<?>[] allInterfaces = extractInterfaces(driver);

        this.driver = (WebDriver) Proxy.newProxyInstance(WebDriverEventListener.class.getClassLoader(),
                allInterfaces, new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if ("getWrappedDriver".equals(method.getName())) {
                            return driver;
                        }

                        try {
                            return method.invoke(driver, args);
                        } catch (InvocationTargetException e) {
                            if (currentStep != null) {
                                currentStep.setIssue(e);
                                dispatcher.onException(currentStep, currentStep.getCmd(), e.getTargetException());
                            } else {
                                // create a dummy step
                                Step step = new Step(Type.Exception, stepNumber, Cmd.quit);
                                step.setIssue(e);
                                dispatcher.onException(step, Cmd.quit, e.getTargetException());
                            }
                            throw e.getTargetException();
                        }
                    }
                });
        // standard listener which writes all events to JSON files
        defaultEventListener = new FullJSONLogger(testName);
    }

    private Class<?>[] extractInterfaces(Object object) {
        Set<Class<?>> allInterfaces = new HashSet<>();
        allInterfaces.add(WrapsDriver.class);
        if (object instanceof WebElement) {
            allInterfaces.add(WrapsElement.class);
        }
        extractInterfaces(allInterfaces, object.getClass());

        return allInterfaces.toArray(new Class<?>[allInterfaces.size()]);
    }

    private void extractInterfaces(Set<Class<?>> addTo, Class<?> clazz) {
        if (Object.class.equals(clazz)) {
            return; // Done
        }

        Class<?>[] classes = clazz.getInterfaces();
        addTo.addAll(Arrays.asList(classes));
        extractInterfaces(addTo, clazz.getSuperclass());
    }

    /**
     * @param eventListener
     *            the event listener to register
     * @return this for method chaining.
     */
    public EventFiringWebDriver register(WebDriverEventListener eventListener) {
        eventListeners.add(eventListener);
        return this;
    }

    /**
     * @param eventListener
     *            the event listener to unregister
     * @return this for method chaining.
     */
    public EventFiringWebDriver unregister(WebDriverEventListener eventListener) {
        eventListeners.remove(eventListener);
        return this;
    }

    public WebDriver getWrappedDriver() {
        if (driver instanceof WrapsDriver) {
            return ((WrapsDriver) driver).getWrappedDriver();
        } else {
            return driver;
        }
    }

    /*--------------------------------------------------------------------
     * Section for all commands called directly from WebDriver object.
     *--------------------------------------------------------------------*/

    @Override
    public void close() {
        Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.close);
        dispatcher.beforeClose(stepBefore);
        currentStep = stepBefore;

        driver.close();
        Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.close);
        dispatcher.afterClose(stepAfter);
    }

    @Override
    public WebElement findElement(By by) {
        Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.findElementByWebDriver);
        stepBefore.setParam1(Step.getLocatorFromBy(by));
        dispatcher.beforeFindElementByWebDriver(stepBefore, by);
        currentStep = stepBefore;

        WebElement returnedElement = driver.findElement(by);

        Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.findElementByWebDriver);
        stepAfter.setParam1(Step.getLocatorFromBy(by));
        stepAfter.setReturnValue(Step.getLocatorFromWebElement(returnedElement));
        stepAfter.setReturnObject(returnedElement);
        dispatcher.afterFindElementByWebDriver(stepAfter, returnedElement, by);
        return createWebElement(returnedElement);
    }

    @Override
    public List<WebElement> findElements(By by) {
        Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.findElementsByWebDriver);
        stepBefore.setParam1(Step.getLocatorFromBy(by));
        dispatcher.beforeFindElementByWebDriver(stepBefore, by);
        currentStep = stepBefore;

        List<WebElement> returnedElements = driver.findElements(by);

        Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.findElementsByWebDriver);
        stepAfter.setParam1(Step.getLocatorFromBy(by));
        if (returnedElements.size() > 0) {
            stepAfter.setReturnValue(Step.getLocatorFromWebElement(returnedElements.get(0)));
        }
        stepAfter.setReturnObject(returnedElements);
        dispatcher.afterFindElementsByWebDriver(stepAfter, returnedElements, by);

        List<WebElement> returnedAndWrappedElements = new ArrayList<>(returnedElements.size());
        for (WebElement element : returnedElements) {
            returnedAndWrappedElements.add(createWebElement(element));
        }
        return returnedAndWrappedElements;
    }

    @Override
    public void get(String url) {
        Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.get);
        stepBefore.setParam1(url);
        dispatcher.beforeGet(stepBefore, url);
        currentStep = stepBefore;

        driver.get(url);

        Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.get);
        stepAfter.setParam1(url);
        dispatcher.afterGet(stepAfter, url);
    }

    @Override
    public String getCurrentUrl() {
        Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getCurrentUrl);
        dispatcher.beforeGetCurrentUrl(stepBefore);
        currentStep = stepBefore;

        String url = driver.getCurrentUrl();

        Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getCurrentUrl);
        stepAfter.setReturnValue(url);
        dispatcher.afterGetCurrentUrl(stepAfter, url);
        return url;
    }

    @Override
    public String getTitle() {
        Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getTitle);
        dispatcher.beforeGetTitle(stepBefore);
        currentStep = stepBefore;

        String title = driver.getTitle();

        Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getTitle);
        stepAfter.setReturnValue(title);
        dispatcher.afterGetTitle(stepAfter, title);
        return title;
    }

    @Override
    public String getWindowHandle() {
        Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getWindowHandle);
        dispatcher.beforeGetWindowHandle(stepBefore);
        currentStep = stepBefore;

        String handle = driver.getWindowHandle();

        Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getWindowHandle);
        stepAfter.setReturnValue(handle);
        dispatcher.afterGetWindowHandle(stepAfter, handle);
        return handle;
    }

    @Override
    public Set<String> getWindowHandles() {
        Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getWindowHandles);
        dispatcher.beforeGetWindowHandles(stepBefore);
        currentStep = stepBefore;

        Set<String> handles = driver.getWindowHandles();

        Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getWindowHandles);
        if ((handles != null) && (handles.size() > 0)) {
            StringBuffer sb = new StringBuffer();
            for (String h : handles) {
                sb.append(h).append(",");
            }
            String handlesAsString = sb.toString();
            // remove the trailing ','
            handlesAsString.substring(handlesAsString.length() - 1);
            stepAfter.setReturnValue(handlesAsString);
        }

        dispatcher.afterGetWindowHandles(stepAfter, handles);
        return handles;
    }

    /**
     * In addition to quitting the wrapped WebDriver instance, this call also will call
     * {@link WebDriverEventListener#closeListener()} on all registered listeners.
     * 
     * @see WebDriver#quit()
     */
    @Override
    public void quit() {
        Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.quit);
        dispatcher.beforeQuit(stepBefore);
        currentStep = stepBefore;

        driver.quit();

        Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.quit);
        dispatcher.afterQuit(stepAfter);
        closeListeners();
    }

    @Override
    public String getPageSource() {
        Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getPageSource);
        dispatcher.beforeGetPageSource(stepBefore);
        currentStep = stepBefore;

        String source = driver.getPageSource();

        Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getPageSource);
        stepAfter.setReturnValue(source);
        dispatcher.afterGetPageSource(stepAfter, source);
        return source;
    }

    @Override
    public Object executeScript(String script, Object... args) {
        if (driver instanceof JavascriptExecutor) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.executeScript);
            stepBefore.setParam1(script);
            // TODO handle args
            dispatcher.beforeExecuteScript(stepBefore, script, args);
            currentStep = stepBefore;

            Object[] usedArgs = unpackWrappedArgs(args);
            Object result = ((JavascriptExecutor) driver).executeScript(script, usedArgs);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.executeScript);
            stepAfter.setParam1(script);
            // TODO handle args and returned result
            dispatcher.afterExecuteScript(stepAfter, script, args);
            return result;
        }
        throw new UnsupportedOperationException("Underlying driver instance does not support executing javascript");
    }

    @Override
    public Object executeAsyncScript(String script, Object... args) {
        if (driver instanceof JavascriptExecutor) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.executeAsyncScript);
            stepBefore.setParam1(script);
            // TODO handle args
            dispatcher.beforeExecuteAsyncScript(stepBefore, script, args);
            currentStep = stepBefore;

            Object[] usedArgs = unpackWrappedArgs(args);
            Object result = ((JavascriptExecutor) driver).executeAsyncScript(script, usedArgs);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.executeAsyncScript);
            stepAfter.setParam1(script);
            // TODO handle args and returned result
            dispatcher.afterExecuteAsyncScript(stepAfter, script, args);
            return result;
        }
        throw new UnsupportedOperationException("Underlying driver instance does not support executing javascript");
    }

    private Object[] unpackWrappedArgs(Object... args) {
        // Walk the args: the various drivers expect unpacked versions of the elements
        Object[] usedArgs = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            usedArgs[i] = unpackWrappedElement(args[i]);
        }
        return usedArgs;
    }

    private Object unpackWrappedElement(Object arg) {
        if (arg instanceof List<?>) {
            List<?> aList = (List<?>) arg;
            List<Object> toReturn = new ArrayList<>();
            for (Object anAList : aList) {
                toReturn.add(unpackWrappedElement(anAList));
            }
            return toReturn;
        } else if (arg instanceof Map<?, ?>) {
            Map<?, ?> aMap = (Map<?, ?>) arg;
            Map<Object, Object> toReturn = new HashMap<>();
            for (Object key : aMap.keySet()) {
                toReturn.put(key, unpackWrappedElement(aMap.get(key)));
            }
            return toReturn;
        } else if (arg instanceof EventFiringWebElement) {
            return ((EventFiringWebElement) arg).getWrappedElement();
        } else {
            return arg;
        }
    }

    // TODO add to WebDriverEventListener interface
    @Override
    public <X> X getScreenshotAs(OutputType<X> target) throws WebDriverException {
        if (driver instanceof TakesScreenshot) {
            return ((TakesScreenshot) driver).getScreenshotAs(target);
        }

        throw new UnsupportedOperationException("Underlying driver instance does not support taking screenshots");
    }

    @Override
    public TargetLocator switchTo() {
        return new EventFiringTargetLocator(driver.switchTo());
    }

    @Override
    public Navigation navigate() {
        return new EventFiringNavigation(driver.navigate());
    }

    @Override
    public Options manage() {
        return new EventFiringOptions(driver.manage());
    }

    private WebElement createWebElement(WebElement from) {
        return new EventFiringWebElement(from);
    }

    @Override
    public Keyboard getKeyboard() {
        if (driver instanceof HasInputDevices) {
            return new EventFiringKeyboard(driver, dispatcher);
        } else {
            throw new UnsupportedOperationException(
                    "Underlying driver does not implement advanced user interactions yet.");
        }
    }

    @Override
    public Mouse getMouse() {
        if (driver instanceof HasInputDevices) {
            return new EventFiringMouse(driver, dispatcher);
        } else {
            throw new UnsupportedOperationException(
                    "Underlying driver does not implement advanced user interactions yet.");
        }
    }

    @Override
    public TouchScreen getTouch() {
        if (driver instanceof HasTouchScreen) {
            return new EventFiringTouch(driver, dispatcher);
        } else {
            throw new UnsupportedOperationException(
                    "Underlying driver does not implement advanced user interactions yet.");
        }
    }

    /*---------------------------------------------------------------------------
     * Section for all commands called directly from WebElement object.
     *---------------------------------------------------------------------------*/

    private class EventFiringWebElement implements WebElement, WrapsElement, WrapsDriver, Locatable {

        private final WebElement element;
        private final WebElement underlyingElement;

        private EventFiringWebElement(final WebElement element) {
            this.element = (WebElement) Proxy.newProxyInstance(WebDriverEventListener.class.getClassLoader(),
                    extractInterfaces(element), new InvocationHandler() {
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if (method.getName().equals("getWrappedElement")) {
                                return element;
                            }
                            try {
                                return method.invoke(element, args);
                            } catch (InvocationTargetException e) {
                                Step step = new Step(Type.Exception, stepNumber, Cmd.quit);
                                step.setIssue(e);
                                dispatcher.onException(step, Cmd.quit, e.getTargetException());
                                throw e.getTargetException();
                            }
                        }
                    });
            this.underlyingElement = element;
        }

        @Override
        public void click() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.clickByElement);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            currentStep = stepBefore;

            dispatcher.beforeClick(stepBefore, element);

            element.click();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.clickByElement);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterClick(stepAfter, element);
        }

        @Override
        public void clear() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.clear);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeClear(stepBefore, element);
            currentStep = stepBefore;

            element.clear();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.clear);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterClear(stepAfter, element);
        }

        @Override
        public WebElement findElement(By by) {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.findElementByElement);
            stepBefore.setParam1(Step.getLocatorFromBy(by));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeFindElementByElement(stepBefore, by, element);
            currentStep = stepBefore;

            WebElement returnedElement = element.findElement(by);

            Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.findElementByElement);
            stepAfter.setParam1(Step.getLocatorFromBy(by));
            stepAfter.setReturnValue(Step.getLocatorFromWebElement(returnedElement));
            stepAfter.setReturnObject(returnedElement);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterFindElementByElement(stepAfter, returnedElement, by, element);
            return createWebElement(returnedElement);
        }

        @Override
        public List<WebElement> findElements(By by) {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.findElementsByElement);
            stepBefore.setParam1(Step.getLocatorFromBy(by));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeFindElementByElement(stepBefore, by, element);
            currentStep = stepBefore;

            List<WebElement> returnedElements = element.findElements(by);

            Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.findElementsByElement);
            stepAfter.setParam1(Step.getLocatorFromBy(by));
            if (returnedElements.size() > 0) {
                stepAfter.setReturnValue(Step.getLocatorFromWebElement(returnedElements.get(0)));
            }
            stepAfter.setReturnObject(returnedElements);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterFindElementsByElement(stepAfter, returnedElements, by, element);

            List<WebElement> returnedAndWrappedElements = new ArrayList<>(returnedElements.size());
            for (WebElement element : returnedElements) {
                returnedAndWrappedElements.add(createWebElement(element));
            }
            return returnedAndWrappedElements;
        }

        @Override
        public String getAttribute(String name) {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getAttribute);
            stepBefore.setParam1(name);
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeGetAttribute(stepBefore, name, element);
            currentStep = stepBefore;

            String value = element.getAttribute(name);

            Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getAttribute);
            stepAfter.setParam1(name);
            stepAfter.setReturnValue(value);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterGetAttribute(stepAfter, value, name, element);
            return value;
        }

        @Override
        public String getCssValue(String propertyName) {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getCssValue);
            stepBefore.setParam1(propertyName);
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeGetCssValue(stepBefore, propertyName, element);
            currentStep = stepBefore;

            String value = element.getCssValue(propertyName);

            Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getCssValue);
            stepAfter.setParam1(propertyName);
            stepAfter.setReturnValue(value);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterGetCssValue(stepAfter, propertyName, value, element);
            return value;
        }

        @Override
        public String getTagName() {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getTagName);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeGetTagName(stepBefore, element);
            currentStep = stepBefore;

            String tagName = element.getTagName();

            Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getTagName);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            stepAfter.setReturnValue(tagName);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterGetTagName(stepAfter, tagName, element);
            return tagName;
        }

        @Override
        public String getText() {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getText);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeGetText(stepBefore, element);
            currentStep = stepBefore;

            String text = element.getText();

            Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getText);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            stepAfter.setReturnValue(text);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterGetText(stepAfter, text, element);
            return text;
        }

        @Override
        public boolean isDisplayed() {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.isDisplayed);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeIsDisplayed(stepBefore, element);
            currentStep = stepBefore;

            boolean isDisplayed = element.isDisplayed();

            Step stepAfter = new Step(Type.AfterGather, stepNumber++, Cmd.isDisplayed);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            stepAfter.setReturnValue("" + isDisplayed);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterIsDisplayed(stepAfter, isDisplayed, element);
            return isDisplayed;
        }

        @Override
        public boolean isEnabled() {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.isEnabled);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeIsEnabled(stepBefore, element);
            currentStep = stepBefore;

            boolean isEnabled = element.isEnabled();

            Step stepAfter = new Step(Type.AfterGather, stepNumber++, Cmd.isEnabled);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            stepAfter.setReturnValue("" + isEnabled);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterIsEnabled(stepAfter, isEnabled, element);
            return isEnabled;
        }

        @Override
        public boolean isSelected() {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.isSelected);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeIsSelected(stepBefore, element);
            currentStep = stepBefore;

            boolean isSelected = element.isSelected();

            Step stepAfter = new Step(Type.AfterGather, stepNumber++, Cmd.isSelected);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            stepAfter.setReturnValue("" + isSelected);
            stepAfter.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.afterIsSelected(stepAfter, isSelected, element);
            return isSelected;
        }

        // TODO add to WebDriverEventListener interface
        @Override
        public Point getLocation() {
            Point point = element.getLocation();
            return point;
        }

        // TODO add to WebDriverEventListener interface
        @Override
        public Dimension getSize() {
            Dimension dimension = element.getSize();
            return dimension;
        }

        // TODO add to WebDriverEventListener interface
        @Override
        public Rectangle getRect() {
            Rectangle rect = element.getRect();
            return rect;
        }

        @Override
        public void sendKeys(CharSequence... keysToSend) {
            StringBuffer buffer = new StringBuffer();
            if ((keysToSend != null) && (keysToSend.length > 0)) {
                for (int i = 0; i < keysToSend.length; i++) {
                    buffer.append(keysToSend[i]);
                }
            }
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.sendKeysByElement);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setParam2(buffer.toString());
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeSendKeys(stepBefore, element, keysToSend);
            currentStep = stepBefore;

            element.sendKeys(keysToSend);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.sendKeysByElement);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            stepAfter.setParam2(buffer.toString());
            dispatcher.afterSendKeys(stepAfter, element, keysToSend);
        }

        @Override
        public void submit() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.submit);
            stepBefore.setParam1(Step.getLocatorFromWebElement(element));
            stepBefore.setElementLocator(Step.getLocatorFromWebElement(element));
            dispatcher.beforeSubmit(stepBefore, element);
            currentStep = stepBefore;

            element.submit();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.submit);
            stepAfter.setParam1(Step.getLocatorFromWebElement(element));
            dispatcher.afterSubmit(stepAfter, element);
        }

        public WebElement getWrappedElement() {
            return underlyingElement;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof WebElement)) {
                return false;
            }

            WebElement other = (WebElement) obj;
            if (other instanceof WrapsElement) {
                other = ((WrapsElement) other).getWrappedElement();
            }

            return underlyingElement.equals(other);
        }

        @Override
        public int hashCode() {
            return underlyingElement.hashCode();
        }

        @Override
        public String toString() {
            return underlyingElement.toString();
        }

        public WebDriver getWrappedDriver() {
            return driver;
        }

        @Override
        public Coordinates getCoordinates() {
            return ((Locatable) underlyingElement).getCoordinates();
        }

        @Override
        public <X> X getScreenshotAs(OutputType<X> outputType) throws WebDriverException {
            return element.getScreenshotAs(outputType);
        }
    }

    /*---------------------------------------------------------------------------
     * Section for all commands called directly from WebDriver.Navigation object.
     *---------------------------------------------------------------------------*/

    private class EventFiringNavigation implements Navigation {

        private final WebDriver.Navigation navigation;

        EventFiringNavigation(Navigation navigation) {
            this.navigation = navigation;
        }

        @Override
        public void back() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.back);
            dispatcher.beforeBack(stepBefore);
            currentStep = stepBefore;

            navigation.back();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.back);
            dispatcher.afterBack(stepAfter);
        }

        @Override
        public void forward() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.forward);
            dispatcher.beforeForward(stepBefore);
            currentStep = stepBefore;

            navigation.forward();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.forward);
            dispatcher.afterForward(stepAfter);
        }

        @Override
        public void refresh() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.refresh);
            dispatcher.beforeRefresh(stepBefore);
            currentStep = stepBefore;

            navigation.refresh();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.refresh);
            dispatcher.afterRefresh(stepAfter);
        }

        @Override
        public void to(URL url) {
            to(String.valueOf(url));
        }

        @Override
        public void to(String url) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.to);
            stepBefore.setParam1(url);
            currentStep = stepBefore;

            navigation.to(url);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.to);
            stepAfter.setParam1(url);
            dispatcher.afterTo(stepAfter, url);
        }
    }

    private class EventFiringOptions implements Options {

        private Options options;

        private EventFiringOptions(Options options) {
            this.options = options;
        }

        @Override
        public Logs logs() {
            return options.logs();
        }

        @Override
        public void addCookie(Cookie cookie) {
            options.addCookie(cookie);
        }

        @Override
        public void deleteCookieNamed(String name) {
            options.deleteCookieNamed(name);
        }

        @Override
        public void deleteCookie(Cookie cookie) {
            options.deleteCookie(cookie);
        }

        @Override
        public void deleteAllCookies() {
            options.deleteAllCookies();
        }

        @Override
        public Set<Cookie> getCookies() {
            Set<Cookie> cookies = options.getCookies();
            return cookies;
        }

        @Override
        public Cookie getCookieNamed(String name) {
            Cookie cookie = options.getCookieNamed(name);
            return cookie;
        }

        @Override
        public Timeouts timeouts() {
            return new EventFiringTimeouts(options.timeouts());
        }

        @Override
        public ImeHandler ime() {
            return options.ime();
        }

        @Override
        public Window window() {
            return new EventFiringWindow(options.window());
        }
    }

    private class EventFiringTimeouts implements Timeouts {

        private final Timeouts timeouts;

        EventFiringTimeouts(Timeouts timeouts) {
            this.timeouts = timeouts;
        }

        @Override
        public Timeouts implicitlyWait(long time, TimeUnit unit) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.implicitlyWait);
            stepBefore.setParam1("" + time);
            stepBefore.setParam2("" + TimeUnit.MILLISECONDS.convert(time, unit));
            dispatcher.beforeImplicitlyWait(stepBefore, time, unit);
            currentStep = stepBefore;

            timeouts.implicitlyWait(time, unit);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.implicitlyWait);
            stepAfter.setParam1("" + time);
            stepAfter.setParam2("" + TimeUnit.MILLISECONDS.convert(time, unit));
            dispatcher.afterImplicitlyWait(stepAfter, time, unit);
            return this;
        }

        @Override
        public Timeouts pageLoadTimeout(long time, TimeUnit unit) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.pageLoadTimeout);
            stepBefore.setParam1("" + time);
            stepBefore.setParam2("" + TimeUnit.MILLISECONDS.convert(time, unit));
            dispatcher.beforePageLoadTimeout(stepBefore, time, unit);
            currentStep = stepBefore;

            timeouts.pageLoadTimeout(time, unit);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.pageLoadTimeout);
            stepAfter.setParam1("" + time);
            stepAfter.setParam2("" + TimeUnit.MILLISECONDS.convert(time, unit));
            dispatcher.afterPageLoadTimeout(stepAfter, time, unit);
            return this;
        }

        @Override
        public Timeouts setScriptTimeout(long time, TimeUnit unit) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.setScriptTimeout);
            stepBefore.setParam1("" + time);
            stepBefore.setParam2("" + TimeUnit.MILLISECONDS.convert(time, unit));
            dispatcher.beforeSetScriptTimeout(stepBefore, time, unit);

            timeouts.setScriptTimeout(time, unit);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.pageLoadTimeout);
            stepAfter.setParam1("" + time);
            stepAfter.setParam2("" + TimeUnit.MILLISECONDS.convert(time, unit));
            dispatcher.afterSetScriptTimeout(stepAfter, time, unit);
            return this;
        }
    }

    /*---------------------------------------------------------------------------
     * Section for all commands called directly from WebDriver.TargetLocator object.
     *---------------------------------------------------------------------------*/

    private class EventFiringTargetLocator implements TargetLocator {

        private TargetLocator targetLocator;

        private EventFiringTargetLocator(TargetLocator targetLocator) {
            this.targetLocator = targetLocator;
        }

        @Override
        public WebElement activeElement() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.activeElement);
            dispatcher.beforeActiveElement(stepBefore);
            currentStep = stepBefore;

            WebElement activeElement = targetLocator.activeElement();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.activeElement);
            stepAfter.setReturnValue(Step.getLocatorFromWebElement(activeElement));
            stepAfter.setReturnObject(activeElement);
            dispatcher.afterActiveElement(stepAfter, activeElement);
            return createWebElement(activeElement);
        }

        @Override
        public Alert alert() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.alert);
            dispatcher.beforeAlert(stepBefore);
            currentStep = stepBefore;

            Alert alert = targetLocator.alert();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.alert);
            stepAfter.setReturnValue(alert.toString());
            stepAfter.setReturnObject(alert);
            dispatcher.afterAlert(stepAfter, alert);
            return alert;
        }

        @Override
        public WebDriver defaultContent() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.defaultContent);
            dispatcher.beforeDefaultContent(stepBefore);
            currentStep = stepBefore;

            WebDriver frameDriver = targetLocator.defaultContent();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.defaultContent);
            dispatcher.afterDefaultContent(stepAfter);
            return frameDriver;
        }

        @Override
        public WebDriver frame(int frameIndex) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.frameByIndex);
            stepBefore.setParam1("" + frameIndex);
            dispatcher.beforeFrameByIndex(stepBefore, frameIndex);
            currentStep = stepBefore;

            WebDriver frameDriver = targetLocator.frame(frameIndex);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.frameByIndex);
            stepAfter.setParam1("" + frameIndex);
            dispatcher.afterFrameByIndex(stepAfter, frameIndex);
            return frameDriver;
        }

        @Override
        public WebDriver frame(String frameName) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.frameByName);
            stepBefore.setParam1(frameName);
            dispatcher.beforeFrameByName(stepBefore, frameName);
            currentStep = stepBefore;

            WebDriver frameDriver = targetLocator.frame(frameName);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.frameByName);
            stepAfter.setParam1(frameName);
            dispatcher.afterFrameByName(stepAfter, frameName);
            return frameDriver;
        }

        @Override
        public WebDriver frame(WebElement frameElement) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.frameByElement);
            stepBefore.setParam1(frameElement.toString());
            dispatcher.beforeFrameByElement(stepBefore, frameElement);
            currentStep = stepBefore;

            WebDriver frameDriver = targetLocator.frame(frameElement);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.frameByElement);
            stepAfter.setParam1(frameElement.toString());
            dispatcher.afterFrameByElement(stepAfter, frameElement);
            return frameDriver;
        }

        @Override
        public WebDriver parentFrame() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.parentFrame);
            dispatcher.beforeParentFrame(stepBefore);
            currentStep = stepBefore;

            WebDriver frameDriver = targetLocator.parentFrame();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.parentFrame);
            dispatcher.afterParentFrame(stepAfter);
            return frameDriver;
        }

        @Override
        public WebDriver window(String windowName) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.window);
            stepBefore.setParam1(windowName);
            dispatcher.beforeWindow(stepBefore, windowName);
            currentStep = stepBefore;

            WebDriver windowDriver = targetLocator.window(windowName);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.window);
            stepAfter.setParam1(windowName);
            dispatcher.afterWindow(stepAfter, windowName);
            return windowDriver;
        }
    }

    /*---------------------------------------------------------------------------
     * Section for all commands called directly from WebDriver.Window object.
     *---------------------------------------------------------------------------*/

    private class EventFiringWindow implements Window {
        private final Window window;

        EventFiringWindow(Window window) {
            this.window = window;
        }

        @Override
        public void fullscreen() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.fullscreen);
            dispatcher.beforeFullscreen(stepBefore);
            currentStep = stepBefore;

            window.fullscreen();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.fullscreen);
            dispatcher.afterFullscreen(stepAfter);
        }

        @Override
        public Point getPosition() {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getPosition);
            dispatcher.beforeGetPosition(stepBefore);
            currentStep = stepBefore;

            Point point = window.getPosition();

            Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getPosition);
            stepAfter.setReturnValue(point.toString());
            stepAfter.setReturnObject(point);
            dispatcher.afterGetPosition(stepAfter, point);
            return point;
        }

        @Override
        public Dimension getSize() {
            Step stepBefore = new Step(Type.BeforeGather, stepNumber, Cmd.getSize);
            dispatcher.beforeGetSize(stepBefore);
            currentStep = stepBefore;

            Dimension size = window.getSize();

            Step stepAfter = new Step(Type.AfterGather, stepNumber, Cmd.getSize);
            stepAfter.setReturnValue(size.toString());
            stepAfter.setReturnObject(size);
            dispatcher.afterGetSize(stepAfter, size);
            return size;
        }

        @Override
        public void maximize() {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.maximize);
            dispatcher.beforeMaximize(stepBefore);
            currentStep = stepBefore;

            window.maximize();

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.maximize);
            dispatcher.afterMaximize(stepAfter);
        }

        @Override
        public void setPosition(Point targetPosition) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.setPosition);
            stepBefore.setParam1(targetPosition.toString());
            dispatcher.beforeSetPosition(stepBefore, targetPosition);
            currentStep = stepBefore;

            window.setPosition(targetPosition);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.setPosition);
            stepAfter.setParam1(targetPosition.toString());
            dispatcher.afterSetPosition(stepAfter, targetPosition);
        }

        @Override
        public void setSize(Dimension targetSize) {
            Step stepBefore = new Step(Type.BeforeAction, stepNumber, Cmd.setSize);
            stepBefore.setParam1(targetSize.toString());
            dispatcher.beforeSetSize(stepBefore, targetSize);
            currentStep = stepBefore;

            window.setSize(targetSize);

            Step stepAfter = new Step(Type.AfterAction, stepNumber++, Cmd.setSize);
            stepAfter.setParam1(targetSize.toString());
            dispatcher.afterSetSize(stepAfter, targetSize);
        }
    }

    private void closeListeners() {
        defaultEventListener.closeListener();
        for (WebDriverEventListener eventListener : eventListeners) {
            eventListener.closeListener();
        }
    }
}