org.aludratest.service.gui.web.selenium.selenium2.Selenium2Wrapper.java Source code

Java tutorial

Introduction

Here is the source code for org.aludratest.service.gui.web.selenium.selenium2.Selenium2Wrapper.java

Source

/*
 * Copyright (C) 2010-2014 Hamburg Sud and the contributors.
 *
 * Licensed 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 org.aludratest.service.gui.web.selenium.selenium2;

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.aludratest.exception.AutomationException;
import org.aludratest.exception.FunctionalFailure;
import org.aludratest.exception.PerformanceFailure;
import org.aludratest.exception.TechnicalException;
import org.aludratest.service.SystemConnector;
import org.aludratest.service.gui.web.selenium.SeleniumResourceService;
import org.aludratest.service.gui.web.selenium.SeleniumWrapperConfiguration;
import org.aludratest.service.gui.web.selenium.SystemDownloadProvider;
import org.aludratest.service.gui.web.selenium.httpproxy.AuthenticatingHttpProxy;
import org.aludratest.service.gui.web.selenium.selenium2.condition.AbstractAjaxIdleCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.AnyDropDownOptions;
import org.aludratest.service.gui.web.selenium.selenium2.condition.DropDownBoxOptionLabelsPresence;
import org.aludratest.service.gui.web.selenium.selenium2.condition.DropDownBoxOptionValuesPresence;
import org.aludratest.service.gui.web.selenium.selenium2.condition.DropDownOptionLocatable;
import org.aludratest.service.gui.web.selenium.selenium2.condition.ElementAbsence;
import org.aludratest.service.gui.web.selenium.selenium2.condition.ElementEditableCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.ElementEnabledCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.ElementNotEditableCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.ElementNotVisibleCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.ElementValuePresence;
import org.aludratest.service.gui.web.selenium.selenium2.condition.IceFacesAjaxIdleCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.JQueryAjaxIdleCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.MixedElementCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.NotCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.OptionSelected;
import org.aludratest.service.gui.web.selenium.selenium2.condition.PrimeFacesAjaxIdleCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.ValidatingCondition;
import org.aludratest.service.gui.web.selenium.selenium2.condition.WindowPresence;
import org.aludratest.service.locator.element.GUIElementLocator;
import org.aludratest.service.locator.element.IdLocator;
import org.aludratest.service.locator.option.IndexLocator;
import org.aludratest.service.locator.option.OptionLocator;
import org.aludratest.service.locator.window.TitleLocator;
import org.aludratest.service.locator.window.WindowLocator;
import org.aludratest.service.util.ServiceUtil;
import org.aludratest.service.util.TaskCompletionUtil;
import org.aludratest.testcase.event.attachment.Attachment;
import org.aludratest.testcase.event.attachment.BinaryAttachment;
import org.aludratest.testcase.event.attachment.StringAttachment;
import org.aludratest.util.data.helper.DataMarkerCheck;
import org.aludratest.util.retry.RetryService;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.databene.commons.Validator;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.CommandExecutor;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Select;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thoughtworks.selenium.Selenium;

/** Wraps an instance of the {@link Selenium2Facade} and provides methods for accessing UI elements and timing.
 * @author Volker Bergmann
 * @author Marcel Malitz
 * @author Joerg Langnickel */
@SuppressWarnings("javadoc")
public class Selenium2Wrapper {

    private static final Logger LOGGER = LoggerFactory.getLogger(Selenium2Wrapper.class);

    // scripts -----------------------------------------------------------------

    private static final String WINDOW_FOCUS_SCRIPT = "window.focus()";

    private static final String HAS_FOCUS_SCRIPT = "return arguments[0] == window.document.activeElement";

    // static attributes -------------------------------------------------------

    private static ProxyPool proxyPool = null;

    // attributes --------------------------------------------------------------

    private final SeleniumWrapperConfiguration configuration;

    private SeleniumResourceService resourceService;

    private String usedSeleniumHost = null;

    private AuthenticatingHttpProxy proxy;

    SystemConnector systemConnector;

    private WebDriver driver;

    private LocatorSupport locatorSupport;

    private URL seleniumUrl;

    private WebElement highlightedElement;

    public Selenium2Wrapper(SeleniumWrapperConfiguration configuration, SeleniumResourceService resourceService) {
        try {
            this.configuration = configuration;
            this.resourceService = resourceService;
            // TODO support HTTPS
            if ("http".equals(configuration.getUrlOfAutAsUrl().getProtocol())
                    && configuration.isUsingLocalProxy()) {
                this.proxy = getProxyPool().acquire();
                this.proxy.start();
            }

            if (configuration.isUsingRemoteDriver()) {
                this.usedSeleniumHost = resourceService.acquire();
                this.seleniumUrl = new URL(usedSeleniumHost + "/wd/hub");
            }
            this.driver = newDriver();
            this.driver.manage().timeouts().pageLoadTimeout(configuration.getTimeout(), TimeUnit.MILLISECONDS);
            this.locatorSupport = new LocatorSupport(this.driver, configuration);
        } catch (Exception e) {
            LOGGER.error("Error initializing Selenium 2", e);
            String host = usedSeleniumHost;
            forceCloseApplicationUnderTest();
            throw new TechnicalException(e.getMessage() + ". Used Host = " + host, e);
        }
    }

    private synchronized ProxyPool getProxyPool() {
        if (proxyPool == null && "http".equals(configuration.getUrlOfAutAsUrl().getProtocol())
                && configuration.isUsingLocalProxy()) { // TODO support HTTPS
            URL url = configuration.getUrlOfAutAsUrl();
            proxyPool = new ProxyPool(url.getHost(), url.getPort(), configuration.getMinProxyPort(),
                    resourceService.getHostCount());
        }
        return proxyPool;
    }

    private WebDriver newDriver() {
        try {
            String driverName = configuration.getDriverName();
            Selenium2Driver driverEnum = Selenium2Driver.valueOf(driverName);

            if (configuration.isUsingRemoteDriver()) {
                return driverEnum.newRemoteDriver(seleniumUrl, configuration.getBrowserArguments());
            } else {
                return driverEnum.newLocalDriver(configuration.getBrowserArguments());
            }
        } catch (Exception e) {
            throw new TechnicalException("WebDriver creation failed: ", e);
        }
    }

    /**
     * Closes the application under test respectively the main browser window
     * and all child windows.
     */
    private void closeApplicationUnderTest() {
        if (configuration.getCloseTestappAfterExecution()) {
            forceCloseApplicationUnderTest();
        }
    }

    private void forceCloseApplicationUnderTest() {
        if (this.proxy != null) {
            this.proxy.stop();
            getProxyPool().release(this.proxy);
            this.proxy = null;
        }
        if (this.driver != null) {
            for (String id : getAllWindowIDs()) {
                try {
                    selectWindowByTechnicalName(id);
                    close();
                } catch (Exception e) {
                    // ignore during close
                }
            }
            quit();
        }
        if (this.usedSeleniumHost != null) {
            resourceService.release(this.usedSeleniumHost);
            this.usedSeleniumHost = null;
        }
    }

    /**
     * Closes the application under test.
     */
    public void tearDown() {
        closeApplicationUnderTest();
    }

    public SeleniumWrapperConfiguration getConfiguration() {
        return configuration;
    }

    /**
     * 
     * @return currently used SeleniumHost
     */
    public String getUsedSeleniumHost() {
        return usedSeleniumHost;
    }

    public void refresh() {
        driver.navigate().refresh();
    }

    @SuppressWarnings("unchecked")
    private WebElement doBeforeDelegate(GUIElementLocator locator, boolean visible, boolean actionPending,
            boolean enabled) {
        if (actionPending) {
            waitUntilNotBusy();
        }
        MixedElementCondition condition = new MixedElementCondition(locator, locatorSupport, visible, enabled,
                configuration.isZIndexCheckEnabled());
        try {
            WebElement element = waitFor(condition, configuration.getTimeout());
            highlight(locator);
            return element;
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    private void doAfterDelegate(int taskCompletionTimeout, String operation) {
        if (taskCompletionTimeout >= 0) {
            int timeout = (taskCompletionTimeout == 0 ? configuration.getTaskCompletionTimeout()
                    : taskCompletionTimeout);
            String failureMessage = "After operation " + operation
                    + "() the system remained busy exceeding the timeout of " + timeout + " ms";
            TaskCompletionUtil.waitForActivityAndCompletion(systemConnector, failureMessage,
                    configuration.getTaskStartTimeout(), timeout, configuration.getTaskPollingInterval());
        }
    }

    private void waitUntilNotBusy() {
        if (this.systemConnector != null) {
            int taskCompletionTimeout = configuration.getTaskCompletionTimeout();
            TaskCompletionUtil.waitUntilNotBusy(this.systemConnector, taskCompletionTimeout,
                    configuration.getTaskPollingInterval(),
                    "System not available within the timeout of " + taskCompletionTimeout + " ms");
        }
    }

    public void click(GUIElementLocator locator, String operation, int taskCompletionTimeout) {
        WebElement element = doBeforeDelegate(locator, true, true, false);
        click(element);
        doAfterDelegate(taskCompletionTimeout, operation);
    }

    public void doubleClick(GUIElementLocator locator, String operation, int taskCompletionTimeout) {
        WebElement element = doBeforeDelegate(locator, true, true, false);
        doubleClick(element);
        doAfterDelegate(taskCompletionTimeout, operation);
    }

    public void hover(GUIElementLocator locator, String operation, int taskCompletionTimeout) {
        WebElement element = doBeforeDelegate(locator, true, true, false);
        hover(element);
        doAfterDelegate(taskCompletionTimeout, operation);
    }

    public String clickForDownload(GUIElementLocator locator, int taskCompletionTimeout) {
        WebElement element = doBeforeDelegate(locator, true, false, true);
        // get System Connector for download
        SystemDownloadProvider downloader = systemConnector.getConnector(SystemDownloadProvider.class);
        if (downloader != null) {
            Attachment att = downloader.downloadFile(configuration.getUrlOfAutAsUrl(), element,
                    driver.manage().getCookies());
            if (att != null) {
                return att.getFileName() + ":" + att.getFileDataAsBase64String();
            } else {
                throw new FunctionalFailure("Expected file download, but no download available");
            }
        } else {
            throw new AutomationException(
                    "A SystemDownloadProvider system connector must be available for downloads");
        }
    }

    public void select(GUIElementLocator locator, final OptionLocator optionLocator, int taskCompletionTimeout) {
        WebElement element = doBeforeDelegate(locator, true, true, true);
        select(optionLocator, element);
        doAfterDelegate(taskCompletionTimeout, "select");
    }

    public void type(GUIElementLocator locator, final String value, int taskCompletionTimeout) {
        WebElement element = doBeforeDelegate(locator, true, true, true);

        if (configuration.isTypeSafemode()) {
            // activate element with a click
            element.click();
            element = driver.switchTo().activeElement();
        }

        // setValue instead of sendKeys to ensure field is reset, and to work with file component
        setValue(element, value);
        doAfterDelegate(taskCompletionTimeout, "type");
    }

    public void sendKeys(GUIElementLocator locator, String keys, int taskCompletionTimeout) {
        WebElement element = doBeforeDelegate(locator, true, true, false);
        sendKeys(element, keys);
        doAfterDelegate(taskCompletionTimeout, "sendKeys");
    }

    public String getText(GUIElementLocator locator, Boolean visible) {
        WebElement element = doBeforeDelegate(locator, visible, false, false);
        String text = getText(element);
        doAfterDelegate(-1, "getText");
        return text;
    }

    public boolean isChecked(GUIElementLocator locator) {
        WebElement element = doBeforeDelegate(locator, true, false, false);
        LOGGER.debug("isChecked({})", locator);
        Boolean returnValue = element.isSelected();
        doAfterDelegate(-1, "isChecked");
        return returnValue.booleanValue();
    }

    public String[] getSelectOptions(GUIElementLocator locator) {
        WebElement element = doBeforeDelegate(locator, true, false, false);
        LOGGER.debug("getSelectOptions({})", locator);
        Select select = new Select(element);
        List<WebElement> options = select.getOptions();
        String[] labels = new String[options.size()];
        for (int i = 0; i < options.size(); i++) {
            labels[i] = options.get(i).getText();
        }
        doAfterDelegate(-1, "getSelectOptions");
        return labels;
    }

    public String getSelectedValue(GUIElementLocator locator) {
        WebElement element = doBeforeDelegate(locator, true, false, false);
        String selectedValue = getSelectedLabel(element);
        doAfterDelegate(-1, "getSelectedValue");
        return selectedValue;
    }

    public String getSelectedLabel(GUIElementLocator locator) {
        WebElement element = doBeforeDelegate(locator, true, false, false);
        String selectedLabel = getSelectedLabel(element);
        doAfterDelegate(-1, "getSelectedLabel");
        return selectedLabel;
    }

    public String getValue(GUIElementLocator locator) {
        LOGGER.debug("getValue({})", locator);
        return getValue(findElementImmediately(locator));
    }

    // window operations -------------------------------------------------------

    @SuppressWarnings("unchecked")
    private void waitForWindow(WindowLocator locator) {
        LOGGER.debug("waitForWindow({})", locator);
        try {
            waitFor(new WindowPresence(locator, this), configuration.getTimeout());
        } catch (TimeoutException e) {
            throw new AutomationException("Window not found");
        }
    }

    public void selectWindow(WindowLocator locator) {
        LOGGER.debug("selectWindow({})", locator);
        waitForWindow(locator);
        selectWindowImmediately(locator);
    }

    public void selectWindowImmediately(WindowLocator locator) {
        LOGGER.debug("selectWindowImmediately({})", locator);
        if (locator instanceof TitleLocator) {
            String requestedTitle = locator.toString();

            // performance tweak: if the current window is the requested one, return immediately
            try {
                LOGGER.debug("driver.getTitle()");
                if (requestedTitle.equals(driver.getTitle())) {
                    return;
                }
            } catch (WebDriverException e) {
                // this happens, when trying to call getTitle()
                // on a driver which has just close()d the recent window.
                // In this case, I fall back to iterating all windows below
            }

            removeHighlight();

            // iterate all windows and return the one with the desired title
            StringBuilder sb = new StringBuilder();
            for (String handle : getWindowHandles()) {
                try {
                    LOGGER.debug("driver.switchTo().window()", locator);
                    String title = driver.switchTo().window(handle).getTitle();
                    if (sb.length() > 0) {
                        sb.append(", ");
                    }
                    sb.append("\"").append(title).append("\"");
                    if (requestedTitle.equals(title)) {
                        return;
                    }
                } catch (WebDriverException e) {
                    // ignore; window has been removed in the meantime
                }
            }

            // if the window has not been found, throw an ElementNotFoundException
            throw new AutomationException("Element not found. Available window titles are: " + sb.toString());
        } else {
            throw ServiceUtil.newUnsupportedLocatorException(locator);
        }
    }

    public void selectWindowByTechnicalName(String windowId) {
        LOGGER.debug("selectWindowByTechnicalName({})", windowId);
        removeHighlight();
        LOGGER.debug("driver.switchTo().window()", windowId);
        driver.switchTo().window(windowId);
    }

    public Map<String, String> getAllWindowHandlesAndTitles() {
        LOGGER.debug("getAllWindowHandlesAndTitles()");
        Map<String, String> handlesAndTitles = new HashMap<String, String>();
        // store handle and title of current window
        String initialWindowHandle;
        try {
            LOGGER.debug("driver.getWindowHandle()");
            initialWindowHandle = driver.getWindowHandle();
        } catch (NoSuchWindowException e) {
            // fallback to next best window - current window has just been closed!
            LOGGER.debug("driver.getWindowHandles()");
            Set<String> handles = driver.getWindowHandles();
            if (handles.isEmpty()) {
                return Collections.emptyMap();
            }

            initialWindowHandle = handles.iterator().next();
            LOGGER.debug("driver.switchTo().window(initialWindowHandle)");
            driver.switchTo().window(initialWindowHandle);
        }

        try {
            String title = getTitle();
            handlesAndTitles.put(initialWindowHandle, title);
        } catch (WebDriverException e) {
            // ignore current window
            LOGGER.warn("Could not determine title of current window. Assuming window has just been closed.");
        }

        // iterate all other windows by handle and get their titles
        String currentHandle = initialWindowHandle;
        Set<String> handles = getWindowHandles();
        for (String handle : handles) {
            if (!handle.equals(initialWindowHandle)) {
                LOGGER.debug("Switching to window with handle {}", handle);
                try {
                    LOGGER.debug("driver.switchTo().window(handle)");
                    driver.switchTo().window(handle);
                    currentHandle = handle;
                    String title;
                    handlesAndTitles.put(handle, title = driver.getTitle());
                    LOGGER.debug("Window with handle {} has title '{}'", handle, title);
                } catch (NoSuchWindowException e) {
                    // ignore this window
                } catch (WebDriverException e) {
                    // ignore this window
                    LOGGER.warn("WebDriverException when querying window with handle " + handle
                            + ". Assuming window has just been closed.");
                }
            }
        }
        // switch back to the original window
        if (!currentHandle.equals(initialWindowHandle)) {
            try {
                LOGGER.debug("driver.switchTo().window(initialWindowHandle)");
                driver.switchTo().window(initialWindowHandle);
            } catch (WebDriverException e) {
                // selenium could now be on an unexpected window
                LOGGER.warn(
                        "Could not switch back to initial window after window iteration. Active window is now unspecified.");
            }
        }
        return handlesAndTitles;
    }

    /** @see Selenium#getAllWindowTitles() */
    public String[] getAllWindowTitles() {
        LOGGER.debug("getAllWindowTitles()");
        Map<String, String> handlesAndTitles = getAllWindowHandlesAndTitles();
        Collection<String> titles = handlesAndTitles.values();
        LOGGER.debug("getAllWindowTitles() -> {}", titles);
        return titles.toArray(new String[handlesAndTitles.size()]);
    }

    /** @see Selenium#getAllWindowIds() */
    public String[] getAllWindowIDs() {
        LOGGER.debug("getWindowHandles()");
        Set<String> handles = getWindowHandles();
        return handles.toArray(new String[handles.size()]);
    }

    /** @see Selenium#getAllWindowNames()  */
    public String[] getAllWindowNames() {
        LOGGER.debug("getAllWindowNames()");
        String current = driver.getWindowHandle();
        List<String> names = new ArrayList<String>();
        for (String handle : getWindowHandles()) {
            LOGGER.debug("driver.switchTo().window(handle)");
            driver.switchTo().window(handle);
            names.add(executeScript("return window.name").toString());
        }
        LOGGER.debug("driver.switchTo().window(current)");
        driver.switchTo().window(current);
        return names.toArray(new String[names.size()]);
    }

    /** @see Selenium#getTitle() */
    public String getTitle() {
        LOGGER.debug("getTitle()");
        AludraSeleniumHttpCommandExecutor executor = null;
        if (driver instanceof RemoteWebDriver) {
            CommandExecutor ce = ((RemoteWebDriver) driver).getCommandExecutor();
            if (ce instanceof AludraSeleniumHttpCommandExecutor) {
                executor = (AludraSeleniumHttpCommandExecutor) ce;
                // also reduce timeout for TCP connection, in case remote hangs
                executor.setRequestTimeout(5000);
            }
        }
        try {
            return driver.getTitle();
        } finally {
            if (executor != null) {
                executor.setRequestTimeout(0);
            }
        }
    }

    public String getWindowHandle() {
        LOGGER.debug("getWindowHandle()");
        return driver.getWindowHandle();
    }

    /** @see Selenium#windowMaximize() */
    public void windowMaximize() {
        LOGGER.debug("windowMaximize()");
        driver.manage().window().maximize();
    }

    /** @see Selenium#windowFocus() */
    public void windowFocus() {
        LOGGER.debug("windowFocus()");
        executeScript(WINDOW_FOCUS_SCRIPT);
    }

    private Set<String> getWindowHandles() {
        LOGGER.debug("getWindowHandles()");
        // try up to three times when getting strange WebDriverExceptions
        Callable<Set<String>> callable = new Callable<Set<String>>() {
            @Override
            public Set<String> call() throws Exception {
                try {
                    Set<String> handles = driver.getWindowHandles();
                    LOGGER.debug("getWindowHandles() -> {}", handles);
                    return handles;
                } catch (Exception e) {
                    // wait a little bit in case of exception, e.g. for browser window to close
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ie) {
                    }
                    throw e;
                }
            }
        };
        try {
            if (driver instanceof RemoteWebDriver) {
                // also reduce timeout for TCP connection, in case remote hangs
                CommandExecutor executor = ((RemoteWebDriver) driver).getCommandExecutor();
                if (executor instanceof AludraSeleniumHttpCommandExecutor) {
                    ((AludraSeleniumHttpCommandExecutor) executor).setRequestTimeout(5000);
                }
            }
            return RetryService.call(callable, WebDriverException.class, 2);
        } catch (Throwable t) {
            LOGGER.error("Could not retrieve window handles", t);
            return Collections.emptySet();
        } finally {
            if (driver instanceof RemoteWebDriver) {
                // also reduce timeout for TCP connection, in case remote hangs
                CommandExecutor executor = ((RemoteWebDriver) driver).getCommandExecutor();
                if (executor instanceof AludraSeleniumHttpCommandExecutor) {
                    ((AludraSeleniumHttpCommandExecutor) executor).setRequestTimeout(0);
                }
            }
        }
    }

    // iframe operations -------------------------------------------------------

    public void switchToIFrame(GUIElementLocator iframeLocator) {
        LOGGER.debug("switchToIFrame({})", iframeLocator);
        if (iframeLocator != null) {
            WebElement element = waitUntilPresent(iframeLocator, configuration.getTimeout());
            element = LocatorSupport.unwrap(element);
            driver.switchTo().frame(element);
        } else {
            driver.switchTo().defaultContent();
        }
    }

    public boolean hasFocus(GUIElementLocator locator) {
        WebElement element = doBeforeDelegate(locator, true, false, true);
        LOGGER.debug("hasFocus({})", locator);
        boolean returnValue = (Boolean) executeScript(HAS_FOCUS_SCRIPT, element);
        doAfterDelegate(-1, "hasFocus");
        return returnValue;
    }

    public void focus(GUIElementLocator locator) {
        LOGGER.debug("focus({})", locator);
        WebElement element = waitUntilEnabled(locator, configuration.getTimeout());
        executeScript("arguments[0].focus()", element);
    }

    public String getAttributeValue(final GUIElementLocator locator, String attributeName) {
        WebElement element = doBeforeDelegate(locator, true, false, false);
        LOGGER.debug("WebElement.getAttributeValue({})", attributeName);
        String returnValue = element.getAttribute(attributeName);
        doAfterDelegate(-1, "getAttributeValue");
        return returnValue;
    }

    public void keyPress(int keycode) {
        LOGGER.debug("keyPress({})", keycode);
        sendKeys(driver.switchTo().activeElement(), String.valueOf((char) keycode));
    }

    public void close() {
        LOGGER.debug("close()");
        try {
            driver.close();
        } catch (WebDriverException e) {
            // ignore this
        }
    }

    public void quit() {
        LOGGER.debug("quit()");
        driver.quit();
    }

    public String getTable(GUIElementLocator locator, int row, int col) {
        LOGGER.debug("getTable({})", locator);
        WebElement table = findElementImmediately(locator);
        List<WebElement> tbodies = table.findElements(By.tagName("tbody"));
        WebElement rowHolder = (tbodies.size() > 0 ? tbodies.get(0) : table);
        List<WebElement> trs = rowHolder.findElements(By.tagName("tr"));
        if (row >= trs.size()) {
            throw new AutomationException(
                    "Table cell not found. " + "Requested row index " + row + " of " + trs.size() + " rows ");
        }
        List<WebElement> tds = trs.get(row).findElements(By.tagName("td"));
        if (col >= tds.size()) {
            throw new AutomationException(
                    "Table cell not found. " + "Requested column index " + col + " of " + tds.size() + " cells ");
        }
        return tds.get(col).getText();
    }

    // features that require intervention for authentication -------------------

    public void open(String url) {
        LOGGER.debug("open({})", url);
        try {
            driver.get(mapUrl(url));

            // do PhantomJS initialization here, if desired
            if (configuration.getPhantomJsInitScript() != null && (driver instanceof PhantomJSDriver)) {
                Object result = ((PhantomJSDriver) driver).executePhantomJS(
                        FileUtils.readFileToString(new File(configuration.getPhantomJsInitScript())));
                LOGGER.debug("Result of PhantomJS init script: " + result);
            }
        } catch (TimeoutException e) {
            throw new PerformanceFailure("Timed out opening '" + url + "': " + e.getMessage(), e);
        } catch (WebDriverException e) {
            String message = e.getMessage();
            if (message != null && message.contains("Timed out")) {
                throw new PerformanceFailure("Timed out opening '" + url + "': " + e.getMessage(), e);
            }
        } catch (IOException e) {
            // could not read init script
            throw new AutomationException(
                    "Could not read PhantomJS init script " + configuration.getPhantomJsInitScript());
        }
    }

    private String mapUrl(String url) {
        String mappedUrl = proxy != null ? proxy.mapTargetToProxyUrl(url) : url;
        LOGGER.debug("mapUrl({}) -> {}", url, mappedUrl);
        return mappedUrl;
    }

    public void addCustomRequestHeader(String key, String value) {
        LOGGER.debug("addCustomRequestHeader({})", key, value);
        if (proxy != null) {
            proxy.setCustomRequestHeader(key, value);
        }
    }

    // wait features -----------------------------------------------------------

    public WebElement waitUntilPresent(GUIElementLocator locator, long timeOutInMillis) {
        return locatorSupport.waitUntilPresent(locator, timeOutInMillis);
    }

    @SuppressWarnings("unchecked")
    public WebElement waitUntilEnabled(GUIElementLocator locator, long timeOutInMillis) {
        ElementEnabledCondition condition = new ElementEnabledCondition(locator, locatorSupport);
        try {
            return waitFor(condition, timeOutInMillis, NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage()); // NOSONAR
        }
    }

    @SuppressWarnings("unchecked")
    public WebElement waitUntilNotEnabled(GUIElementLocator locator, long timeOutInMillis) {
        ElementEnabledCondition condition = new ElementEnabledCondition(locator, locatorSupport, false);
        try {
            return waitFor(condition, timeOutInMillis, NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage()); // NOSONAR
        }
    }

    @SuppressWarnings("unchecked")
    public WebElement waitUntilEditable(GUIElementLocator locator, long timeOutInMillis) {
        ElementEditableCondition condition = new ElementEditableCondition(locator, locatorSupport);
        try {
            return waitFor(condition, timeOutInMillis, NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage()); // NOSONAR
        }
    }

    @SuppressWarnings("unchecked")
    public WebElement waitUntilNotEditable(GUIElementLocator locator, long timeOutInMillis) {
        ElementNotEditableCondition condition = new ElementNotEditableCondition(locator, locatorSupport);
        try {
            return waitFor(condition, timeOutInMillis, NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage()); // NOSONAR
        }
    }

    @SuppressWarnings("unchecked")
    public void waitUntilVisible(GUIElementLocator locator, long timeOutInMillis) {
        try {
            waitFor(new MixedElementCondition(locator, locatorSupport, true, false,
                    configuration.isZIndexCheckEnabled()), timeOutInMillis, NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException("The element is not visible."); // NOSONAR
        }
    }

    @SuppressWarnings("unchecked")
    public void waitUntilNotVisible(GUIElementLocator locator, long timeOutInMillis) {
        try {
            waitFor(new ElementNotVisibleCondition(locator, locatorSupport), timeOutInMillis,
                    NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException("The element is unexpectedly visible."); // NOSONAR
        }
    }

    @SuppressWarnings("unchecked")
    public void waitUntilElementNotPresent(final GUIElementLocator locator, long timeOutInMillis) {
        try {
            waitFor(new ElementAbsence(locator, locatorSupport), timeOutInMillis, NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException("An element was unexpectedly found"); // NOSONAR
        }
    }

    @SuppressWarnings("unchecked")
    public void waitUntilInForeground(final GUIElementLocator locator, long timeOutInMillis) {
        MixedElementCondition condition = new MixedElementCondition(locator, locatorSupport, false, false,
                configuration.isZIndexCheckEnabled());
        try {
            waitFor(condition, timeOutInMillis, NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public String[] waitForAnyDropDownOptionLabels(final GUIElementLocator dropDownLocator) {
        AnyDropDownOptions condition = new AnyDropDownOptions(dropDownLocator,
                AnyDropDownOptions.DROPDOWN_OPTION_LABEL_PROPERTY, locatorSupport);
        try {
            return waitFor(condition, configuration.getTimeout(), NoSuchElementException.class,
                    StaleElementReferenceException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public String[] waitForAnyDropDownOptionValues(final GUIElementLocator dropDownLocator) {
        AnyDropDownOptions condition = new AnyDropDownOptions(dropDownLocator,
                AnyDropDownOptions.DROPDOWN_OPTION_VALUE_PROPERTY, locatorSupport);
        try {
            return waitFor(condition, configuration.getTimeout(), NoSuchElementException.class,
                    StaleElementReferenceException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public void waitForDropDownEntryLocatablity(final OptionLocator entryLocator,
            final GUIElementLocator dropDownLocator) {
        DropDownOptionLocatable condition = new DropDownOptionLocatable(dropDownLocator, entryLocator,
                locatorSupport);
        try {
            waitFor(condition, configuration.getTimeout(), NoSuchElementException.class,
                    StaleElementReferenceException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public void waitForDropDownEntries(GUIElementLocator dropDownLocator, String[] labels, boolean contains,
            boolean checkOrder) {
        DropDownBoxOptionLabelsPresence condition = new DropDownBoxOptionLabelsPresence(dropDownLocator, labels,
                contains, checkOrder, locatorSupport);
        try {
            waitFor(condition, configuration.getTimeout(), NoSuchElementException.class,
                    StaleElementReferenceException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public void waitForDropDownValues(GUIElementLocator dropDownLocator, String[] values, boolean contains,
            boolean checkOrder) {
        DropDownBoxOptionValuesPresence condition = new DropDownBoxOptionValuesPresence(dropDownLocator, values,
                contains, checkOrder, locatorSupport);
        try {
            waitFor(condition, configuration.getTimeout(), NoSuchElementException.class,
                    StaleElementReferenceException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public String waitForValue(GUIElementLocator locator) {
        ElementValuePresence condition = new ElementValuePresence(locator, locatorSupport);
        try {
            return waitFor(condition, configuration.getTimeout(), NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public String waitForSelection(GUIElementLocator locator) {
        OptionSelected condition = new OptionSelected(locator, locatorSupport);
        try {
            return waitFor(condition, configuration.getTimeout(), NoSuchElementException.class);
        } catch (TimeoutException e) {
            throw new AutomationException(condition.getMessage());
        }
    }

    public void waitForTextValidity(GUIElementLocator locator, Validator<String> validator) {
        // ensure element is visible (scroll into view)
        waitUntilVisible(locator, configuration.getTimeout());

        waitForValidity(new ValidatingCondition(locator, locatorSupport, validator, "Text") {
            @Override
            protected String getTextToValidate(WebElement element) {
                return getText(element);
            }
        });
    }

    public void waitForValueValidity(GUIElementLocator locator, Validator<String> validator) {
        // ensure element is visible (scroll into view)
        waitUntilVisible(locator, configuration.getTimeout());

        waitForValidity(new ValidatingCondition(locator, locatorSupport, validator, "Value") {
            @Override
            protected String getTextToValidate(WebElement element) {
                return getValue(element);
            }
        });
    }

    public void waitForSelectedLabelValidity(GUIElementLocator locator, Validator<String> validator) {
        waitForValidity(new ValidatingCondition(locator, locatorSupport, validator, "Label") {
            @Override
            protected String getTextToValidate(WebElement element) {
                return getSelectedLabel(element);
            }
        });
    }

    @SuppressWarnings("unchecked")
    public void waitForValidity(ValidatingCondition condition) {
        try {
            waitFor(condition, configuration.getTimeout());
        } catch (TimeoutException e) {
            throw new FunctionalFailure(condition.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    public void waitForWindowToBeClosed(TitleLocator locator, int taskCompletionTimeout) {
        try {
            waitFor(new NotCondition(new WindowPresence(locator, this)),
                    taskCompletionTimeout == -1 ? configuration.getTimeout() : taskCompletionTimeout);
        } catch (TimeoutException e) {
            throw new PerformanceFailure(
                    "Window was not closed within the timeout of " + taskCompletionTimeout + " ms");
        }
    }

    @SuppressWarnings("unchecked")
    public void waitForAjaxOperationEnd(String frameworkName, int maxWaitTime) {
        frameworkName = frameworkName.toLowerCase(Locale.US);
        AbstractAjaxIdleCondition condition = null;

        if ("jquery".equals(frameworkName)) {
            condition = new JQueryAjaxIdleCondition();
        } else if ("primefaces".equals(frameworkName)) {
            condition = new PrimeFacesAjaxIdleCondition();
        } else if ("icefaces".equals(frameworkName)) {
            condition = new IceFacesAjaxIdleCondition();
        }

        if (condition != null) {
            try {
                waitFor(condition, maxWaitTime);
            } catch (TimeoutException e) {
                throw new PerformanceFailure(
                        "AJAX operation was not finished within the timeout of " + maxWaitTime + " ms");
            }
        } else {
            throw new AutomationException("Unsupported JavaScript framework for AJAX check: " + frameworkName);
        }
    }

    private <T> T waitFor(ExpectedCondition<T> condition, long timeOutInMillis,
            Class<? extends Exception>... exceptionsToIgnore) {
        return locatorSupport.waitFor(condition, timeOutInMillis, exceptionsToIgnore);
    }

    // HTML source and screenshot provision ------------------------------------

    public Attachment getPageSource() {
        LOGGER.debug("getPageSource()");
        String pageSource = driver.getPageSource();
        return new StringAttachment("Source", pageSource, configuration.getPageSourceAttachmentExtension());
    }

    public Attachment getScreenshotOfThePage() {
        LOGGER.debug("getScreenshotOfThePage()");
        final String base64Data = captureScreenshotToString();
        final Attachment attachment = getScreenshotAttachment(base64Data);
        return attachment;
    }

    public List<Attachment> getWindowsScreenshots() {
        LOGGER.debug("getWindowsScreenshots()");

        // if close() already has been called, no screenshots available
        if (driver == null) {
            return Collections.emptyList();
        }

        Base64 base64 = new Base64();

        Set<String> windowHandles = getWindowHandles();
        String activeHandle = getWindowHandle();

        List<Attachment> result = new ArrayList<Attachment>();

        int index = 0;
        for (String handle : windowHandles) {
            driver.switchTo().window(handle);
            try {
                String data = captureActiveWindowScreenshotToString();
                byte[] decodedData = base64.decode(data);
                String title = driver.getTitle();

                result.add(new BinaryAttachment("Screenshot-" + (title == null ? "" + (++index) : title),
                        decodedData, configuration.getScreenshotAttachmentExtension()));
            } catch (UnsupportedOperationException e) {
                // OK, no screenshot available
                LOGGER.warn("Web driver is not able to take screenshots; no screenshots available");
            }
        }

        driver.switchTo().window(activeHandle);

        return result;
    }

    private String captureScreenshotToString() {
        // use Selenium1 interface to capture full screen
        String url = seleniumUrl.toString();
        Pattern p = Pattern.compile("(http(s)?://.+)/wd/hub(/?)");
        Matcher matcher = p.matcher(url);
        if (matcher.matches()) {
            String screenshotUrl = matcher.group(1);
            screenshotUrl += (screenshotUrl.endsWith("/") ? "" : "/")
                    + "selenium-server/driver/?cmd=captureScreenshotToString";
            InputStream in = null;
            try {
                in = new URL(screenshotUrl).openStream();
                // read away "OK,"
                if (in.read(new byte[3]) < 3) {
                    throw new EOFException();
                }
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                IOUtils.copy(in, baos);
                return new String(baos.toByteArray(), "UTF-8");
            } catch (IOException e) {
                // OK, fallthrough to Selenium 2 method
            } finally {
                IOUtils.closeQuietly(in);
            }
        }

        WebDriver screenshotDriver;
        if (RemoteWebDriver.class.isAssignableFrom(driver.getClass())) {
            screenshotDriver = new Augmenter().augment(driver);
        } else {
            screenshotDriver = driver;
        }
        if (screenshotDriver instanceof TakesScreenshot) {
            TakesScreenshot tsDriver = (TakesScreenshot) screenshotDriver;
            return tsDriver.getScreenshotAs(OutputType.BASE64);
        } else {
            throw new UnsupportedOperationException(driver.getClass() + " does not implement TakeScreenshot");
        }
    }

    private String captureActiveWindowScreenshotToString() {
        WebDriver screenshotDriver;
        if (RemoteWebDriver.class.isAssignableFrom(driver.getClass())) {
            screenshotDriver = new Augmenter().augment(driver);
        } else {
            screenshotDriver = driver;
        }
        if (screenshotDriver instanceof TakesScreenshot) {
            TakesScreenshot tsDriver = (TakesScreenshot) screenshotDriver;
            return tsDriver.getScreenshotAs(OutputType.BASE64);
        } else {
            throw new UnsupportedOperationException(driver.getClass() + " does not implement TakeScreenshot");
        }
    }

    private Attachment getScreenshotAttachment(String base64Data) {
        final String title = "Screenshot";
        final Base64 base64 = new Base64();
        final byte[] decodedData = base64.decode(base64Data);
        return new BinaryAttachment(title, decodedData, configuration.getScreenshotAttachmentExtension());
    }

    // element highlighting ----------------------------------------------------

    private void removeHighlight() {
        if (configuration.getHighlightCommands() && this.highlightedElement != null) {
            // unwrap element if wrapped, for direct access
            while (highlightedElement instanceof ElementWrapper) {
                highlightedElement = ((ElementWrapper) highlightedElement).getWrappedElement();
            }
            try {
                // first try via ID
                String id = highlightedElement.getAttribute("id");
                if (id != null) {
                    executeScript("document.getElementById('" + id + "').className = document.getElementById('" + id
                            + "').className.replace( /(?:^|\\s)selenium-highlight(?!\\S)/g , '' );");
                } else {
                    // fallback via Selenium arguments infrastructure
                    executeScript(
                            "arguments[0].className = arguments[0].className.replace( /(?:^|\\s)selenium-highlight(?!\\S)/g , '' );",
                            highlightedElement);
                }
            } catch (WebDriverException e) {
                LOGGER.trace("Highlight remove failed. ", e);
            }
        }
        highlightedElement = null;
    }

    private void checkHighlightCss() {
        // check if our hidden element is present
        try {
            findElementImmediately(new IdLocator("__aludra_selenium_hidden"));
        } catch (Exception e) {
            // add CSS and element
            executeScript(
                    "var css = document.createElement('style'); css.setAttribute('id', '__aludra_selenium_css'); css.setAttribute('type', 'text/css'); css.innerHTML = '.selenium-highlight { border: 3px solid red !important; }'; document.getElementsByTagName('head')[0].appendChild(css);");
            // add hidden element
            executeScript(
                    "var hidden = document.createElement('div'); hidden.setAttribute('id', '__aludra_selenium_hidden'); hidden.setAttribute('style', 'display:none;'); document.getElementsByTagName('body')[0].appendChild(hidden);");
        }
    }

    public void highlight(GUIElementLocator locator) {
        if (configuration.getHighlightCommands()) {
            LOGGER.debug("highlight()");
            try {
                removeHighlight();

                // ensure that current document has highlight CSS class
                checkHighlightCss();

                WebElement elementToHighlight = findElementImmediately(locator);
                executeScript("arguments[0].className +=' selenium-highlight'", elementToHighlight);
                this.highlightedElement = elementToHighlight;
            } catch (WebDriverException e) {
                // It does not matter if highlighting works or not, why a
                // possibly thrown exception must be caught to avoid test
                // execution termination.
                LOGGER.trace("Highlighting failed. ", e);
            }
        }
    }

    // script support ----------------------------------------------------------

    private Object executeScript(String script, Object... arguments) {
        LOGGER.debug("executeScript({})", script);
        // first unwrap all possibly wrapped arguments of type WebElement, or JavaScript/JSON will fail
        for (int i = 0; i < arguments.length; i++) {
            if (arguments[i] instanceof WebElement) {
                arguments[i] = LocatorSupport.unwrap((WebElement) arguments[i]);
            }
        }
        // then execute the script
        return ((JavascriptExecutor) driver).executeScript(script, arguments);
    }

    // element lookup and access -----------------------------------------------

    private WebElement findElementImmediately(GUIElementLocator locator) {
        return locatorSupport.findElementImmediately(locator);
    }

    private String getText(WebElement element) {
        LOGGER.debug("getText(WebElement)");
        return element.getText();
    }

    private void sendKeys(WebElement element, CharSequence... keys) {
        LOGGER.debug("sendKeys({}, WebElement)", keys);
        element.sendKeys(keys);
    }

    private String getValue(WebElement element) {
        LOGGER.debug("getValue(WebElement)");
        return element.getAttribute("value");
    }

    private void setValue(WebElement element, String value) {
        LOGGER.debug("setValue(WebElement, {})", value);
        sendKeys(element, Keys.END);
        String text;
        int tryCounter = 3;
        while (tryCounter > 0 && !DataMarkerCheck.isNull(text = getValue(element))) {
            int length = text.length();
            String[] arr = new String[length];
            for (int i = 0; i < length; i++) {
                arr[i] = "\b";
            }
            sendKeys(element, arr);
            tryCounter--;
        }
        if (tryCounter == 0) {
            throw new AutomationException("Could not clear input field. Maybe covered by other component?");
        }
        sendKeys(element, value);

        try {
            sendKeys(element, Keys.TAB);
        } catch (StaleElementReferenceException e) {
            // ignore; key could have caused page change
            LOGGER.debug("Could not fire change event for element because element is now stale.");
        } catch (WebDriverException e) {
            // of course, PhantomJS does NOT throw a StaleElementReferenceException, but some evil error...
            if (e.getMessage() != null && e.getMessage().matches("(?s).*'?undefined'? is not a .*")) {
                LOGGER.debug("Could not fire change event for element because element is now stale.");
            } else {
                throw e;
            }
        }
    }

    private String getSelectedLabel(WebElement element) {
        LOGGER.debug("getSelectedLabel(WebElement)");
        Select select = new Select(element);
        return select.getFirstSelectedOption().getText();
    }

    private void select(final OptionLocator optionLocator, WebElement element) {
        LOGGER.debug("select({}, WebElement)", optionLocator);
        Select select = new Select(element);
        try {
            if (optionLocator instanceof org.aludratest.service.locator.option.LabelLocator) {
                select.selectByVisibleText(
                        ((org.aludratest.service.locator.option.LabelLocator) optionLocator).getLabel());
            } else if (optionLocator instanceof IndexLocator) {
                select.selectByIndex(((IndexLocator) optionLocator).getIndex());
            } else {
                throw ServiceUtil.newUnsupportedLocatorException(optionLocator);
            }
        } catch (NoSuchElementException e) {
            throw new AutomationException("Selection Item not found");
        }
    }

    private void click(WebElement element) {
        LOGGER.debug("click(WebElement)");
        try {
            element.click();
        } catch (Exception e) {
            handleSeleniumException(e);
        }
    }

    private void doubleClick(WebElement element) {
        LOGGER.debug("doubleClick(WebElement)");
        element = LocatorSupport.unwrap(element);
        new Actions(driver).doubleClick(element).build().perform();
    }

    private void hover(WebElement element) {
        LOGGER.debug("hover(WebElement)");
        try {
            element = LocatorSupport.unwrap(element);
            Actions actions = new Actions(driver);
            actions.moveToElement(element).build().perform();
        } catch (Exception e) {
            handleSeleniumException(e);
        }
    }

    // special handling of some Selenium exceptions ----------------------------

    private void handleSeleniumException(Throwable e) {
        Throwable origException = e;
        String message = e.getMessage();

        // check if there is a WebDriverException
        WebDriverException wde = null;
        while (e != null) {
            if (e instanceof WebDriverException) {
                wde = (WebDriverException) e;
                break;
            }
            e = e.getCause();
        }

        if (wde != null) {
            if (message == null) {
                message = wde.getMessage();
            }
            // "not clickable" exception
            Pattern p = Pattern.compile("(unknown error: )?(.* not clickable .*)");
            Matcher m;
            if (message != null && (m = p.matcher(message)).find() && m.start() == 0) {
                throw new AutomationException(m.group(2), wde);
            }

            // NoSuchElementException
            if (wde instanceof NoSuchElementException) {
                throw new AutomationException("Element not found", wde);
            }

            // check for a time out
            if (wde instanceof TimeoutException) {
                throw new PerformanceFailure(wde.getMessage(), wde);
            }

            throw wde;
        }

        // otherwise, throw a technical exception
        throw new TechnicalException("Unknown exception when clicking element", origException);
    }

}