org.safs.selenium.webdriver.lib.WDLibrary.java Source code

Java tutorial

Introduction

Here is the source code for org.safs.selenium.webdriver.lib.WDLibrary.java

Source

/**
 * Copyright (C) SAS Institute, All rights reserved.
 * General Public License: http://www.opensource.org/licenses/gpl-license.php
 **/
package org.safs.selenium.webdriver.lib;

/**
*
* History:<br>
*
*  <br>   NOV 19, 2013    (CANAGL) Initial release.
*  <br>   DEC 18, 2013    (SBJLWA) Add codes to support ComboBox (HTML tag &lt;select&gt;).
*  <br>   DEC 26, 2013    (SBJLWA) Move ComboBox class out; add methods useBrowser() and stopBrowser().
*                                  Modify startBrowser() to permit passing more browser parameters, such as proxy settings.
*  <br>   JAN 16, 2014    (DHARMESH) Updated reconnection browser support.
*  <br>   FEB 02, 2014      (DHARMESH) Add Resize and Maximize WebBrowser window KW.
*  <br>   MAR 05, 2014    (SBJLWA) Add some methods related to mouse-click (based on webdriver API).
*  <br>   MAR 27, 2014    (SBJLWA) Get element's screen location and use SAFS-Robot to do click-related keywords firstly.
*  <br>   APR 15, 2014    (SBJLWA) Listen to the 'mousedown' event for detecting if the click/double has happened.
*  <br>   APR 15, 2014    (DHARMESH) Add HighLight keyword.
*  <br>   AUG 29, 2014    (DHARMESH) Add selenium grid host and port support.
*  <br>   DEC 02, 2014    (CANAGL) Fix SeBuilder Script imports to use UTF-8 Character Encoding.
*  <br>   DEC 09, 2014    (SBJLWA) Modify isVisible(): catch the un-expected exception.
*  <br>   JAN 06, 2015    (SBJLWA) Add method leftDrag().
*  <br>   JAN 15, 2015    (SBJLWA) Add methods xxxDrag().
*  <br>   JAN 20, 2015    (DHARMESH) Add mobile support.
*  <br>   APR 16, 2015    (CANAGL) Try to fix Selenium Keyboard Actions in inputKeysSAFS2Selenium.
*  <br>   APR 29, 2015    (SBJLWA) Modify inputKeys/inputChars: if webelement cannot be focused, just log a warning message.
*                                  instead of throwing an Exception.
*  <br>   MAY 29, 2015    (CANAGL) Add support for setDelayBetweenKeystrokes
*  <br>   JUN 05, 2015    (SBJLWA) Add checkBeforeOperation(): check if element is stale or invisible before clicking.
*                                  Add isDisplayed(): not like isVisible(), it will not check the value of attribute 'visibility'.
*                                  Modify isVisible(), isStale(): set implicitWait directly, don't wait.
*  <br>   JUN 14, 2015    (SBJLWA) Modify focus(): if the component is "EditBox", use Robot Click to set focus.
*  <br>   JUN 15, 2015    (CANAGL) Add isPointInBounds to account for points that might be on the width & height edge.
*  <br>   JUL 24, 2015    (SBJLWA) Create class WD_XMLHttpRequest and its static instance AJAX.
*  <br>   JUL 25, 2015    (SBJLWA) Modify windowSetFocus(): remove the unnecessary parameter element.
*  <br>     AUG 08, 2015    (Dharmesh) Added delayWaitReady for WaitOnClick.
*  <br>   SEP 07, 2015    (SBJLWA) Add method getElementOffsetScreenLocation().
*  <br>   OCT 12, 2015    (SBJLWA) Modify method getProperty(): get property by native SAP method.
*  <br>   OCT 30, 2015    (SBJLWA) Move method isVisible(), isDisplayed and isStale() to SearchObject class.
*  <br>   NOV 20, 2015    (SBJLWA) Add a unit test for "WDLibrary.AJAX.getURL".
*  <br>   NOV 26, 2015    (SBJLWA) Move some content from getScreenLocation() to getLocation().
*  <br>   DEC 02, 2015    (SBJLWA) Modify getLocation(): modify to get more accurate location.
*  <br>   DEC 03, 2015    (SBJLWA) Move "code of fixing browser client area offset problem" from getLocation() to getScreenLocation().
*  <br>   DEC 10, 2015    (SBJLWA) Add methods to handle clipboard on local machine or on RMI server machine.
*  <br>   DEC 24, 2015    (SBJLWA) Add methods to get browser's name, version, and selenium-server's version etc.
*                                  Add method checkKnownIssue().
*  <br>   FEB 05, 2016    (SBJLWA) Add method killChromeDriver().
*  <br>   FEB 26, 2016    (SBJLWA) Modify cilck(), doubleClick(): if the offset is out of element's boundary, disable the click listener.
*  <br>   FEB 29, 2016    (SBJLWA) Modify checkOffset(): if the offset is out of element's boundary, use the whole document as click event receiver.
*  <br>   MAR 02, 2016    (SBJLWA) Add clickUnverified(), closeAlert().
*                                  Add class RBT: To encapsulate the local Robot and Robot RMI agent.
*                                  Modify click() and doubleClick(): use RBT to do the click action.
*  <br>   MAR 14, 2016    (SBJLWA) Add isAlertPresent(), waitAlert().
*  <br>   MAR 29, 2016    (SBJLWA) Modify click() and doubleClick(): detect "Alert" after clicking.
*  <br>   APR 19, 2016    (SBJLWA) Modify click() doubleClick() etc.: Handle the optional parameter 'autoscroll'.
*/
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.rmi.ServerException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.imageio.ImageIO;

import org.apache.commons.io.FileUtils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.interactions.internal.Coordinates;
import org.openqa.selenium.internal.Locatable;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.safari.SafariDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.safs.IndependantLog;
import org.safs.Processor;
import org.safs.SAFSException;
import org.safs.StringUtils;
import org.safs.image.ImageUtils;
import org.safs.model.commands.DDDriverCommands;
import org.safs.natives.NativeWrapper;
import org.safs.net.IHttpRequest.Key;
import org.safs.net.XMLHttpRequest;
import org.safs.robot.Robot;
import org.safs.selenium.util.DocumentClickCapture;
import org.safs.selenium.util.JavaScriptFunctions;
import org.safs.selenium.util.MouseEvent;
import org.safs.selenium.webdriver.CFComponent;
import org.safs.selenium.webdriver.CFEditBox;
import org.safs.selenium.webdriver.SeleniumPlus.WDTimeOut;
import org.safs.selenium.webdriver.WebDriverGUIUtilities;
import org.safs.selenium.webdriver.lib.interpreter.WDScriptFactory;
import org.safs.selenium.webdriver.lib.interpreter.WDTestRunFactory;
import org.safs.text.FileUtilities;
import org.safs.text.INIFileReader;
import org.safs.tools.CaseInsensitiveFile;
import org.safs.tools.GenericProcessMonitor;
import org.safs.tools.GenericProcessMonitor.ProcessInfo;
import org.safs.tools.GenericProcessMonitor.WQLSearchCondition;
import org.safs.tools.input.CreateUnicodeMap;
import org.safs.tools.input.InputKeysParser;
import org.safs.tools.input.RobotKeyEvent;
import org.safs.tools.stringutils.StringUtilities;

import com.sebuilder.interpreter.Script;
import com.sebuilder.interpreter.factory.StepTypeFactory;
import com.sebuilder.interpreter.webdriverfactory.WebDriverFactory;

/**
 *  <br>   NOV 19, 2013    (CANAGL) Initial release.
 */
public class WDLibrary extends SearchObject {

    /** {@link InputEvent#BUTTON1_MASK} */
    public static final int MOUSE_BUTTON_LEFT = InputEvent.BUTTON1_MASK;
    /** {@link InputEvent#BUTTON2_MASK} */
    public static final int MOUSE_BUTTON_MIDDLE = InputEvent.BUTTON2_MASK;
    /** {@link InputEvent#BUTTON3_MASK} */
    public static final int MOUSE_BUTTON_RIGHT = InputEvent.BUTTON3_MASK;

    public static boolean isLeftMouseButton(int buttonNumber) {
        return (buttonNumber == MOUSE_BUTTON_LEFT);
    }

    public static boolean isMiddleMouseButton(int buttonNumber) {
        return (buttonNumber == MOUSE_BUTTON_MIDDLE);
    }

    public static boolean isRightMouseButton(int buttonNumber) {
        return (buttonNumber == MOUSE_BUTTON_RIGHT);
    }

    protected static boolean debug = false;

    /** 2 seconds to wait for a click action finished on page. */
    public static final int DEFAULT_TIMEOUT_WAIT_CLICK = 2;//seconds

    /**
     * The timeout in seconds to wait for the alert's presence.<br>
     * The default value is 2 seconds.<br>
     */
    public static final int DEFAULT_TIMEOUT_WAIT_ALERT = 2;//seconds

    /** 0, means no waiting. */
    public static final int TIMEOUT_NOWAIT = 0;
    /** -1, means wait for ever */
    public static final int TIMEOUT_WAIT_FOREVER = -1;

    /** 10 seconds to wait for a Robot click action finished on page. */
    public static final int DEFAULT_TIMEOUT_WAIT_ROBOT_CLICK = 10;//seconds

    /** time (in seconds) to wait for a click action finished on page. */
    public static int timeoutWaitClick = DEFAULT_TIMEOUT_WAIT_CLICK;//seconds

    /** time (in seconds) to wait for an Alert appear on page. */
    public static int timeoutWaitAlert = DEFAULT_TIMEOUT_WAIT_ALERT;//seconds

    /** time (in seconds) to check if an Alert is present on page before clicking.
     * The default value is set to {@link #TIMEOUT_NOWAIT}, means we check immediately
     * without waiting for presence of Alert. */
    public static int timeoutCheckAlertForClick = TIMEOUT_NOWAIT;//seconds

    /** time (in seconds) to wait for a Robot click action finished on page. */
    public static int timeoutWaitRobotClick = DEFAULT_TIMEOUT_WAIT_ROBOT_CLICK;//seconds

    static protected InputKeysParser keysparser = null;

    public static int getTimeoutCheckAlertForClick() {
        return timeoutCheckAlertForClick;
    }

    public static void setTimeoutCheckAlertForClick(int timeoutCheckAlertForClick) {
        WDLibrary.timeoutCheckAlertForClick = timeoutCheckAlertForClick;
    }

    /**
      * Default true.
      * Set to true if we want an Exception to be thrown when we perform a Click and attempt to verify through event
      * listeners that the Click actually happened.  The problem is, that so many Click verifications fail due to the
      * page and or WebElement going stale or being overwritten that Click verifications issue failures even though
      * the Click was successful.
      * <p>
      * Set to 'false' when you wish to avoid these types of failures.
      */
    public static boolean enableClickListenerFailures = true;

    static {
        if (keysparser == null) {
            try {
                InputStream stream = ClassLoader.getSystemResourceAsStream(
                        CreateUnicodeMap.DEFAULT_FILE + CreateUnicodeMap.DEFAULT_FILE_EXT);
                INIFileReader reader = new INIFileReader(stream, 0, false);
                keysparser = new InputKeysParser(reader);
            } catch (Exception x) {
                IndependantLog.debug("WDLibrary keysparser INI Reader or parser instantiation exception:", x);
            }

        }
    }

    /**
     * Check if the webelement is ready (not stale, visible) for any operation like click, double-click etc.<br>
     * If the webelement is stale, a SeleniumPlusException will be thrown out.<br>
     * If something unexpected happened, a SeleniumPlusException will be thrown out.<br>
     * If the webelement is not displayed (webdriver API), log a warning message or a SeleniumPlusException thrown out.<br>
     * @param clickable WebElement, the WebElement to check.
     * @param warnNotDisplayed boolean, if true, log a warning message when the element is not displayed; if false, throw a SeleniumPlusException.
     * @throws SeleniumPlusException if the WebElement is not ready (null, stale or invisible).
     */
    private static void checkBeforeOperation(WebElement clickable, boolean warnNotDisplayed)
            throws SeleniumPlusException {
        if (!isDisplayed(clickable)) {
            //As isDisplayed() will not reflect the real visibility of component
            //some visible object will be considered as not visible, which will not be processed if
            //an Exception is thrown out, so a warning is logged instead of exception.
            String msg = "weblement is not displayed.";
            if (warnNotDisplayed)
                IndependantLog.warn(StringUtils.debugmsg(false) + msg);
            else
                throw new SeleniumPlusException(msg);
        }
    }

    /**
     * Click(Mouse Left Button) - Click on WebElement or WebDriver object at the center
     * @param clickable WebElement, as WebElement obj
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void click(WebElement clickable, String... optional) throws SeleniumPlusException {
        checkBeforeOperation(clickable, true);
        try {
            boolean autoscroll = parseAutoScroll(optional);
            if (autoscroll) {
                try {
                    new Actions(WDLibrary.getWebDriver()).moveToElement(clickable).perform();
                } catch (Throwable t) {
                    IndependantLog.error("Ignoring Selenium Click 'moveToElement' action failure caused by "
                            + t.getClass().getName());
                }
            }
            clickable.click();
        } catch (Throwable th) {
            IndependantLog.error("Selenium Click action failed.  Trying Robot...", th);
            click(clickable, null, null, MOUSE_BUTTON_LEFT);
        }
    }

    /**
     * Click(Mouse Left Button) the WebElement at center with a special key pressed.
     * @param clickable    WebElement, the WebElement to click on
     * @param specialKey   Keys, the special key to presse during the click
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void click(WebElement clickable, Keys specialKey, String... optional)
            throws SeleniumPlusException {
        click(clickable, null, specialKey, MOUSE_BUTTON_LEFT, optional);
    }

    /**
     * Click(Mouse Left Button) the WebElement at a certain coordination.
     * @param clickable    WebElement, the WebElement to click on
     * @param offset      Point, the coordination relative to this WebElement to click at
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void click(WebElement clickable, Point offset, String... optional) throws SeleniumPlusException {
        click(clickable, offset, null, MOUSE_BUTTON_LEFT, optional);
    }

    /**
     * Click(Mouse Right Button) - Click on WebElement or WebDriver object at the center
     * @param clickable WebElement, as WebElement obj
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void rightclick(WebElement clickable, String... optional) throws SeleniumPlusException {
        click(clickable, null, null, MOUSE_BUTTON_RIGHT, optional);
    }

    /**
     * Click(Mouse Right Button) the WebElement at center with a special key pressed.
     * @param clickable    WebElement, the WebElement to click on
     * @param specialKey   Keys, the special key to presse during the click
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void rightclick(WebElement clickable, Keys specialKey, String... optional)
            throws SeleniumPlusException {
        click(clickable, null, specialKey, MOUSE_BUTTON_RIGHT, optional);
    }

    /**
     * Click(Mouse Right Button) the WebElement at a certain coordination.
     * @param clickable    WebElement, the WebElement to click on
     * @param offset      Point, the coordination relative to this WebElement to click at
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void rightclick(WebElement clickable, Point offset, String... optional)
            throws SeleniumPlusException {
        click(clickable, offset, null, MOUSE_BUTTON_RIGHT, optional);
    }

    /**
     * Check if the point is inside of the boundary of WebElement.<br>
     * @param element   WebElement,   The element to get boundary to check with. 
     * @param p         Point,   The point to check.
     * @return boolean, true if the point is inside of the boundary of WebElement
     */
    public static boolean inside(WebElement element, Point p) {
        if (p == null)
            return true;

        Dimension dimension = element.getSize();
        Rectangle rect = new Rectangle(0, 0, dimension.width, dimension.height);

        return rect.contains(p);
    }

    /**
     * Enlarge the listening area of DocumentClickCapture, if click offset is outside of the WebElement's boundary.<br>
     * @param clickable   WebElement, The element to click.
     * @param offset   Point, The offset relative to the WebElement to click at.
     * @param listener   DocumentClickCapture, The click listener to capture the click event.
     */
    private static void checkOffset(WebElement clickable, Point offset, DocumentClickCapture listener) {
        String debugmsg = StringUtils.debugmsg(false);

        if (clickable == null || offset == null || listener == null) {
            //We have nothing to check, or have no listener to disable
            return;
        }

        if (!inside(clickable, offset)) {
            IndependantLog.warn(debugmsg + "Enlarge the listening area of DocumentClickCapture, the click point "
                    + offset + " is outside of the WebElement " + clickable.getSize());
            listener.setEnlargeListeningArea(true);
        }
    }

    /**
     * 'true', the default value for scrolling the web-element into view automatically before performing click ation on it.
     */
    public static final boolean DEFAUT_AUTOSCROLL = true;

    /**
     * parse the optional parameter to get the value of 'autoscroll'.
     * @see #click(WebElement, String...)
     * @see #click(WebElement, Point, Keys, int, String...)
     * @see #doubleClick(WebElement, Point, Keys, int, String...)
     * @see #clickUnverified(WebElement, Point, String...)
     */
    private static boolean parseAutoScroll(String... optional) {
        boolean autoscroll = DEFAUT_AUTOSCROLL;

        if (optional != null && optional.length > 0 && optional[0] != null) {
            try {
                autoscroll = Boolean.parseBoolean(optional[0]);
            } catch (Exception e) {
                IndependantLog.warn(StringUtils.debugmsg(false) + " Ignoring invalid parameter 'autoscroll' "
                        + optional[0] + ", met " + StringUtils.debugmsg(e));
            }
        }

        return autoscroll;
    }

    /**
     * Click the WebElement at a certain coordination with a special key pressed.<br>
     * Firstly it will try to get webelement's location and use Robot to click. At the same<br>
     * time, it will listen to a 'javascript mouse down' event to find out if the click really<br>
     * happened; If not, it will try to use Selenium's API to do the work.<br>
     * If the click point is outside of the boundary of the WebElement, which means we are going<br>
     * to click on the sibling component. At this situation, our click-listener will never receive<br>
     * the click event, we will turn off the click-listener.<br>
     *
     * @param clickable    WebElement, the WebElement to click on
     * @param offset      Point, the coordination relative to this WebElement to click at.<br>
     *                         if the offset is null, then click at the center.
     * @param specialKey   Keys, the special key to press during the click
     * @param mouseButtonNumber int, the mouse-button-number representing right, middle, or left button.
     *                          it can be {@link #MOUSE_BUTTON_LEFT} or {@link #MOUSE_BUTTON_RIGHT}.<br>
     *                          {@link #MOUSE_BUTTON_MIDDLE} NOT supported yet.
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void click(WebElement clickable, Point offset, Keys specialKey, int mouseButtonNumber,
            String... optional) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "click");

        checkBeforeOperation(clickable, true);
        WebDriver wd = WDLibrary.getWebDriver();
        RemoteDriver rd = (wd instanceof RemoteDriver) ? (RemoteDriver) wd : null;
        boolean autoscroll = parseAutoScroll(optional);

        if (autoscroll) {
            try {
                new Actions(wd).moveToElement(clickable).perform();
            } catch (Throwable t) {
                IndependantLog
                        .error(debugmsg + "Ignoring Selenium Robot Click 'moveToElement' action failure caused by "
                                + t.getClass().getName());
            }
        }

        MouseEvent event = null;
        DocumentClickCapture listener = new DocumentClickCapture(true, clickable);
        checkOffset(clickable, offset, listener);

        try {
            //2. Perform the click action by Robot
            Point location = getScreenLocation(clickable);
            if (offset != null)
                location.translate(offset.x, offset.y);
            else {
                Dimension d = clickable.getSize();
                location.translate(d.width / 2, d.height / 2);
            }
            listener.addListeners(false);
            RBT.click(rd, location, specialKey, mouseButtonNumber, 1);
            listener.startListening();

            //3. Wait for the 'click' event, check if the 'mousedown' event really happened.
            // CANAGL -- FIREFOX PROBLEM: A link that takes you to a new page (like the Google SignIn link) will
            // trigger the default action and apparently will NOT allow us to detect the Click occurred.
            // So this WILL generate a waitForClick InterruptedException (Timeout)
            event = listener.waitForClick(timeoutWaitRobotClick);
            if (event == null) {
                IndependantLog.resumeLogging();
                IndependantLog
                        .warn(debugmsg + " Robot may fail to perform click. Click screen location is " + location);
                throw new SeleniumPlusException("The Robot click action didn't happen.");
            } else {
                IndependantLog.resumeLogging();
                IndependantLog.debug(debugmsg + "Robot click successful.");
            }
        } catch (Throwable thr) {
            IndependantLog.resumeLogging();
            IndependantLog.warn(debugmsg + "Met Exception " + StringUtils.debugmsg(thr));

            // let the failed listeners exit.
            try {
                Thread.sleep(DocumentClickCapture.LISTENER_LOOP_DELAY + DocumentClickCapture.delayWaitReady);
            } catch (Exception x) {
                IndependantLog.debug(debugmsg + StringUtils.debugmsg(x));
            }

            try {
                //2. Perform the click action by Selenium
                IndependantLog.debug(debugmsg + " Try selenium API to click.");
                //Create a combined actions according to the parameters
                Actions actions = new Actions(getWebDriver());

                if (autoscroll) {
                    if (offset != null)
                        actions.moveToElement(clickable, offset.x, offset.y);
                    else
                        actions.moveToElement(clickable);
                }

                if (specialKey != null)
                    actions.keyDown(specialKey);
                if (isRightMouseButton(mouseButtonNumber))
                    actions.contextClick();
                else if (isLeftMouseButton(mouseButtonNumber))
                    actions.click();
                else if (isMiddleMouseButton(mouseButtonNumber)) {
                    throw new SeleniumPlusException("Click 'mouse middle button' has not been supported yet.");
                } else {
                    throw new SeleniumPlusException(
                            "Mouse button number '" + mouseButtonNumber + "' cannot be recognized.");
                }
                if (specialKey != null)
                    actions.keyUp(specialKey);

                IndependantLog.debug(
                        debugmsg + "click with key '" + specialKey + "', mousebutton='" + mouseButtonNumber + "'");

                //Perform the actions
                listener.addListeners(false);
                try {
                    //if the Robot click worked, but was not detected. If we clicked a link, original page has
                    //disappeared, so the link doesn't exist neither, the WebElement is stale. WebDriver will
                    //not throw StaleElementReferenceException until the 'implicit timeout' is reached.
                    //But we don't want to waste that time, so just set 'implicit timeout' to 0 and don't wait.
                    WDTimeOut.setImplicitlyWait(0, TimeUnit.SECONDS);
                    actions.build().perform();
                    listener.startListening();

                    // Dharmesh: Not report waitForClick failure due to listener event not capture 
                    // if click coordination out of component size or background. 
                    // It is hard to find sibling component.
                    try {
                        event = listener.waitForClick(timeoutWaitClick);
                    } catch (Throwable the) {
                        IndependantLog.debug(debugmsg + " waitForClick failed but not reported");
                    }
                    ;

                    /*if(event != null)
                       IndependantLog.debug(debugmsg+"click has been performed.");
                    else{
                       throw new SeleniumPlusException("Selenium Action.click failed to return the MouseEvent.");
                    }*/

                } catch (StaleElementReferenceException x) {
                    listener.stopListening(); // chrome is NOT stopping!
                    // the click probably was successful because the elements have changed!
                    IndependantLog.debug(debugmsg
                            + "StaleElementException (not found) suggests the click has been performed successfully.");
                } finally {
                    IndependantLog.debug(
                            debugmsg + "selenium API click finally stopping listener and resetting timeouts.");
                    listener.stopListening(); // chrome is NOT stopping!
                    WDTimeOut.resetImplicitlyWait(Processor.getSecsWaitForComponent(), TimeUnit.SECONDS);
                }
            } catch (Throwable th) {
                listener.stopListening(); // chrome is NOT stopping!
                if (enableClickListenerFailures) {
                    IndependantLog.error(debugmsg, th);
                    throw new SeleniumPlusException("click action failed: " + StringUtils.debugmsg(th));
                } else {
                    IndependantLog.debug(debugmsg + "ignoring selenium API click failure caused by "
                            + th.getClass().getName() + ", " + th.getMessage());
                }
            }
        } finally {
            IndependantLog.debug(debugmsg + "FINALLY stopping any ongoing listener, if any.");
            listener.stopListening(); // chrome is NOT stopping!
        }
    }

    /**
     * Double-Click the WebElement at a certain coordination with a special key pressed.<br>
     * Firstly it will try to get webelement's location and use Robot to double click. At the same<br>
     * time, it will listen to a 'javascript mouse down' event to find out if the double click really<br>
     * happened; If not, it will try to use Selenium's API to do the work.<br>
     * If the click point is outside of the boundary of the WebElement, which means we are going<br>
     * to click on the sibling component. At this situation, our click-listener will never receive<br>
     * the click event, we will turn off the click-listener.<br>
     *
     * @param clickable    WebElement, the WebElement to click on
     * @param offset      Point, the coordination relative to this WebElement to click at.<br>
     *                         if the offset is null, then click at the center.
     * @param specialKey   Keys, the special key to press during the click
     * @param mouseButtonNumber int, the mouse-button-number representing right, middle, or left button.
     *                          it can be {@link #MOUSE_BUTTON_LEFT}<br>
     *                          {@link #MOUSE_BUTTON_MIDDLE} and {@link #MOUSE_BUTTON_RIGHT} NOT supported yet.
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void doubleClick(WebElement clickable, Point offset, Keys specialKey, int mouseButtonNumber,
            String... optional) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "doubleClick");

        checkBeforeOperation(clickable, true);

        MouseEvent event = null;
        DocumentClickCapture listener = new DocumentClickCapture(true, clickable);
        checkOffset(clickable, offset, listener);
        boolean autoscroll = parseAutoScroll(optional);

        try {
            //2. Perform the click action by Robot
            Point location = getScreenLocation(clickable);
            if (offset != null)
                location.translate(offset.x, offset.y);
            else
                location.translate(clickable.getSize().width / 2, clickable.getSize().height / 2);
            listener.addListeners(false);
            RBT.click(location, specialKey, mouseButtonNumber, 2);
            listener.startListening();

            //3. Wait for the 'click' event, check if the 'mousedown' event really happened.
            event = listener.waitForClick(timeoutWaitClick);
            if (event == null) {
                IndependantLog.warn(
                        debugmsg + " Robot may fail to perform doubleclick. Click screen location is " + location);
                throw new SeleniumPlusException("The doubleclick action didn't happen.");
            } else {
                IndependantLog.debug(debugmsg + "doubleclick has been peformed.");
            }
        } catch (Throwable thr) {
            IndependantLog.warn(debugmsg + "Met Exception " + StringUtils.debugmsg(thr));
            try {
                //2. Perform the click action by Selenium
                IndependantLog.debug(debugmsg + " Try selenium API to doubleclick.");
                //Create a combined actions according to the parameters
                Actions actions = new Actions(WDLibrary.getWebDriver());

                if (autoscroll) {
                    if (offset != null)
                        actions.moveToElement(clickable, offset.x, offset.y);
                    else
                        actions.moveToElement(clickable);
                }

                if (specialKey != null)
                    actions.keyDown(specialKey);
                if (isLeftMouseButton(mouseButtonNumber))
                    actions.doubleClick();
                else if (isMiddleMouseButton(mouseButtonNumber) || isRightMouseButton(mouseButtonNumber)) {
                    throw new SeleniumPlusException(
                            "Double click 'mouse middle/right button' has not been supported yet.");
                } else
                    throw new SeleniumPlusException(
                            "Mouse button number '" + mouseButtonNumber + "' cannot be recognized.");

                if (specialKey != null)
                    actions.keyUp(specialKey);

                IndependantLog.debug(debugmsg + "doubleclick with key '" + specialKey + "', mousebutton='"
                        + mouseButtonNumber + "'");

                //Perform the actions
                listener.addListeners(false);
                try {
                    //unfortunately, if the Robot click worked, but was not detected, we have to wait the full
                    //WebDriver implied timeout period for the perform() failure to occur.
                    actions.build().perform();
                    listener.startListening();
                    event = listener.waitForClick(timeoutWaitClick);
                    if (event != null)
                        IndependantLog.debug(debugmsg + "doubleclick has been peformed.");
                    else {
                        throw new SeleniumPlusException(
                                "Selenium Action.doubleclick failed to detect the MouseEvent.");
                    }
                } catch (StaleElementReferenceException x) {
                    // the click probably was successful because the elements have changed!
                    IndependantLog.debug(debugmsg
                            + "StaleElementException (not found) suggests the click has been performed successfully.");
                }
            } catch (Throwable th) {
                IndependantLog.error(debugmsg, th);
                throw new SeleniumPlusException("doubleclick action failed: " + StringUtils.debugmsg(th));
            }
        } finally {
            listener.stopListening();
        }
    }

    /**
     * Double-Click(Mouse Left Button) the WebElement at the center.
     * @param clickable    WebElement, the WebElement to click on
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void doubleClick(WebElement clickable, String... optional) throws SeleniumPlusException {
        doubleClick(clickable, null, null, MOUSE_BUTTON_LEFT, optional);
    }

    /**
     * Double-Click(Mouse Left Button) the WebElement at the center with a special key pressed.
     * @param clickable    WebElement, the WebElement to click on
     * @param specialKey   Keys, the special key to presse during the click
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void doubleClick(WebElement clickable, Keys specialKey, String... optional)
            throws SeleniumPlusException {
        doubleClick(clickable, null, specialKey, MOUSE_BUTTON_LEFT, optional);
    }

    /**
     * Double-Click(Mouse Left Button) the WebElement at a certain coordination.
     * @param clickable    WebElement, the WebElement to click on
     * @param offset      Point, the coordination relative to this WebElement to click at
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @throws SeleniumPlusException
     */
    public static void doubleClick(WebElement clickable, Point offset, String... optional)
            throws SeleniumPlusException {
        doubleClick(clickable, offset, null, MOUSE_BUTTON_LEFT, optional);
    }

    /**
     * Get the web-element's location relative to the browser. If the web-element is inside frames, 
     * the frame's location will also be added.<br>
     * <b>Note: </b>The location got by this method might be slightly shifted. To get more accurate
     *             location, please call {@link #getLocation(WebElement, boolean)} with 2th parameter
     *             given as false.<br>
     * 
     * @param webelement WebElement, the element to get location
     * @return Point, the element's location inside a browser
     * @throws SeleniumPlusException
     * @see {@link #getLocation(WebElement, boolean)}
     */
    public static Point getLocation(WebElement webelement) throws SeleniumPlusException {
        return getLocation(webelement, true);
    }

    /**
     * Get the web-element's location relative to the browser client area. If the web-element is inside frames, 
     * the frame's location will also be added.<br>
     * <b>Note: </b>If the 2th parameter is given false, you might get a more accurate location.<br>
     * @param webelement WebElement, the element to get location
     * @param useOnPageFirstly boolean, There are 2 ways to get element's location relative to the page:
     *                                  one is {@link Coordinates#onPage()}, the other is {@link WebElement#getLocation()}.
     *                                  it seems that the 2th method ({@link WebElement#getLocation()}) is more accurate.
     *                                  But historically, we called {@link Coordinates#onPage()} in first place.<br>
     *                                  If this parameter is true, {@link Coordinates#onPage()} will be used firstly as before.<br>
     *                                  Otherwise, {@link WebElement#getLocation()} will be used directly<br>
     * 
     * @return Point, the element's location relative to the browser client area
     * @throws SeleniumPlusException
     */
    public static Point getLocation(WebElement webelement, boolean useOnPageFirstly) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            //1. Get the element's location relative to the frame
            org.openqa.selenium.Point p = null;
            if (useOnPageFirstly) {
                try {
                    Coordinates c = ((RemoteWebElement) webelement).getCoordinates();
                    p = c.onPage();
                } catch (UnsupportedOperationException x) {
                    IndependantLog.debug(debugmsg + "Selenium reports coordinates.onPage() is NOT yet supported.");
                } catch (Throwable t) {
                    IndependantLog.debug(debugmsg + "ignoring " + StringUtils.debugmsg(t));
                }
            }
            if (p == null)
                p = webelement.getLocation();
            IndependantLog.debug(debugmsg + "Selenium reports the WebElement 'CLIENT AREA' location as (" + p.x
                    + "," + p.y + ")");

            //2. Add the frame's location (relative to the the browser client area)
            if (lastFrame != null) {
                p.x += lastFrame.getLocation().x;
                p.y += lastFrame.getLocation().y;
                //IndependantLog.debug(debugmsg+"added lastFrame offsets, new tentative 'CLIENT AREA' location ("+p.x+","+p.y+")");
            }

            return new Point(p.x, p.y);
        } catch (Exception e) {
            IndependantLog.error(debugmsg, e);
            throw new SeleniumPlusException("getLocation failed: " + StringUtils.debugmsg(e));
        }
    }

    /**
     * Get the absolute coordination on the screen of the webelement object.<br>
     * Hope Selenium will give this function one day!<br>
     *
     * @param webelement WebElement, a selenium webelement object
     * @return Point, the absolute coordination on the screen of the webelement object.
     * @throws SeleniumPlusException
     */
    public static Point getScreenLocation(WebElement webelement) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "getScreenLocation");

        Point p = null;
        try {
            try {
                Coordinates c = ((RemoteWebElement) webelement).getCoordinates();
                org.openqa.selenium.Point screen = c.onScreen();
                IndependantLog.debug(debugmsg + "Selenium reports the WebElement SCREEN location as (" + screen.x
                        + "," + screen.y + ")");
            } catch (UnsupportedOperationException x) {
                IndependantLog.debug(debugmsg + "Selenium reports coordinates.onScreen() is NOT yet supported.");
            } catch (Throwable t) {
                IndependantLog.debug(debugmsg + "ignoring " + StringUtils.debugmsg(t));
            }

            //1. Get the element's location relative to the browser client area
            p = getLocation(webelement);

            //2. Add the browser client area's location (relative to the browser's window), which is different according to browser
            p.x += lastBrowserWindow.getClientX();
            p.y += lastBrowserWindow.getClientY();
            IndependantLog
                    .debug(debugmsg + "added lastBrowserWindow ClientXY offsets, new tentative PAGE location ("
                            + p.x + "," + p.y + ")");

            //2.1 Fix "client area LOCATION offset problem"
            if (lastFrame == null) {
                //LEIWANG: I think that "client area LOCATION offset problem" is not related to the lastFrame.
                //Even the lastFrame is not null, that problem might exist too and we should try to fix it.
                //TODO remove the condition "if(lastFrame==null)" in future
                if (lastBrowserWindow.getClientX() == 0 && lastBrowserWindow.getBorderWidth() == 0
                        && lastBrowserWindow.getPageXOffset() == 0
                        && lastBrowserWindow.getWidth() > lastBrowserWindow.getClientWidth()) {
                    int diff = Math.round(lastBrowserWindow.getWidth() - lastBrowserWindow.getClientWidth()) / 2;
                    IndependantLog.debug(debugmsg + "detecting potential client area LOCATION offset problem of "
                            + diff + " pixels");
                    if (diff < 12) {
                        p.x += diff;
                        p.y += diff;
                        IndependantLog.debug(debugmsg
                                + "added lastBrowserWindow suspected location offset error, new tentative PAGE location ("
                                + p.x + "," + p.y + ")");
                    }
                }
            }

            //3. Add the browser window's location (relative to the screen)
            p.x += lastBrowserWindow.getX();
            p.y += lastBrowserWindow.getY();
            //IndependantLog.debug(debugmsg+"added lastBrowserWindow XY offsets, new tentative SCREEN location ("+p.x+","+p.y+")");

            //4. minus scrollbar offset
            p.x -= lastBrowserWindow.getPageXOffset();
            p.y -= lastBrowserWindow.getPageYOffset();
            //IndependantLog.debug(debugmsg+"added lastBrowserWindow PageXY offsets, new tentative SCREEN location ("+p.x+","+p.y+")");

            if (debug) {
                Robot.getRobot().mouseMove(p.x, p.y);
                Thread.sleep(500);
            }
            IndependantLog
                    .debug(debugmsg + "The Left-Upper corner's SCREEN coordinate is (" + p.x + "," + p.y + ")");

            return p;
        } catch (Throwable th) {
            IndependantLog.error(debugmsg, th);
            throw new SeleniumPlusException("getScreenLocation failed: " + StringUtils.debugmsg(th));
        }
    }

    /**
     * Convert a SAFS RobotKeyEvent to a Selenium WebDriver Keys Enum
     * @param event RobotKeyEvent
     * @return Keys enum for (primarily) non-printable (control) characters, or null.
     */
    public static Keys convertToKeys(RobotKeyEvent event) {
        try {
            return convertToKeys(event.get_keycode());
        } catch (Exception e) {
            IndependantLog.error(StringUtils.debugmsg(false) + " Met Exception " + StringUtils.debugmsg(e));
            return null;
        }
    }

    /**
     * Convert a Java KEYCODE to a Selenium WebDriver Keys Enum
     * @param keycode int, a java keycode
     * @return Keys enum for (primarily) non-printable (control) characters, or null.
     */
    public static Keys convertToKeys(int keycode) {
        Keys key = null;
        switch (keycode) {
        case java.awt.event.KeyEvent.VK_ADD:
            key = Keys.ADD;
            break;
        case java.awt.event.KeyEvent.VK_ALT:
            key = Keys.ALT;
            break;
        case java.awt.event.KeyEvent.VK_KP_DOWN:
            key = Keys.ARROW_DOWN;
            break;
        case java.awt.event.KeyEvent.VK_KP_LEFT:
            key = Keys.ARROW_LEFT;
            break;
        case java.awt.event.KeyEvent.VK_KP_RIGHT:
            key = Keys.ARROW_RIGHT;
            break;
        case java.awt.event.KeyEvent.VK_KP_UP:
            key = Keys.ARROW_UP;
            break;
        case java.awt.event.KeyEvent.VK_BACK_SPACE:
            key = Keys.BACK_SPACE;
            break;
        case java.awt.event.KeyEvent.VK_CANCEL:
            key = Keys.CANCEL;
            break;
        case java.awt.event.KeyEvent.VK_CLEAR:
            key = Keys.CLEAR;
            break;
        case java.awt.event.KeyEvent.VK_WINDOWS:
            key = Keys.COMMAND;
            break;
        case java.awt.event.KeyEvent.VK_CONTROL:
            key = Keys.CONTROL;
            break;
        case java.awt.event.KeyEvent.VK_DECIMAL:
            key = Keys.DECIMAL;
            break;
        case java.awt.event.KeyEvent.VK_DELETE:
            key = Keys.DELETE;
            break;
        case java.awt.event.KeyEvent.VK_DIVIDE:
            key = Keys.DIVIDE;
            break;
        case java.awt.event.KeyEvent.VK_DOWN:
            key = Keys.DOWN;
            break;
        case java.awt.event.KeyEvent.VK_END:
            key = Keys.END;
            break;
        case java.awt.event.KeyEvent.VK_ENTER:
            key = Keys.ENTER;
            break;
        case java.awt.event.KeyEvent.VK_EQUALS:
            key = Keys.EQUALS;
            break;
        case java.awt.event.KeyEvent.VK_ESCAPE:
            key = Keys.ESCAPE;
            break;
        case java.awt.event.KeyEvent.VK_F1:
            key = Keys.F1;
            break;
        case java.awt.event.KeyEvent.VK_F2:
            key = Keys.F2;
            break;
        case java.awt.event.KeyEvent.VK_F3:
            key = Keys.F3;
            break;
        case java.awt.event.KeyEvent.VK_F4:
            key = Keys.F4;
            break;
        case java.awt.event.KeyEvent.VK_F5:
            key = Keys.F5;
            break;
        case java.awt.event.KeyEvent.VK_F6:
            key = Keys.F6;
            break;
        case java.awt.event.KeyEvent.VK_F7:
            key = Keys.F7;
            break;
        case java.awt.event.KeyEvent.VK_F8:
            key = Keys.F8;
            break;
        case java.awt.event.KeyEvent.VK_F9:
            key = Keys.F9;
            break;
        case java.awt.event.KeyEvent.VK_F10:
            key = Keys.F10;
            break;
        case java.awt.event.KeyEvent.VK_F11:
            key = Keys.F11;
            break;
        case java.awt.event.KeyEvent.VK_F12:
            key = Keys.F12;
            break;
        case java.awt.event.KeyEvent.VK_HELP:
            key = Keys.HELP;
            break;
        case java.awt.event.KeyEvent.VK_HOME:
            key = Keys.HOME;
            break;
        case java.awt.event.KeyEvent.VK_INSERT:
            key = Keys.INSERT;
            break;
        case java.awt.event.KeyEvent.VK_LEFT:
            key = Keys.LEFT;
            break;
        case java.awt.event.KeyEvent.VK_META:
            key = Keys.META;
            break;
        case java.awt.event.KeyEvent.VK_MULTIPLY:
            key = Keys.MULTIPLY;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD0:
            key = Keys.NUMPAD0;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD1:
            key = Keys.NUMPAD1;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD2:
            key = Keys.NUMPAD2;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD3:
            key = Keys.NUMPAD3;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD4:
            key = Keys.NUMPAD4;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD5:
            key = Keys.NUMPAD5;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD6:
            key = Keys.NUMPAD6;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD7:
            key = Keys.NUMPAD7;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD8:
            key = Keys.NUMPAD8;
            break;
        case java.awt.event.KeyEvent.VK_NUMPAD9:
            key = Keys.NUMPAD9;
            break;
        case java.awt.event.KeyEvent.VK_PAGE_DOWN:
            key = Keys.PAGE_DOWN;
            break;
        case java.awt.event.KeyEvent.VK_PAGE_UP:
            key = Keys.PAGE_UP;
            break;
        case java.awt.event.KeyEvent.VK_PAUSE:
            key = Keys.PAUSE;
            break;
        case java.awt.event.KeyEvent.VK_RIGHT:
            key = Keys.RIGHT;
            break;
        case java.awt.event.KeyEvent.VK_SEMICOLON:
            key = Keys.SEMICOLON;
            break;
        case java.awt.event.KeyEvent.VK_SEPARATOR:
            key = Keys.SEPARATOR;
            break;
        case java.awt.event.KeyEvent.VK_SHIFT:
            key = Keys.SHIFT;
            break;
        case java.awt.event.KeyEvent.VK_SPACE:
            key = Keys.SPACE;
            break;
        case java.awt.event.KeyEvent.VK_SUBTRACT:
            key = Keys.SUBTRACT;
            break;
        case java.awt.event.KeyEvent.VK_TAB:
            key = Keys.TAB;
            break;
        case java.awt.event.KeyEvent.VK_UP:
            key = Keys.UP;
            break;
        }
        return key;
    }

    /**
     * Convert a SAFS RobotKeyEvent to a standard CharSequence character.
     * @param event
     * @return CharSequence character, or null.
     */
    static CharSequence convertToCharacter(RobotKeyEvent event) {
        ArrayList<RobotKeyEvent> list = new ArrayList();
        list.add(event);
        return keysparser.antiParse(list);
    }

    /**
     * Does NOT clear any existing text in the control, but does attempt to insure the window/control has focus.
     * <br><em>Purpose:</em>
     * <a href="http://safsdev.sourceforge.net/sqabasic2000/SeleniumGenericMasterFunctionsReference.htm#detail_InputKeys" alt="inputKeys Keyword Reference" title="inputKeys Keyword Reference">inputKeys</a>
     * <p>
     * Bypasses attempts to use AWT Robot for keystrokes.
     * Attempts to convert SAFS keystrokes to Selenium low-level Actions keystrokes.
     * @param we WebElement to send SAFS keystrokes (or plain text).
     * @param keystrokes SAFS keystrokes or plain text to type.
     * @throws SeleniumPlusException if we are unable to process the keystrokes successfully.
     **/
    public static void inputKeysSAFS2Selenium(WebElement we, String keystrokes) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        IndependantLog.debug(debugmsg + " processing '" + keystrokes + "' on webelement " + we);
        if (!focus(we))
            IndependantLog.warn(debugmsg + " Fail to set focus to webelement " + we);

        RemoteDriver wd = null;
        try {
            wd = (RemoteDriver) getWebDriver();
        } catch (Exception x) {
        }
        // convert to Selenium low-level Action keystrokes.
        if (keysparser != null) {
            Vector keys = keysparser.parseInput(keystrokes);
            Actions actions = new Actions(wd);

            if (we != null)
                actions = actions.moveToElement(we);

            Iterator events = keys.iterator();
            RobotKeyEvent event;
            Keys k = null;
            CharSequence c = null;
            while (events.hasNext()) {
                try {
                    event = (RobotKeyEvent) events.next();
                    c = null;
                    k = convertToKeys(event);
                    if (k == null) {
                        c = convertToCharacter(event);
                    } else {

                    }
                    switch (event.get_event()) {

                    case RobotKeyEvent.KEY_PRESS:
                        if (k != null) {
                            IndependantLog.debug(debugmsg + " handling keyDown '" + k.name() + "'");
                            actions = actions.keyDown(k);
                        } else {
                            IndependantLog.debug(debugmsg + " send char '" + c + "'");
                            actions = actions.sendKeys(c);
                        }
                        break;

                    case RobotKeyEvent.KEY_RELEASE:
                        if (k != null) {
                            IndependantLog.debug(debugmsg + " handling keyUp '" + k.name() + "'");
                            actions = actions.keyUp(k);
                        } else {
                            IndependantLog.debug(debugmsg + " send char '" + c + "'");
                            actions = actions.sendKeys(c);
                        }
                        break;

                    case RobotKeyEvent.KEY_TYPE:
                        if (k != null) {
                            IndependantLog.debug(debugmsg + " send Key '" + k.name() + "'");
                            actions = actions.sendKeys(k);
                        } else {
                            IndependantLog.debug(debugmsg + " send char '" + c + "'");
                            actions = actions.sendKeys(c);
                        }
                        break;
                    default:
                    }
                } catch (Exception x) {
                    IndependantLog.debug(debugmsg + " IGNORING RobotKeyEvent exception:", x);
                }
            }
            try {
                actions.build().perform();
            } catch (StaleElementReferenceException x) {
                // the click probably was successful because the elements have changed!
                IndependantLog.debug(debugmsg + " StaleElementException (not found).");
            } catch (Throwable x) {
                IndependantLog.debug(debugmsg + " " + x.getClass().getName() + ", " + x.getMessage());
            } finally {
                IndependantLog.debug(debugmsg + " selenium actions.build().perform() complete.");
            }
        } else {
            // TODO what if keyparser cannot load a keys converter???
        }
    }

    /**
     * Press down a Key by Java Robot. Call {@link #keyRelease(int)} to release the key.
     * @param keycode int, keycode to press (e.g. <code>KeyEvent.VK_A</code>)
     * @throws SeleniumPlusException if fail
     * @see #keyRelease(int)
     */
    public static void keyPress(int keycode) throws SeleniumPlusException {
        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }

            if (wd == null || wd.isLocalServer()) {
                if (!Robot.keyPress(keycode)) {
                    throw new SeleniumPlusException("SAFS Robot Fail to press key " + KeyEvent.getKeyText(keycode));
                }
            } else {
                //try RMI server.
                wd.rmiAgent.remoteKeyPress(keycode);
            }
        } catch (Exception e) {
            throw new SeleniumPlusException("Unable to successfully complete keyPress due to " + e.getMessage(), e);
        }
    }

    /**
     * Release a Key by Java Robot. Release the key pressed by {@link #keyPress(int)}.
     * @param keycode int, keycode to release (e.g. <code>KeyEvent.VK_A</code>)
     * @throws SeleniumPlusException if fail
     * @see #keyPress(int)
     */
    public static void keyRelease(int keycode) throws SeleniumPlusException {
        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }

            if (wd == null || wd.isLocalServer()) {
                if (!Robot.keyRelease(keycode)) {
                    throw new SeleniumPlusException(
                            "SAFS Robot Fail to release key " + KeyEvent.getKeyText(keycode));
                }
            } else {
                //try RMI server.
                wd.rmiAgent.remoteKeyRelease(keycode);
            }
        } catch (Exception e) {
            throw new SeleniumPlusException("Unable to successfully complete keyRelease due to " + e.getMessage(),
                    e);
        }
    }

    /**
     * Scroll the mouse wheel by Java Robot.
     * @param wheelAmt int, the wheel amount to scroll.
     * @throws SeleniumPlusException if fail
     */
    public static void mouseWheel(int wheelAmt) throws SeleniumPlusException {
        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            if (wd == null || wd.isLocalServer()) {
                if (!Robot.mouseWheel(wheelAmt)) {
                    throw new SeleniumPlusException("SAFS Robot Fail to scroll mouse wheel.");
                }
            } else {
                //try RMI server.
                wd.rmiAgent.remoteMouseWheel(wheelAmt);
            }
        } catch (Exception e) {
            throw new SeleniumPlusException("Unable to successfully complete mouseWheel due to " + e.getMessage(),
                    e);
        }
    }

    /**
     * Press down a Key by Selenium's Actions API. Call {@link #keyUp(Keys)} to release the key.
     * @param keycode Keys, keycode to press (e.g. <code>Keys.CONTROL</code>)
     * @throws SeleniumPlusException if fail
     * @see #keyUp(Keys)
     */
    public static void keyDown(Keys keycode) throws SeleniumPlusException {
        try {
            WebDriver wd = (WebDriver) getWebDriver();
            Actions actions = new Actions(wd);
            actions.keyDown(keycode);
            actions.build().perform();
        } catch (Exception e) {
            throw new SeleniumPlusException("Unable to successfully complete Selenium keyDown for key '" + keycode
                    + "' due to " + e.getMessage(), e);
        }
    }

    /**
     * Release a Key by Selenium's Actions API. Release the key pressed by {@link #keyDown(Keys)}.
     * @param keycode Keys, keycode to release (e.g. <code>Keys.CONTROL</code>)
     * @throws SeleniumPlusException if fail
     * @see #keyDown(Keys)
     */
    public static void keyUp(Keys keycode) throws SeleniumPlusException {
        try {
            WebDriver wd = (WebDriver) getWebDriver();
            Actions actions = new Actions(wd);
            actions.keyUp(keycode);
            actions.build().perform();
        } catch (Exception e) {
            throw new SeleniumPlusException("Unable to successfully complete Selenium keyUp for key '" + keycode
                    + "' due to " + e.getMessage(), e);
        }
    }

    /**
     * Sets a delay in milliseconds between Robot keystrokes for both local and remote servers.
     * @param millisDelay between keystrokes.
     * @throws SeleniumPlusException if we are unable to process the keystrokes successfully.
     * @see org.safs.robot.Robot#setMillisBetweenKeystrokes(int)
     **/
    public static void setDelayBetweenKeystrokes(int millisDelay) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            // change local to match remote for local getMillisBetweenKeystrokes
            IndependantLog.info(debugmsg + " sending '" + String.valueOf(millisDelay) + "' to local Robot.");
            Robot.setMillisBetweenKeystrokes(millisDelay);
            if (wd != null && !wd.isLocalServer()) {
                try {
                    IndependantLog.info(debugmsg + " sending RMI Agent SetKeyDelay '" + String.valueOf(millisDelay)
                            + "' to RMI Server");
                    wd.rmiAgent.remoteSetKeyDelay(millisDelay);
                } catch (Exception e) {
                    IndependantLog.warn(debugmsg + " Fail RMI Agent SetKeyDelay '" + String.valueOf(millisDelay)
                            + "' due to " + StringUtils.debugmsg(e));
                }
            }
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Unable to successfully complete setDelayBetweenKeystrokes due to " + e.getMessage(), e);
        }
    }

    /**
     * Gets the current delay in milliseconds between Robot keystrokes for both local and remote servers.
     * @throws SeleniumPlusException if we are unable to process the keystrokes successfully.
     * @see org.safs.robot.Robot#setMillisBetweenKeystrokes(int)
     **/
    public static int getDelayBetweenKeystrokes() throws SeleniumPlusException {
        int d = Robot.getMillisBetweenKeystrokes();
        // should be the same for local and remote
        IndependantLog
                .info("WDLibrary.getDelayBetweenKeystrokes returning  '" + String.valueOf(d) + "' from Robot.");
        return d;
    }

    /**
     * Set if wait for reaction to "input keys/chars" for both local and remote servers.
     * @param wait boolean if wait or not.
     * @throws SeleniumPlusException if fail.
     * @see org.safs.robot.Robot#setWaitReaction(boolean)
     **/
    public static void setWaitReaction(boolean wait) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            if (wd == null || wd.isLocalServer()) {
                IndependantLog.info(
                        debugmsg + " sending SetWaitReaction  '" + String.valueOf(wait) + "' to local Robot.");
                Robot.setWaitReaction(wait);
            } else {
                try {
                    IndependantLog.info(debugmsg + " sending RMI Agent SetWaitReaction '" + String.valueOf(wait)
                            + "' to RMI Server");
                    wd.rmiAgent.remoteWaitReaction(wait);
                } catch (Exception e) {
                    IndependantLog.warn(debugmsg + " Fail RMI Agent SetWaitReaction '" + String.valueOf(wait)
                            + "' due to " + StringUtils.debugmsg(e));
                }
            }
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Unable to successfully complete setWaitReaction due to " + e.getMessage(), e);
        }
    }

    /**
     * Set if wait for reaction to "input keys/chars" for both local and remote servers.
     * @param wait boolean, if wait or not.
     * @param tokenLength int, the length of a token. Only if the string is longer than this 
     *                         then we wait the reaction after input-keys a certain time 
     *                         indicated by the parameter dealyForToken.
     * @param dealyForToken int, The delay in millisecond to wait the reaction after input-keys 
     *                           for the string as long as a token.
     * @param dealy int, The constant delay in millisecond to wait the reaction after input-keys.
     * @throws SeleniumPlusException if fail.
     * @see org.safs.robot.Robot#setWaitReaction(boolean, int, int, int)
     **/
    public static void setWaitReaction(boolean wait, int tokenLength, int dealyForToken, int dealy)
            throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            if (wd == null || wd.isLocalServer()) {
                IndependantLog.info(
                        debugmsg + " sending SetWaitReaction  '" + String.valueOf(wait) + "' to local Robot.");
                Robot.setWaitReaction(wait, tokenLength, dealyForToken, dealy);
            } else {
                try {
                    IndependantLog.info(debugmsg + " sending RMI Agent SetWaitReaction '" + String.valueOf(wait)
                            + "' to RMI Server");
                    wd.rmiAgent.remoteWaitReaction(wait, tokenLength, dealyForToken, dealy);
                } catch (Exception e) {
                    IndependantLog.warn(debugmsg + " Fail RMI Agent SetWaitReaction '" + String.valueOf(wait)
                            + "' due to " + StringUtils.debugmsg(e));
                }
            }
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Unable to successfully complete setWaitReaction due to " + e.getMessage(), e);
        }
    }

    /**
     * Does NOT clear any existing text in the control, but does attempt to insure the window/control has focus.
     * <br><em>Purpose:</em>
     * <a href="http://safsdev.sourceforge.net/sqabasic2000/SeleniumGenericMasterFunctionsReference.htm#detail_InputKeys" alt="inputKeys Keyword Reference" title="inputKeys Keyword Reference">inputKeys</a>
     * <p>
     * Attempts to use AWT Robot for keystrokes for both local and remote servers.
     * If the remote server does NOT have an RMI Server running to receive the request, then we
     * will attempt to convert SAFS keystrokes to Selenium Actions keystrokes and try that way.
     * @param we WebElement to send SAFS keystrokes; if null, the keystrokes will be sent to the focused element.
     * @param keystrokes in SAFS format to type.
     * @throws SeleniumPlusException if we are unable to process the keystrokes successfully.
     * @see org.safs.robot.Robot#inputKeys(String)
     * @see #inputKeysSAFS2Selenium(WebElement)
     **/
    public static void inputKeys(WebElement we, String keystrokes) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        try {
            if (!focus(we))
                IndependantLog.warn(debugmsg + " Fail to set focus to webelement " + we);

            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            if (wd == null || wd.isLocalServer()) {
                IndependantLog.info(debugmsg + " sending '" + keystrokes + "' to local Robot.inputKeys.");
                Robot.inputKeys(keystrokes);
            } else {
                try {
                    IndependantLog
                            .info(debugmsg + " sending RMI Agent TypeKeys '" + keystrokes + "' to RMI Server");
                    wd.rmiAgent.remoteTypeKeys(keystrokes);
                } catch (Exception e) {
                    IndependantLog.warn(debugmsg + " Fail RMI Agent TypeKeys '" + keystrokes + "' due to "
                            + StringUtils.debugmsg(e));
                    inputKeysSAFS2Selenium(we, keystrokes);
                }
            }
        } catch (Exception e) {
            throw new SeleniumPlusException("Unable to successfully complete InputKeys due to " + e.getMessage(),
                    e);
        }
    }

    /**
     * Does NOT clear any existing text in the control, but does attempt to insure the window/control has focus.
     * <br><em>Purpose:</em>
     * <a href="http://safsdev.sourceforge.net/sqabasic2000/SeleniumGenericMasterFunctionsReference.htm#detail_InputCharacters" alt="inputCharacters Keyword Reference" title="inputCharacters Keyword Reference">inputCharacters</a>
     * @param we WebElement to send characters; if null, the keystrokes will be sent to the focused element.
     * @param keystrokes/plain text to type.
     * @throws SeleniumPlusException if we are unable to process the keystrokes successfully.
     * @see org.safs.robot.Robot#inputKeys(String)
     **/
    public static void inputChars(WebElement we, String keystrokes) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        try {
            if (!focus(we))
                IndependantLog.warn(debugmsg + " Fail to set focus to webelement " + we);

            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            if (wd == null || wd.isLocalServer()) {
                IndependantLog.info(debugmsg + " sending '" + keystrokes + "' to local Robot.inputChars.");
                Robot.inputChars(keystrokes);
            } else {
                try {
                    IndependantLog
                            .info(debugmsg + " sending RMI Agent TypeChars '" + keystrokes + "' to RMI Server");
                    wd.rmiAgent.remoteTypeChars(keystrokes);
                } catch (Exception e) {
                    IndependantLog.warn(debugmsg + " Fail RMI Agent TypeChars '" + keystrokes + "' due to "
                            + StringUtils.debugmsg(e));
                    IndependantLog.info(debugmsg + " sending '" + keystrokes + "' to via WebElement.sendKeys.");
                    we.sendKeys(keystrokes);
                }
            }
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Unable to successfully complete InputCharacters due to " + e.getMessage());
        }
    }

    /**
     * Clear the clipboard on local machine or on machine where the RMI server is running.
     * @throws SeleniumPlusException
     */
    public static void clearClipboard() throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            if (wd == null || wd.isLocalServer()) {
                IndependantLog.info(debugmsg + " clear local clipboard by Robot.");
                Robot.clearClipboard();
            } else {
                try {
                    IndependantLog.info(debugmsg + " clear clipboard on RMI Server");
                    wd.rmiAgent.clearClipboard();
                } catch (Exception e) {
                    IndependantLog.error(
                            debugmsg + " Fail to clear RMI Server clipboard due to " + StringUtils.debugmsg(e));
                    throw e;
                }
            }
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Unable to successfully complete ClearClipboard due to " + e.getMessage());
        }
    }

    /**
     * Set content to the clipboard on local machine or on machine where the RMI server is running.
     * @param content String, the content to set to clipboard
     * @throws SeleniumPlusException
     */
    public static void setClipboard(String content) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            if (wd == null || wd.isLocalServer()) {
                IndependantLog.info(debugmsg + " set '" + content + "' to local clipboard by Robot.");
                Robot.setClipboard(content);
            } else {
                try {
                    IndependantLog.info(debugmsg + " set '" + content + "' to clipboard on RMI Server");
                    wd.rmiAgent.setClipboard(content);
                } catch (Exception e) {
                    IndependantLog.error(debugmsg + " Fail to set '" + content + "' to RMI Server clipboard due to "
                            + StringUtils.debugmsg(e));
                    throw e;
                }
            }
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Unable to successfully complete SetClipboard due to " + e.getMessage());
        }
    }

    /**
     * Get the content from the clipboard on local machine or on machine where the RMI server is running.
     * @param dataFlavor DataFlavor, the data flavor for the content in clipboard
     * @return Object, the content of the clipboard
     * @throws SeleniumPlusException
     */
    public static Object getClipboard(DataFlavor dataFlavor) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            RemoteDriver wd = null;
            try {
                wd = (RemoteDriver) getWebDriver();
            } catch (Exception x) {
            }
            if (wd == null || wd.isLocalServer()) {
                IndependantLog.info(debugmsg + " get content from local clipboard by Robot.");
                return Robot.getClipboard(dataFlavor);
            } else {
                try {
                    IndependantLog.info(debugmsg + " get content from clipboard on RMI Server");
                    return wd.rmiAgent.getClipboard(dataFlavor);
                } catch (Exception e) {
                    IndependantLog.error(debugmsg + " Fail to get content from RMI Server clipboard due to "
                            + StringUtils.debugmsg(e));
                    throw e;
                }
            }
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Unable to successfully complete GetClipboard due to " + e.getMessage());
        }
    }

    /**
     * Get the screen rectangle for a WebElement object.
     * @param we WebElement, the webelement object to get screen rectangle
     * @return Rectangle, the screen rectangle of a webelement object
     * @throws SeleniumPlusException
     */
    public static Rectangle getRectangleOnScreen(WebElement we) throws SeleniumPlusException {
        Rectangle rectangle = null;
        try {
            Point p = getScreenLocation(we);
            org.openqa.selenium.Dimension dim = we.getSize();
            rectangle = new Rectangle();
            rectangle.setBounds(p.x, p.y, dim.getWidth(), dim.getHeight());
            return rectangle;
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Fail get screen rectangle for webelement, due to " + StringUtils.debugmsg(e));
        }
    }

    /**
     *
     * @param key Keys, the selenium Keys value
     * @return int the value of java KeyEvent
     * @throws SeleniumPlusException
     */
    static int toJavaKeyCode(Keys key) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "toJavaKeyCode");
        if (Keys.SHIFT.equals(key))
            return KeyEvent.VK_SHIFT;
        else if (Keys.LEFT_SHIFT.equals(key))
            return KeyEvent.VK_SHIFT;
        else if (Keys.CONTROL.equals(key))
            return KeyEvent.VK_CONTROL;
        else if (Keys.LEFT_CONTROL.equals(key))
            return KeyEvent.VK_CONTROL;
        else if (Keys.ALT.equals(key))
            return KeyEvent.VK_ALT;
        else if (Keys.LEFT_ALT.equals(key))
            return KeyEvent.VK_ALT;
        else {
            String msg = " No handled key '" + (key == null ? "null" : key.toString()) + "'.";
            IndependantLog.debug(debugmsg + msg);
            throw new SeleniumPlusException(msg);
        }
    }

    /**
     * Capture entire current HTML page
     * @param image - Image full path with name.
     * @throws SeleniumPlusException
     */
    public static void captureScreen(String image) throws SeleniumPlusException {

        try {
            File source = ((TakesScreenshot) lastUsedWD).getScreenshotAs(OutputType.FILE);
            FileUtils.copyFile(source, new File(image));
        } catch (IOException e) {
            throw new SeleniumPlusException("Failed to capture screenshot " + e.getMessage());
        } catch (NullPointerException e) {
            throw new SeleniumPlusException("Failed to capture screenshot " + e.getMessage());
        }
    }

    /**
     * @param we WebElement, the component to hover mouse
     * @param point Point, the position relative to the component to hover the mouse
     * @param millisStay double, the period to hover the mouse, in milliseconds
     * @throws SeleniumPlusException if the hover fail
     */
    public static void mouseHover(WebElement we, Point point, int millisStay) throws SeleniumPlusException {

        try {
            //Hover mouse on the webelement
            Actions action = new Actions(lastUsedWD);
            action.moveToElement(we);
            if (point != null) {
                int xOffset = point.x - we.getSize().width / 2;
                int yOffset = point.y - we.getSize().height / 2;
                action.moveByOffset(xOffset, yOffset);
            }
            action.build().perform();

            //Pause a while
            StringUtilities.sleep(millisStay);

            //Move out the mouse
            //action.moveByOffset(-Robot.SCREENSZIE.width, -Robot.SCREENSZIE.height);//This will throw exception
            //action.build().perform();
            Robot.getRobot().mouseMove(-Robot.SCREENSZIE.width, -Robot.SCREENSZIE.height);

        } catch (Exception e) {
            IndependantLog.warn(StringUtils.debugmsg(false) + "Failed to hover mouse by Selenium API: "
                    + StringUtils.debugmsg(e));
            Point screenPoint = new Point(point.x, point.y);
            try {
                translatePoint(we, screenPoint);
                IndependantLog.warn(StringUtils.debugmsg(false) + "Try SAFS Robot to hover mouse at screen point ["
                        + screenPoint.x + "," + screenPoint.y + "]");
                Robot.mouseHover(screenPoint, millisStay);
            } catch (Exception e2) {
                throw new SeleniumPlusException(
                        "Failed to hover mouse at point [" + point.x + "," + point.y + "] relative to webelement.");
            }
        }
    }

    /**
     * Perform a left-drag from start point to end point relative to webelement (LeftUp corner)
     * with the "ALT" key pressed during the drag.
     * @param we WebElement, the component relative to which to drag
     * @param start Point, the start point relative to the webelement
     * @param end Point, the end point relative to the webelement
     * @throws SeleniumPlusException
     */
    public static void altLeftDrag(WebElement we, Point start, Point end) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            IndependantLog.debug(debugmsg + " drag from " + start + " to " + end + " relative to webelement.");
            translatePoints(we, start, end);
            Robot.altLeftDrag(start, end);

        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Failed to drag by SAFS Robot: " + StringUtils.debugmsg(e));
            throw new SeleniumPlusException("Failed to drag.");
        }
    }

    /**
     * Perform a left-drag from start point to end point relative to webelement (LeftUp corner)
     * with the "CONTROL" and "ALT" key pressed during the drag.
     * @param we WebElement, the component relative to which to drag
     * @param start Point, the start point relative to the webelement
     * @param end Point, the end point relative to the webelement
     * @throws SeleniumPlusException
     */
    public static void ctrlAltLeftDrag(WebElement we, Point start, Point end) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            IndependantLog.debug(debugmsg + " drag from " + start + " to " + end + " relative to webelement.");
            translatePoints(we, start, end);
            Robot.ctrlAltLeftDrag(start, end);

        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Failed to drag by SAFS Robot: " + StringUtils.debugmsg(e));
            throw new SeleniumPlusException("Failed to drag.");
        }
    }

    /**
     * Perform a left-drag from start point to end point relative to webelement (LeftUp corner)
     * with the "CONTROL" key pressed during the drag.
     * @param we WebElement, the component relative to which to drag
     * @param start Point, the start point relative to the webelement
     * @param end Point, the end point relative to the webelement
     * @throws SeleniumPlusException
     */
    public static void ctrlLeftDrag(WebElement we, Point start, Point end) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            IndependantLog.debug(debugmsg + " drag from " + start + " to " + end + " relative to webelement.");
            translatePoints(we, start, end);
            Robot.ctrlLeftDrag(start, end);

        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Failed to drag by SAFS Robot: " + StringUtils.debugmsg(e));
            throw new SeleniumPlusException("Failed to drag.");
        }
    }

    /**
     * Perform a left-drag from start point to end point relative to webelement (LeftUp corner)
     * with the "CONTROL" and "SHIFT" key pressed during the drag.
     * @param we WebElement, the component relative to which to drag
     * @param start Point, the start point relative to the webelement
     * @param end Point, the end point relative to the webelement
     * @throws SeleniumPlusException
     */
    public static void ctrlShiftLeftDrag(WebElement we, Point start, Point end) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            IndependantLog.debug(debugmsg + " drag from " + start + " to " + end + " relative to webelement.");
            translatePoints(we, start, end);
            Robot.ctrlShiftLeftDrag(start, end);

        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Failed to drag by SAFS Robot: " + StringUtils.debugmsg(e));
            throw new SeleniumPlusException("Failed to drag.");
        }
    }

    /**
     * Perform a left-drag from start point to end point relative to webelement (LeftUp corner)
     * with the "SHIFT" key pressed during the drag.
     * @param we WebElement, the component relative to which to drag
     * @param start Point, the start point relative to the webelement
     * @param end Point, the end point relative to the webelement
     * @throws SeleniumPlusException
     */
    public static void shiftLeftDrag(WebElement we, Point start, Point end) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            IndependantLog.debug(debugmsg + " drag from " + start + " to " + end + " relative to webelement.");
            translatePoints(we, start, end);
            Robot.shiftLeftDrag(start, end);

        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Failed to drag by SAFS Robot: " + StringUtils.debugmsg(e));
            throw new SeleniumPlusException("Failed to drag.");
        }
    }

    /**
     * Perform a right-drag from start point to end point relative to webelement (LeftUp corner).
     * @param we WebElement, the component relative to which to drag
     * @param start Point, the start point relative to the webelement
     * @param end Point, the end point relative to the webelement
     * @throws SeleniumPlusException
     */
    public static void rightDrag(WebElement we, Point start, Point end) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            IndependantLog.debug(debugmsg + " drag from " + start + " to " + end + " relative to webelement.");
            translatePoints(we, start, end);
            Robot.rightDrag(start, end);

        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Failed to drag by SAFS Robot: " + StringUtils.debugmsg(e));
            throw new SeleniumPlusException("Failed to drag.");
        }
    }

    /**
     * Perform a left-drag from start point to end point relative to webelement (LeftUp corner).
     * @param we WebElement, the component relative to which to drag
     * @param start Point, the start point relative to the webelement
     * @param end Point, the end point relative to the webelement
     * @throws SeleniumPlusException
     */
    public static void leftDrag(WebElement we, Point start, Point end) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            IndependantLog.debug(debugmsg + " drag from " + start + " to " + end + " relative to webelement.");
            translatePoints(we, start, end);
            Robot.leftDrag(start, end);

        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Failed to drag by SAFS Robot: " + StringUtils.debugmsg(e));
            IndependantLog.debug(debugmsg + "Try to drag by Selenium API.");

            try {
                Actions actions = new Actions(lastUsedWD);
                actions = actions.moveToElement(we, start.x, start.y);
                actions = actions.clickAndHold().moveByOffset((end.x - start.x), (end.y - start.y)).release();
                actions.build().perform();
            } catch (Exception e1) {
                IndependantLog.warn(debugmsg + "Failed to drag by Selenium API: " + StringUtils.debugmsg(e1));
                throw new SeleniumPlusException("Failed to drag.");
            }
        }
    }

    /**
     * Adjust the relative coordinate to screen absolute coordinate according to the webelement.
     * @param we WebElement, the component relative to which to adjust coordinate
     * @param start Point, the start relative point, it will bring back the adjusted screen coordinate
     * @param end Point, the end relative point, it will bring back the adjusted screen coordinate
     * @throws Exception
     */
    private static void translatePoints(WebElement we, Point start, Point end) throws Exception {
        translatePoint(we, start);
        translatePoint(we, end);
    }

    /**
     * Adjust the relative coordinate to screen absolute coordinate according to the webelement.
     * @param we WebElement, the component relative to which to adjust coordinate
     * @param point Point, the relative point, it will bring back the adjusted screen coordinate
     * @throws Exception
     */
    private static void translatePoint(WebElement we, Point point) throws Exception {
        String debugmsg = StringUtils.debugmsg(false);

        Rectangle rec = WDLibrary.getRectangleOnScreen(we);
        IndependantLog.debug(debugmsg + " webelement screen location is [" + rec.getX() + "," + rec.getY() + "]");

        //Translate the point according to webelement's left-up corner
        point.translate(rec.x, rec.y);

        //check and keep in screen boundaries
        if (point.x < 0)
            point.x = 0;
        if (point.y < 0)
            point.y = 0;
        if (point.x > ImageUtils.getScreenWidth() - 1)
            point.x = ImageUtils.getScreenWidth() - 1;
        if (point.y > ImageUtils.getScreenHeight() - 1)
            point.y = ImageUtils.getScreenHeight() - 1;
    }

    /**
     * Given the element, and the (offsetX, offsetY) relative to element.
     * This function will calculate the offset point screen coordination.
     * 
     * @param element WebElement, the element relative to which the coordination will be calculated.
     * @param offsetX String, the offset on x axis, in pixel or in percentage, for example 15 or 30%.
     * @param offsetX String, the offset on y axis, in pixel or in percentage, for example 45 or 50%.
     * 
     * @return Point, the offset point screen coordination; or null if any exception occured.
     *
     **/
    public static Point getElementOffsetScreenLocation(WebElement element, String offsetX, String offsetY) {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            Point screenLoc = WDLibrary.getScreenLocation(element);
            Dimension dimemsion = element.getSize();

            //calc coords according to the offset and element's location and dimension
            double dx, dy;
            dx = ImageUtils.calculateAbsoluteCoordinate(screenLoc.getX(), dimemsion.getWidth(), offsetX);
            dy = ImageUtils.calculateAbsoluteCoordinate(screenLoc.getY(), dimemsion.getHeight(), offsetY);

            return new Point((int) dx, (int) dy);
        } catch (Exception e) {
            IndependantLog.error(debugmsg + ": Exception", e);
            return null;
        }
    }

    /**
     * Capture component image
     * @param we - WebElement object.
     * @param imgName - Image full path with name
     * @throws Exception
     */
    public static void captureScreen(WebElement we, String imgName, String fileformat)
            throws SeleniumPlusException {
        try {
            Rectangle rect = new Rectangle(we.getLocation().x, we.getLocation().y, we.getSize().width,
                    we.getSize().height);
            BufferedImage img = captureBrowserArea(rect);
            File outputfile = new File(imgName + "." + fileformat);
            ImageIO.write(img, fileformat, outputfile);
        } catch (Exception e) {
            throw new SeleniumPlusException("Failed to capture screenshot " + e.getMessage());
        }
    }

    /**
     * Capture the image on browser according to the rectangle relative to the browser.
     * @param rectangle Rectangle, within which to capture the image.
     * @return BufferedImage, the image within the rectangle on a browser, or the whole browser if rectangle is null.
     * @throws SeleniumPlusException
     */
    public static BufferedImage captureBrowserArea(Rectangle rectangle) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        try {
            //Get the whole page image
            byte[] imageBytes = ((TakesScreenshot) lastUsedWD).getScreenshotAs(OutputType.BYTES);
            BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageBytes));
            //img = new PngImage().read(new ByteArrayInputStream(imageBytes), true);
            //Get sub image according to the rectangle
            if (rectangle == null)
                return img;
            else {
                IndependantLog.debug(debugmsg + " the initial subarea is " + rectangle);
                //If the rectangle is beyond the browser, throw exception
                if (rectangle.x >= img.getWidth() || rectangle.y >= img.getHeight()) {
                    throw new SeleniumPlusException("The component is totally outside of browser!");
                }
                if (rectangle.x < 0) {
                    IndependantLog.debug(debugmsg + " subarea x coordinate should NOT be negative, set it to 0.");
                    rectangle.x = 0;
                }
                if (rectangle.y < 0) {
                    IndependantLog.debug(debugmsg + " subarea y coordinate should NOT be negative, set it to 0.");
                    rectangle.y = 0;
                }

                //if the rectangle is larger than the captured browser image, then
                //we need to reduce the rectangle
                int tempW = rectangle.x + rectangle.width;
                int tempH = rectangle.y + rectangle.height;
                if (tempW > img.getWidth())
                    rectangle.width = img.getWidth() - rectangle.x;
                if (tempH > img.getHeight())
                    rectangle.height = img.getHeight() - rectangle.y;

                if (tempW > img.getWidth() || tempH > img.getHeight()) {
                    IndependantLog.debug(debugmsg + " subarea has been adjusted to " + rectangle);
                }
                return img.getSubimage(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
            }
        } catch (Exception e) {
            String message = (rectangle == null ? "of whole browser" : "on browser area '" + rectangle + "'");
            message = "Failed to capture screenshot " + message;
            message += " due to " + e.getMessage();
            IndependantLog.error(debugmsg + message);
            throw new SeleniumPlusException(message);
        }
    }

    /**
     * Start browser
     * @param BrowserName - Browser name such as InternetExplorer, Chrome and FireFox.
     * @param Url - Url including http protocol prefix.
     * @param Id - Id or Title of the Browser incase of two instances needs.
     * @param timeout - Implicit time out to be waited before throw exception.
     * @param isRemote - Start interactive testcase development mode.
     * @throws Exception
     * @see {@link #startBrowser(String, String, String, int, boolean, HashMap)}
     */
    public static void startBrowser(String BrowserName, String Url, String Id, int timeout, boolean isRemote)
            throws SeleniumPlusException {
        startBrowser(BrowserName, Url, Id, timeout, isRemote, null);
    }

    /**
     * Start browser
     * <p>
     * Expects System Properties 'selenium.host' and 'selenium.port' to be set.<br>
     * Otherwise, defaults to 'localhost' on port '4444'.
     * <p>
     * @param BrowserName - Browser name such as InternetExplorer, Chrome and FireFox.
     * @param Url - Url including http protocol prefix.
     * @param Id - Id or Title of the Browser incase of two instances needs.
     * @param timeout - Implicit time out to be waited before throw exception.
     * @param isRemote - Start interactive testcase development mode.
     * @param extraParameters HashMap<String,Object>, can be used to pass more browser parameters, such as a firefox profile to use.
     * @throws Exception
     */
    public static void startBrowser(String BrowserName, String Url, String Id, int timeout, boolean isRemote,
            HashMap<String, Object> extraParameters) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "startBrowser");
        //previousDriver is the possible WebDriver with the same ID stored in the Cache.
        WebDriver previousDriver = null;

        String host = System.getProperty(SelectBrowser.SYSTEM_PROPERTY_SELENIUM_HOST);
        if (host == null || host.isEmpty())
            host = SelectBrowser.DEFAULT_SELENIUM_HOST;
        String port = System.getProperty(SelectBrowser.SYSTEM_PROPERTY_SELENIUM_PORT);
        if (port == null || port.isEmpty())
            port = SelectBrowser.DEFAULT_SELENIUM_PORT;

        if (BrowserName == null || BrowserName.length() == 0) {
            BrowserName = System.getProperty(SelectBrowser.SYSTEM_PROPERTY_BROWSER_NAME);
            if (BrowserName == null || BrowserName.length() == 0) {
                BrowserName = SelectBrowser.BROWSER_NAME_FIREFOX;
                System.setProperty(SelectBrowser.SYSTEM_PROPERTY_BROWSER_NAME, BrowserName);
            }
        }
        if (Id == null || Id.equals("")) {
            Id = String.valueOf("".hashCode());
        }

        if (!isRemote) {
            IndependantLog.warn(debugmsg + "attempting to start a local (not remote) browser instance...");
            SelectBrowser sb = new SelectBrowser();
            previousDriver = addWebDriver(Id, sb.getBrowserInstance(BrowserName, extraParameters));
            lastUsedWD.manage().timeouts().implicitlyWait(timeout, TimeUnit.SECONDS);
            lastUsedWD.manage().window().setSize(new Dimension(1024, 768)); // default window size
            if (Url != null && Url.length() > 0)
                lastUsedWD.get(Url);

        } else {
            IndependantLog.warn(debugmsg + "attempting to start new session on remote server");
            try {
                SelectBrowser sb = new SelectBrowser();
                DesiredCapabilities capabilities = sb.getDesiredCapabilities(BrowserName, extraParameters);
                capabilities.setJavascriptEnabled(true);
                capabilities.setCapability(CapabilityType.TAKES_SCREENSHOT, true);
                capabilities.setCapability(RemoteDriver.CAPABILITY_ID, Id); // custom id for session tracking
                capabilities.setCapability(RemoteDriver.CAPABILITY_RECONNECT, false); // custom id
                capabilities.setCapability(RemoteDriver.CAPABILITY_REMOTESERVER, host); // custom id
                //capabilities.setBrowserName(BrowserName); now it set from capabilities
                previousDriver = addWebDriver(Id,
                        new RemoteDriver(new URL("http://" + host + ":" + port + "/wd/hub"), capabilities));
                lastUsedWD.manage().timeouts().implicitlyWait(timeout, TimeUnit.SECONDS);

                try {
                    //setSize() is not supported by "chromedriver" for "android chrome"
                    lastUsedWD.manage().window().setSize(new Dimension(1024, 768)); // default window size
                } catch (Exception e) {
                    IndependantLog.warn(debugmsg + StringUtils.debugmsg(e));
                }

                if (Url != null && Url.length() > 0)
                    lastUsedWD.get(Url);
            } catch (Throwable t) {
                IndependantLog.warn(debugmsg + "new RemoteDriver failure.  RemoteServer may not be running!");
                IndependantLog.warn(debugmsg + "Caused By: " + t.getClass().getName() + ", " + t.getMessage());
                Throwable c = t;
                do {
                    c = c.getCause();
                    if (c instanceof Throwable) {
                        IndependantLog
                                .warn(debugmsg + "Caused By: " + c.getClass().getName() + ", " + c.getMessage());
                    }
                } while (c != null);
                throw new SeleniumPlusException(t);
            }
        }

        if (previousDriver != null) {
            //Just simply stop duplicate session.
            IndependantLog.warn(debugmsg + "There is a WebDriver previously stored in cache with id '" + Id
                    + "', going to stop it.");
            previousDriver.quit();
        }
        //Initialize javascript's variables.
        js_initialize();
    }

    /**
     * Close browser (close all windows associated) indicated by ID.
     * If the provided ID is associated with the "current" or "lastUsed" WebDriver
     * the call to removeWebDriver will automatically "pop" the next WebDriver off
     * the stack to be the new "current" or "lastUsed" WebDriver.
     * @param ID   String, the id to identify the browser
     * @throws IllegalArgumentException if the provided browser ID is null or not known as a running instance.
     * @see #removeWebDriver(String)
     */
    public static void stopBrowser(String ID) throws IllegalArgumentException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "stopBrowser");
        if (ID == null)
            throw new IllegalArgumentException("Browser ID provided was null.");
        WebDriver webdriver = removeWebDriver(ID);
        if (webdriver == null) {
            IndependantLog.warn(debugmsg + "cannot get webdriver according to unknown id '" + ID + "'");
            throw new IllegalArgumentException(
                    "Browser ID '" + ID + "' is not a valid ID for a running browser session.");
        }
        webdriver.quit();
    }

    /**
     * Resize current browser window
     * @param width int, width in pixels
     * @param height int, height in pixels
     * @throws SeleniumPlusException
     */
    public static void resizeBrowserWindow(int width, int height) throws SeleniumPlusException {

        try {
            Dimension size = new Dimension(width, height);
            lastUsedWD.manage().window().setSize(size);
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Failed to resize current browser window to (" + width + "," + height + ") " + e.getMessage());
        }
    }

    /**
     * Kill the process 'chromedriver.exe'.
     * @param host String, the host name on which the process 'chromedriver.exe' will be killed.
     * @return List<ProcessInfo>, a list containing the killed process's id and kill-execution-RC.
     * @throws SAFSException
     */
    public static List<ProcessInfo> killChromeDriver(String host) throws SAFSException {
        //wmic process where " commandline like '%d:\\seleniumplus\\extra\\chromedriver.exe%' and name = 'chromedriver.exe' "
        String wmiSearchCondition = GenericProcessMonitor.wqlCondition("commandline", "\\extra\\chromedriver.exe",
                true, false);

        wmiSearchCondition += " and "
                + GenericProcessMonitor.wqlCondition("name", "chromedriver.exe", false, false);

        WQLSearchCondition condition = new WQLSearchCondition(wmiSearchCondition);

        return GenericProcessMonitor.shutdownProcess(host, condition);
    }

    /**
     * scroll browser window by x and/or y number of pixels.
     * Only works if the associated scrollbar(s) are actually visible.
     * Synonymous to Javascript window.scrollBy(x,y).
     * @param x int, horizontal scrolling in pixels
     * @param y int, vertical scrolling in pixels
     * @param element, scroll the topmost window containing this element.
     * @throws SeleniumPlusException
     */
    public static void scrollBrowserWindowBy(int x, int y, WebElement element) throws SeleniumPlusException {
        try {
            executeJavaScriptOnWebElement("window.scrollBy(" + x + "," + y + ");", element);
        } catch (Exception e) {
            throw new SeleniumPlusException("Failed to scroll browser window by (" + x + "," + y + ") pixels. "
                    + e.getClass().getName() + ", " + e.getMessage());
        }
    }

    /**
     * scroll browser window to x and/or y scroll position.
     * Only works if the associated scrollbar(s) are actually visible.
     * Synonymous to Javascript window.scrollTo(x,y).
     * @param x int, horizontal position in pixels
     * @param y int, vertical position in pixels
     * @param element, scroll the topmost window containing this element.
     * @throws SeleniumPlusException
     */
    public static void scrollBrowserWindowTo(int x, int y, WebElement element) throws SeleniumPlusException {
        try {
            executeJavaScriptOnWebElement("window.scrollTo(" + x + "," + y + ");", element);
        } catch (Exception e) {
            throw new SeleniumPlusException("Failed to scroll browser window to (" + x + "," + y + ") pixels. "
                    + e.getClass().getName() + ", " + e.getMessage());
        }
    }

    /**
     * Align the top of webelement to the top of browser.
     * @param element WebElement, the element to scroll to align with browser's top
     * @throws SeleniumPlusException
     */
    public static void alignToTop(WebElement element) throws SeleniumPlusException {
        try {
            StringBuffer jsScript = new StringBuffer();
            jsScript.append(JavaScriptFunctions.scrollIntoView());
            jsScript.append("scrollIntoView(arguments[0], arguments[1]);\n");

            executeJavaScriptOnWebElement(jsScript.toString(), element, true);

        } catch (Exception e) {
            throw new SeleniumPlusException("Failed to align to top of browser. Met " + StringUtils.debugmsg(e));
        }
    }

    /**
     * Align the bottom of webelement to the bottom of browser.
     * @param element WebElement, the element to scroll to align with browser's bottom
     * @throws SeleniumPlusException
     */
    public static void alignToBottom(WebElement element) throws SeleniumPlusException {
        try {
            StringBuffer jsScript = new StringBuffer();
            jsScript.append(JavaScriptFunctions.scrollIntoView());
            jsScript.append("scrollIntoView(arguments[0], arguments[1]);\n");

            executeJavaScriptOnWebElement(jsScript.toString(), element, false);

        } catch (Exception e) {
            throw new SeleniumPlusException("Failed to align to bottom of browser. Met " + StringUtils.debugmsg(e));
        }
    }

    /**
     * Try to show the webelement on the browser's page.<br>
     * @param element WebElement, the webelement to show.
     *
     * @return boolean, true if succeed
     */
    public static boolean showOnPage(WebElement element) {
        return showOnPage(element, new String[0]);
    }

    /**
     * Try to show the webelement on the browser's page.<br>
     * First, try to see if the web-element is already shown on page, if not shown on the page<br>
     * then it will try to move the web-element to show it on page.<br>
     * Finally, if the passed in parameter verify is true, it will return true only if the element
     * is shown on the page;<br>
     * if the passed in parameter verify is false, it will always return true.<br>
     * @param element WebElement, the webelement to show.
     * @param params optional<ul>
     * <b>optionals[0] verify</b> boolean, verify that the component is shown on page. The default value is false.<br>
     * </ul>
     *
     * @return boolean, true if succeed
     *
     * @history
     * <br> May 18, 2015   sbjlwa   Get refreshed WebElement after moving it on the page.
     */
    public static boolean showOnPage(WebElement element, String... optional) {
        String debugmsg = StringUtils.debugmsg(false);

        if (isShowOnPage(element))
            return true;

        boolean verify = false;
        if (optional != null && optional.length > 0) {
            verify = StringUtilities.convertBool(optional[0]);
        }

        try {
            IndependantLog.debug(debugmsg + "make element visible in brower viewport.");
            Coordinates coordinate = ((Locatable) element).getCoordinates();
            coordinate.inViewPort();
            //after inViewPort(), the real element may move, but WebElement.getLocation() may still return the old value
            if (verify) {
                IndependantLog.debug(debugmsg + "refreshing element reference for verification.");
                element = CFComponent.refresh(element);
            }
            if (isShowOnPage(element))
                return true;
        } catch (Exception e) {
            IndependantLog.warn(
                    debugmsg + "Fail to show webelement in browser's viewport, due to " + StringUtils.debugmsg(e));
        }

        try {
            IndependantLog.debug(debugmsg + "scroll browser to the top-left corner of this component.");
            org.openqa.selenium.Point compLoc = element.getLocation();
            WDLibrary.scrollBrowserWindowTo(compLoc.x, compLoc.y, element);
            if (verify) {
                IndependantLog.debug(debugmsg + "refreshing element reference again for verification.");
                element = CFComponent.refresh(element);
            }
            if (isShowOnPage(element))
                return true;
        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Fail to scroll browser to the top-left corner of webelement, due to "
                    + StringUtils.debugmsg(e));
        }

        try {
            IndependantLog.debug(debugmsg + "align the top this component to top of browser's viewport.");
            WDLibrary.alignToTop(element);
            if (verify) {
                IndependantLog.debug(debugmsg + "refreshing element reference yet again for verification.");
                element = CFComponent.refresh(element);
            }
            if (isShowOnPage(element))
                return true;
        } catch (Exception e) {
            IndependantLog.warn(debugmsg + "Fail to align top of webelement to browser-viewport-top, due to "
                    + StringUtils.debugmsg(e));
        }

        if (verify) {
            IndependantLog.debug(debugmsg + "check if the element 'Top-Left Corner' is located in the page.");
            org.openqa.selenium.Point offset = new org.openqa.selenium.Point(1, 1);
            return isShowOnPage(element, offset);
        }

        return true;
    }

    /**
     * @param element WebElement, to check if it is shown on the page
     * @return boolean true if the web-element is fully shown on the page
     */
    public static boolean isShowOnPage(WebElement element) {
        return isShowOnPage(element, null);
    }

    /**
     * To check if a certain point of web-element is shown on the page.<br>
     * If the point is not given or it is out of boundary of web-element, method will<br>
     * check if the web-element is fully shown on the page.<br>
     *
     * @param element WebElement, to check if it is shown on the page
     * @param offset Point, the offset from Left-Top of web-element; the point to check. can be null
     * @return boolean if the offest-point or the full-web-element is shown on the page.
     */
    public static boolean isShowOnPage(WebElement element, org.openqa.selenium.Point offset) {
        String debugmsg = StringUtils.debugmsg(false);
        boolean isShowOnPage = false;

        try {
            WDLibrary.checkNotNull(element);

            //The location(Left-Top) is relative to the viewport
            org.openqa.selenium.Point elementLTLoc = element.getLocation();
            Dimension elementD = element.getSize();

            int browserXOffset = (int) WDLibrary.lastBrowserWindow.getPageXOffset();
            int browserYOffset = (int) WDLibrary.lastBrowserWindow.getPageYOffset();
            //move the location according to the page-offset, get the location relative to the page
            elementLTLoc = elementLTLoc.moveBy(-browserXOffset, -browserYOffset);
            org.openqa.selenium.Point elementBRLoc = elementLTLoc.moveBy(elementD.width, elementD.height);

            //Get the bounds of the browser's page
            int browserClientW = (int) WDLibrary.lastBrowserWindow.getClientWidth();
            int browserClientH = (int) WDLibrary.lastBrowserWindow.getClientHeight();
            Dimension browserPageBounds = new Dimension(browserClientW, browserClientH);

            IndependantLog.debug(debugmsg + " offset=" + offset + "; element dimension=" + elementD + "; ");

            if (offset != null && isLocationInBounds(offset, elementD)) {
                //check the offset point is shown browser's page
                elementLTLoc = elementLTLoc.moveBy(offset.x, offset.y);
                IndependantLog.debug(debugmsg + "check if 'element (Top Left+offset) Corner'=" + elementLTLoc
                        + " is in 'browser window dimension'=" + browserPageBounds + "; ");
                isShowOnPage = isLocationInBounds(elementLTLoc, browserPageBounds);
            } else {
                //if Left-Top and Bottom-Right is in the browser, the element is fully shown
                IndependantLog.debug(debugmsg + "check if 'element Top Left Corner'=" + elementLTLoc
                        + " and 'Bottom Right Corner'=" + elementBRLoc + " are both in 'browser window dimension'="
                        + browserPageBounds + "; ");
                isShowOnPage = isLocationInBounds(elementLTLoc, browserPageBounds)
                        && isPointInBounds(elementBRLoc, browserPageBounds);
            }

        } catch (Exception e) {
            IndependantLog.error(debugmsg + "Fail due to " + StringUtils.debugmsg(e));
        }

        IndependantLog.debug(debugmsg + " return " + isShowOnPage);
        return isShowOnPage;
    }

    /**
     * check if the point p locates in the Dimension.
     * @param p Point, relative to the Dimension. Usually the top-left point of an item. 
     * see if the Point is inside bounds of 0,0,bounds.width, bounds.height.
     * @param bounds Dimension, the boundary
     * @return boolean true if the point p locates in the Dimension.
     * A point p on the extreme width or height boundary is NOT in-bounds.
     */
    public static boolean isLocationInBounds(org.openqa.selenium.Point p, Dimension bounds) {
        if (p == null || bounds == null)
            return false;
        return (0 < p.x && p.x < bounds.width) && (0 < p.y && p.y < bounds.height);
    }

    /**
     * check if the Point p locates inside the Dimension.
     * @param p Point, point relative to 0,0, bounds.width, bounds.height.  
     * Usually this is the bottom-right point test of a boundary.
     * @param bounds Dimension, the boundary
     * @return boolean true if the point p locates in the Dimension. 
     * A point p on the extreme width or height boundary IS considered in-bounds.
     */
    public static boolean isPointInBounds(org.openqa.selenium.Point p, Dimension bounds) {
        if (p == null || bounds == null)
            return false;
        return (0 < p.x && p.x <= bounds.width) && (0 < p.y && p.y <= bounds.height);
    }

    /**
     * Set position of current browser window.
     * Synonymous to Javascript window.moveTo().
     * @param x int, x-coordination in pixels
     * @param y int, y-coordination in pixels
     * @throws SeleniumPlusException
     */
    public static void setPositionBrowserWindow(int x, int y) throws SeleniumPlusException {

        try {
            org.openqa.selenium.Point position = new org.openqa.selenium.Point(x, y);
            lastUsedWD.manage().window().setPosition(position);
        } catch (Exception e) {
            throw new SeleniumPlusException(
                    "Failed to set position of current browser window to (" + x + "," + y + ") " + e.getMessage());
        }
    }

    /**
     * Maximize current browser window
     * @throws SeleniumPlusException
     */
    public static void maximizeBrowserWindow() throws SeleniumPlusException {

        try {
            lastUsedWD.manage().window().maximize();
        } catch (Exception e) {
            throw new SeleniumPlusException("Failed to maximize current browser window" + e.getMessage());
        }
    }

    /**
     * This implementation does NOT really minimize the window, it is a workaround.<br>
     * Note: set the size to (0,0) can NOT minimize the window. So move the window out of the screen.<br>
     * Minimize current browser window
     * @throws SeleniumPlusException
     */
    public static void minimizeBrowserWindow() throws SeleniumPlusException {

        try {
            org.openqa.selenium.Point outOfScreenPosition = new org.openqa.selenium.Point(-1000, -1000);
            lastUsedWD.manage().window().setPosition(outOfScreenPosition);
            lastUsedWD.manage().window().setSize(new Dimension(0, 0));
        } catch (Exception e) {
            throw new SeleniumPlusException("Failed to minimize current browser window" + e.getMessage());
        }
    }

    /**
     * Retrieves the WebDriver/Browser with the given title.
     * This also makes that WebDriver the "current" or "lastUsedWD".
     * @param title
     * @return
     * @throws SeleniumPlusException
     */
    public static WebDriver getBrowserWithTitle(String title) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "getBrowserWithTitle");
        WebDriver webdriver = getWebDriverWithTitle(title);
        if (webdriver == null) {
            throw new SeleniumPlusException(debugmsg + "cannot get webdriver according to title '" + title + "'",
                    SeleniumPlusException.CODE_OBJECT_IS_NULL);
        }
        String ID = getIDForWebDriver(webdriver);
        try {
            RemoteDriver.setLastSessionId(ID);
        } catch (Exception e) {
        }

        if (lastUsedWD != webdriver) {
            lastUsedWD = webdriver;
            try {
                refreshJSExecutor();
            } catch (SeleniumPlusException ignored) {
            }
        }
        return webdriver;
    }

    /**
     * Close the Browser/WebDriver associated with the last WebElement accessed.
     * So, we will assume the lastUsedWD is the browser to close.
     * Currently, the call to stopBrowser will set the new lastUsedWD.
     * However, in the future the search algorithms for each component will set the
     * lastUsedWD independently.
     * @throws SeleniumPlusException if the lastUsed WebDriver is not valid or is null.
     * @see #stopBrowser(String)
     */
    public static void closeBrowser() throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "closeBrowser");
        WebDriver webdriver = lastUsedWD;
        if (webdriver == null) {
            throw new SeleniumPlusException(debugmsg + "lastUsed webdriver is null!",
                    SeleniumPlusException.CODE_OBJECT_IS_NULL);
        }
        String ID = getIDForWebDriver(webdriver);
        try {
            stopBrowser(ID);
        } catch (IllegalArgumentException i) {
            throw new SeleniumPlusException(debugmsg + "illegal or invalid browser ID.",
                    SeleniumPlusException.CODE_OBJECT_IS_NULL);
        }
    }

    /**
     * Retrieves the WebDriver/Browser with the given id.
     * This also makes that WebDriver the "current" or "lastUsedWD".
     * @param id
     * @return
     * @throws SeleniumPlusException
     */
    public static WebDriver getBrowserWithID(String id) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(WDLibrary.class, "getBrowserWithID");
        WebDriver webdriver = getWebDriver(id);
        if (webdriver == null)
            throw new SeleniumPlusException(debugmsg + "cannot get webdriver with id '" + id + "'",
                    SeleniumPlusException.CODE_OBJECT_IS_NULL);

        try {
            RemoteDriver.setLastSessionId(id);
        } catch (Exception e) {
        }

        if (lastUsedWD != webdriver) {
            lastUsedWD = webdriver;
            try {
                refreshJSExecutor();
            } catch (SeleniumPlusException ignored) {
            }
        }
        return webdriver;
    }

    /**
     * Highlight Element / object
     * @param element to be highlighted
     * 
     * @deprecated it is replaced by {@link #highlight(WebElement)}.
     */
    public static void highlightElement(WebElement element) throws SeleniumPlusException {

        try {
            for (int i = 0; i < 3; i++) {
                JavascriptExecutor js = (JavascriptExecutor) getWebDriver();
                ;
                js.executeScript("arguments[0].setAttribute('style', arguments[1]);", element,
                        "color: yellow; border: 2px solid yellow;");
                Thread.sleep(3);
                js.executeScript("arguments[0].setAttribute('style', arguments[1]);", element, "");

            }
        } catch (Exception e) {
            throw new SeleniumPlusException("Failed to highlight object " + e.getMessage());
        }
    }

    /**
     * @param driver -- can be null to use the last used WebDriver session.
     * @return String of browser name or null if there is no WebDriver session.
     * @see SelectBrowser#BROWSER_NAME_CHROME
     * @see SelectBrowser#BROWSER_NAME_FIREFOX
     * @see SelectBrowser#BROWSER_NAME_HTMLUNIT
     * @see SelectBrowser#BROWSER_NAME_IE
     * @see SelectBrowser#BROWSER_NAME_SAFARI
     */
    public static String getBrowserName(WebDriver driver) {
        if (driver == null)
            driver = getWebDriver();
        if (driver instanceof FirefoxDriver)
            return SelectBrowser.BROWSER_NAME_FIREFOX;
        if (driver instanceof ChromeDriver)
            return SelectBrowser.BROWSER_NAME_CHROME;
        if (driver instanceof InternetExplorerDriver)
            return SelectBrowser.BROWSER_NAME_IE;
        if (driver instanceof SafariDriver)
            return SelectBrowser.BROWSER_NAME_SAFARI;
        if (driver instanceof HtmlUnitDriver)
            return SelectBrowser.BROWSER_NAME_HTMLUNIT;
        if (driver instanceof RemoteWebDriver) {
            RemoteWebDriver rdriver = (RemoteWebDriver) driver;
            try {
                return rdriver.getCapabilities().getBrowserName();
            } catch (NullPointerException ignore) {
            }
        }
        return null;
    }

    /**
     * @param driver -- can be null to use last used webdriver session.
     * @return true if browser name matches BROWSER_NAME_HTMLUNIT
     * @see #getBrowserName(WebDriver)
     * @see SelectBrowser#BROWSER_NAME_HTMLUNIT
     */
    public static boolean isHtmlUnit(WebDriver driver) {
        return SelectBrowser.BROWSER_NAME_HTMLUNIT.equalsIgnoreCase(getBrowserName(driver));
    }

    /**
     * @param driver -- can be null to use last used webdriver session.
     * @return true if browser name matches BROWSER_NAME_SAFARI
     * @see #getBrowserName(WebDriver)
     * @see SelectBrowser#BROWSER_NAME_SAFARI
     */
    public static boolean isSafari(WebDriver driver) {
        return SelectBrowser.BROWSER_NAME_SAFARI.equalsIgnoreCase(getBrowserName(driver));
    }

    /**
     * @param driver -- can be null to use last used webdriver session.
     * @return true if browser name matches BROWSER_NAME_FIREFOX
     * @see #getBrowserName(WebDriver)
     * @see SelectBrowser#BROWSER_NAME_FIREFOX
     */
    public static boolean isFireFox(WebDriver driver) {
        return SelectBrowser.BROWSER_NAME_FIREFOX.equalsIgnoreCase(getBrowserName(driver));
    }

    /**
     * @param driver -- can be null to use last used webdriver session.
     * @return true if browser name matches BROWSER_NAME_IE
     * @see #getBrowserName(WebDriver)
     * @see SelectBrowser#BROWSER_NAME_IE
     */
    public static boolean isInternetExplorer(WebDriver driver) {
        return SelectBrowser.BROWSER_NAME_IE.equalsIgnoreCase(getBrowserName(driver));
    }

    /**
     * @param driver -- can be null to use last used webdriver session.
     * @return true if browser name matches BROWSER_NAME_CHROME
     * @see #getBrowserName(WebDriver)
     * @see SelectBrowser#BROWSER_NAME_CHROME
     */
    public static boolean isChrome(WebDriver driver) {
        return SelectBrowser.BROWSER_NAME_CHROME.equalsIgnoreCase(getBrowserName(driver));
    }

    /**
     * Load a SeBuilder JSON Script from the provided path so it can be started or run.
     * @param path -- full path to the JSON script.
     * @return Script
     * @throws FileNotFoundException -- if a provided path is null or otherwise invalid
     */
    public static Script getSeleniumBuilderScript(String path) throws FileNotFoundException {

        File source = new CaseInsensitiveFile(path).toFile();
        BufferedReader reader = FileUtilities.getUTF8BufferedFileReader(source.getAbsolutePath());
        WDScriptFactory factory = new WDScriptFactory();
        StepTypeFactory stepTypes = new StepTypeFactory();
        stepTypes.setSecondaryPackage(factory.SRSTEPTYPE_PACKAGE);
        factory.setStepTypeFactory(stepTypes);
        try {
            return factory.parse(reader, source).get(0);
        } catch (IOException x) {
            throw new FileNotFoundException("IOException reading or processing " + path + ", " + x.getMessage());
        }
    }

    /**
     * @return the "current" or "lastUsed" WebDriver as a WebDriverFactory usable by the
     * Selenium Builder Interpreter.
     */
    public static WebDriverFactory getWebDriverAsWebDriverFactory() {
        return (new WebDriverFactory() {
            public RemoteWebDriver make(HashMap<String, String> config) {
                return (RemoteWebDriver) WDLibrary.getWebDriver();
            }
        });
    }

    /**
     * Run a SeBuilder JSON Script.
     * @param path - fullpath to JSON script file.
     * <p>
     * @param log - An appropriate logging interface--such as the ApacheLogUtilities we instantiate
     * when launching our Selenium engine.
     * <p>
     * @param driverFactory -- if null, we will use a default Factory that does NOT instantiate a new
     * WebDriver but uses the "current" or "lastUsed" WebDriver ignoring any webDriverConfig that may
     * or may not be provided.
     * <p>
     * Consult the Selenium Interpreter documentation for any additional info on using the
     * <a href="http://github.com/sebuilder/se-builder/wiki/Se-Interpreter">Selenium Builder Interpreter</a>
     * if you are NOT intending to use the current or lastUsed WebDriver.
     * <p>
     * @param webDriverConfig -- can be null if driverFactory is null and we will be using an existing
     * WebDriver.
     * @param initialVars -- can be null.
     * @param allowClose -- set to false to prevent the script from closing the Driver.
     * @return
     * @throws FileNotFoundException
     * @throws RuntimeException
     */
    public static boolean runSeleniumBuilderScript(String path, org.apache.commons.logging.Log log,
            WebDriverFactory driverFactory, HashMap<String, String> webDriverConfig,
            Map<String, String> initialVars, boolean allowClose) throws FileNotFoundException, RuntimeException {

        Map<String, String> vars = initialVars == null ? new HashMap<String, String>() : initialVars;
        WebDriverFactory factory = getWebDriverAsWebDriverFactory();
        Script script = getSeleniumBuilderScript(path);
        script.closeDriver = allowClose;
        script.testRunFactory = new WDTestRunFactory();
        return script.run(log, factory, webDriverConfig, vars);
    }

    /**
     * Run a SeBuilder JSON Script explictly using the existing WebDriver instance.
     * <p>
     * @param path - fullpath to JSON script file.
     * <p>
     * @param log - An appropriate logging interface--such as the ApacheLogUtilities we instantiate
     * when launching our Selenium engine.
     * <p>
     * @param allowClose -- set to false to prevent the script from closing the Driver.
     * @return
     * @throws FileNotFoundException
     * @throws RuntimeException
     */
    public static boolean runSeleniumBuilderScript(String path, org.apache.commons.logging.Log log,
            boolean allowClose) throws FileNotFoundException, RuntimeException {

        return runSeleniumBuilderScript(path, log, null, null, null, allowClose);
    }

    /**
     * Attempt to SetFocus on the topmost window containing the WebElement.
     * @param element WebElement from which to get the proper RemoteWebDriver
     * @return boolean true if the window is focused.
     * @throws SeleniumPlusException if an error occured during the execution attempt.
     * @see #windowSetFocus(WebDriver)
     */
    public static boolean windowSetFocus(WebElement element) throws SeleniumPlusException {
        //we don't really need element as parameter for executing js code "window.top.focus();"
        //and if element is dynamically added by javascript and will be considered as stale
        //which will cause executeJavaScriptOnWebElement to throw SeleniumPlusException
        //executeJavaScriptOnWebElement("try{ window.top.focus();}catch(error){ debug(error); }", element);

        executeScript("try{ window.top.focus();}catch(error){ debug(error); }");
        return true;
    }

    /**
     * Attempt to SetFocus on the topmost window for the provided WebDriver.
     * @param adriver WebDriver from which to get the window title
     * @return boolean true if the window is focused.
     * @throws SeleniumPlusException if an error occured during the execution attempt.
     * @see #windowSetFocus(String)
     */
    public static boolean windowSetFocus(WebDriver adriver) throws SeleniumPlusException {
        boolean rc = false;
        IndependantLog.info("WDLibrary.windowSetFocus set focus via WebDriver window title");
        try {
            rc = windowSetFocus(adriver.getTitle());
        } catch (Exception x) {
            IndependantLog.info("WDLibrary.windowSetFocus RemoteWebElement ignoring " + getThrowableMessages(x)
                    + ": " + x.getMessage());
        }
        return rc;
    }

    /**
     * Set a specific Window matching the provided title to be the foreground window. 
     * @param titleRegExp -- title or regular expression matching the target window's caption.
     * @return true on success
     * @throws SeleniumPlusException
     * @see {@link NativeWrapper#SetForegroundWindow(String)}
     */
    public static boolean windowSetFocus(String titleRegExp) throws SeleniumPlusException {
        IndependantLog.info("WDLibrary.windowSetFocus set focus window title: " + titleRegExp);
        boolean rc = NativeWrapper.SetForegroundWindow(titleRegExp);
        if (!rc)
            throw new SeleniumPlusException("Failed to setfocus, return:" + rc);
        return rc;
    }

    /**
     * Attempt to SetFocus on the WebElement.
     * @param element WebElement, to get focus.
     * @return boolean true if the webelement is focused.
     * @throws SeleniumPlusException
     */
    public static boolean focus(WebElement element) throws SeleniumPlusException {
        boolean focused = false;
        if (element == null)
            return false;

        try {
            focused = windowSetFocus(element);
            if (!focused)
                IndependantLog.warn("WDLibrary.focus fail to set focus to top window.");
            else
                IndependantLog.info("WDLibrary.focus successfully set focus to top window.");
            new Actions(getWebDriver()).moveToElement(element).perform();
            if (WebDriverGUIUtilities.isTypeMatched(element, CFEditBox.LIBRARY_NAME)) {
                //Use Robot click to set focus. It is a little risky, it the click will invoke other reaction!
                IndependantLog.info("WDLibrary.focus using Click to set focus.");
                WDLibrary.click(element, null, null, WDLibrary.MOUSE_BUTTON_LEFT);
            } else {
                IndependantLog.info("WDLibrary.focus using sending empty String to set focus.");
                element.sendKeys("");
            }

        } catch (Exception e) {
            IndependantLog.warn(
                    "Fail to set focus on WebElement due to " + getThrowableMessages(e) + ": " + e.getMessage());
            focused = false;
        }
        return focused;
    }

    /**
     * Get css values of the object
     * @param element - WebElement
     * @return - return map as key and value pair
     */
    public static Map<String, String> getCssValues(WebElement element) {

        String[] styleattr = { "color", "display", "float", "font-family", "font-size", "font-weight", "height",
                "white-space", "width", "background-color", "background-repeat", "visibility" };

        Map<String, String> map = new HashMap<String, String>();
        String val = null;
        for (int i = 0; i < styleattr.length; i++) {
            try {
                val = element.getCssValue(styleattr[i]);
                map.put(styleattr[i], val);
            } catch (Throwable t) {
            }
        }
        return map;
    }

    /**
     * get the value of a property. The property can be an attribute, a css-attribute, a true property field, or certain property methods.
     * @param element WebElement, from which to retrieve the property
     * @param property String, the property name
     * @return String, the value of the property
     * @throws SeleniumPlusException if the attribute or property is not found.
     * @see #getProperties(WebElement)
     */
    public static String getProperty(WebElement element, String property) throws SeleniumPlusException {
        String value = null;
        String dbg = "WDLibrary.getProperty() ";
        try {
            try {
                value = element.getAttribute(property);
            } catch (Throwable x) {
                IndependantLog.debug(dbg + "getAttribute('" + property + "') threw " + x.getClass().getName() + ", "
                        + x.getMessage());
            }
            if (value == null) {
                IndependantLog.debug(dbg + "getAttribute('" + property + "') returned null.  Trying getCssValue.");
                try {
                    value = element.getCssValue(property);
                } catch (Throwable x) {
                    IndependantLog.debug(dbg + "getCssValue('" + property + "') threw " + x.getClass().getName()
                            + ", " + x.getMessage());
                }
                //for a non-exist css-property, SeleniumWebDriver will return "" instead of null
                if (value != null && value.isEmpty()) {
                    IndependantLog.debug(
                            dbg + "getCssValue('" + property + "') returned empty value. Resetting to null.");
                    value = null;
                }
            }
            if (value == null) {
                IndependantLog.debug(dbg + "trying wide getProperties() net.");
                Map<String, Object> props = getProperties(element);
                if (props.containsKey(property)) {
                    value = props.get(property).toString();
                } else {
                    IndependantLog.debug(dbg + "getProperties() reports no property named '" + property + "'.");
                    String keys = "";
                    for (String key : props.keySet())
                        keys += key + " ";
                    IndependantLog.debug(dbg + "propertyNames: " + keys);
                }
            }
            if (value == null) {
                IndependantLog.debug(dbg + "trying *NATIVE JS function* to get property '" + property + "'");
                StringBuffer script = new StringBuffer();
                script.append(JavaScriptFunctions.SAP.sap_getProperty(true, property));
                script.append("return sap_getProperty(arguments[0], arguments[1]);");
                Object result = null;
                try {
                    result = WDLibrary.executeJavaScriptOnWebElement(script.toString(), element, property);
                    if (result != null) {
                        IndependantLog.debug(dbg + " got '" + result + "' by *NATIVE JS function*.");
                        value = result.toString();
                        if (!(result instanceof String)) {
                            IndependantLog.warn(dbg + " the result is not String, it is "
                                    + result.getClass().getName() + ", may need more treatment.");
                        }
                    }
                } catch (SeleniumPlusException se) {
                    IndependantLog.error(dbg + StringUtils.debugmsg(se));
                }
            }
            if (value == null) {
                IndependantLog.error(
                        dbg + "got nothing but (null) for all getProperty attempts using '" + property + "'");
                throw new SeleniumPlusException(property + " not found.");
            }
            return value;
        } catch (Exception e) {
            IndependantLog.error(dbg + "caught " + e.getClass().getName() + ": " + e.getMessage());
            throw new SeleniumPlusException(property + " not found.",
                    SeleniumPlusException.CODE_PropertyNotFoundException);
        }
    }

    /**
     * Retrieve all properties of an element--attributes, css values, property fields and certain property methods.
     * @param element WebElement, from which to retrieve all properties
     * @return Map, a set of pair(property, value)
     * @throws SeleniumPlusException
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> getProperties(WebElement element) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);

        Map<String, Object> map = new HashMap<String, Object>();

        WDTimeOut.setImplicitlyWait(100, TimeUnit.MILLISECONDS, 1000);

        IndependantLog.debug(debugmsg + "calling getCssValues...");
        map.putAll(getCssValues(element));
        IndependantLog.debug(debugmsg + "returned from getCssValues...");
        //ADD other attributes by javascript
        StringBuffer jsScript = new StringBuffer();

        try {
            jsScript.append(JavaScriptFunctions.getAttributes());
            jsScript.append("return getAttributes(arguments[0]);\n");
            IndependantLog.debug(debugmsg + "DOM-attributes calling executeJavaScriptOnWebElement...");
            Object attributes = WDLibrary.executeJavaScriptOnWebElement(jsScript.toString(), element);
            IndependantLog
                    .debug(debugmsg + "receieved DOM-attributes Object from executeJavaScriptOnWebElement...");
            if (attributes instanceof Map) {
                IndependantLog.debug(debugmsg + "received attributes Object *IS* instanceof Map.");
                Map<String, Object> attributesMap = (Map<String, Object>) attributes;
                map.putAll(attributesMap);
            } else {
                if (attributes != null)
                    IndependantLog.debug(debugmsg + "received attributes object '" + attributes.getClass().getName()
                            + "', needs to handle in code.");
                else
                    IndependantLog.debug(debugmsg + "received attributes object is " + null);
            }
        } catch (Exception ignore) {
            IndependantLog.debug(debugmsg + " DOM-attributes, met " + StringUtils.debugmsg(ignore));
        }

        try {
            jsScript = new StringBuffer();
            jsScript.append(JavaScriptFunctions.getHtmlProperties());
            jsScript.append("return getHtmlProperties(arguments[0]);\n");
            IndependantLog.debug(debugmsg + "Htmlproperties calling executeJavaScriptOnWebElement...");
            Object properties = WDLibrary.executeJavaScriptOnWebElement(jsScript.toString(), element);
            IndependantLog.debug(debugmsg + "received Htmlproperties Object from executeJavaScriptOnWebElement.");
            if (properties instanceof Map) {
                IndependantLog.debug(debugmsg + "received properties Object *IS* instanceof Map.");
                try {
                    Map<String, ?> propertiesMap = (Map<String, ?>) properties;
                    map.putAll(propertiesMap);
                } catch (Throwable t) {
                    IndependantLog.debug(debugmsg + "returned properties object is " + null);
                }
            } else {
                if (properties != null)
                    IndependantLog.debug("returned properties object '" + properties.getClass().getName()
                            + "', needs to handle in code.");
                else
                    IndependantLog.debug(debugmsg + "returned properties object is " + null);
            }

        } catch (Exception ignore) {
            IndependantLog.debug(debugmsg + "Htmlproperties, met " + StringUtils.debugmsg(ignore));
        }

        WDTimeOut.resetImplicitlyWait(Processor.getSecsWaitForComponent(), TimeUnit.SECONDS);
        return map;
    }

    /**
     * Attempts to fire (dispatchEvent) a MouseEvent.
     * The MouseEvent should be suitable and contain all relevant and necessary information for the event.
     * This is typically used to re-fire a MouseEvent that might have been captured with DocumentClickCapture
     * but was not allowed to propogate to the actual element.
     * @param event
     * @throws SeleniumPlusException on an execution error.
     * @throws NullPointerException if required parameters in the MouseEvent are null.
     */
    public static void fireMouseEvent(MouseEvent event) throws SeleniumPlusException {
        if (event == null)
            throw new NullPointerException("fireMouseEvent event cannot be null.");
        if (event.EVENT_TARGET == null)
            throw new NullPointerException("fireMouseEvent event.EVENT_TARGET cannot be null.");

        StringBuffer jsScript = new StringBuffer();
        jsScript.append(JavaScriptFunctions.fireMouseEvent(event));
        jsScript.append("fireMouseEvent(arguments[0],arguments[1]);\n");
        IndependantLog.info("WDLibrary.fireMouseEvent firing...");
        WDLibrary.executeJavaScriptOnWebElement(jsScript.toString(), event.EVENT_TARGET, event.EVENT_VIEW);
    }

    /**An instance of WD_XMLHttpRequest, used to send HTTP request.*/
    public static WD_XMLHttpRequest AJAX = new WD_XMLHttpRequest();

    /**
     * Implements {@link XMLHttpRequest}, through {@link WDLibrary} to execute javascript
     * {@link JavaScriptFunctions#sendHttpRequest(Map)} to handle HTTP Request. If the execution
     * is asynchronous, some value of {@link Key} will be stored in javascript global variables,
     * and they can be retrieved later.
     * 
     * @see org.safs.net.XMLHttpRequest
      */
    public static class WD_XMLHttpRequest extends XMLHttpRequest {

        /**
         * Implementation: through {@link WDLibrary}, execute {@link JavaScriptFunctions#sendHttpRequest(Map)}.<br>
         * For synchronous execution, all results will be stored in the returned Map result with one value of {@link Key};<br>
         * For asynchronous execution, except {@link Key#READY_STATE}, the other values will not be in the returned Map result,
         * they will be stored in javascript global variables:<br>
         * <pre>
         *   {@link #VARIABLE_STATUS}:            HTTP response status number
         *   {@link #VARIABLE_STATUS_TEXT}:       HTTP response status text
         *   {@link #VARIABLE_RESPONSE_HEADERS}:  HTTP response headers
         *   {@link #VARIABLE_RESPONSE_TEXT}:     HTTP response as string
         *   {@link #VARIABLE_RESPONSE_XML}:      HTTP response as XML data
         * </pre>
         * 
         * @throws SeleniumPlusException
         * @see {@link #getReadyState()}
         */
        @SuppressWarnings({ "unchecked" })
        public Map<String, Object> execute(HttpCommand command, String url, boolean async,
                Map<String, String> headers, String data) throws SeleniumPlusException {
            if (!StringUtils.isValid(url))
                throw new SeleniumPlusException("The request url is null or is empty!");

            String debugmsg = StringUtils.debugmsg(false);

            String parameters = "" + (data == null ? "" : "with data '" + data + "'")
                    + ((headers == null || headers.isEmpty()) ? "" : " with request headers " + headers);
            IndependantLog.info(debugmsg + " performing '" + command.value() + "' on '" + url + "' " + parameters);

            Object result = null;
            StringBuffer jsScript = new StringBuffer();
            jsScript.append(JavaScriptFunctions.sendHttpRequest(headers));
            //jsScript.append(" return sendHttpRequest(url , command, async, data, headers);\n");
            jsScript.append(
                    " return sendHttpRequest(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4]);\n");
            //TODO Do we need to encode the url and form-data?
            //      url = StringUtils.urlEncode(url);
            //      data = StringUtils.urlEncode(data);
            if (data == null) {
                result = executeScript(jsScript.toString(), url, command.value(), async);
            } else {
                result = executeScript(jsScript.toString(), url, command.value(), async, data);
            }

            if (async) {
                IndependantLog.debug(debugmsg
                        + " the http request is executed aynchronously. We should not expect any result immediately.");
            }
            if (result == null) {
                IndependantLog.warn(debugmsg + " the response is null!");
                return null;
            }

            if (result instanceof Map) {
                return (Map<String, Object>) result;
            } else {
                IndependantLog
                        .warn(debugmsg + " the returned result is not a Map! Need modify source code to parse it!");
                return null;
            }
        }

        /**
         * Get javascript global variable {@link #VARIABLE_READY_STATE}.
         */
        public String getReadyState() {
            return String.valueOf(js_getGlobalVariable(VARIABLE_READY_STATE));
        }

        /**
         * Get javascript global variable {@link #VARIABLE_RESPONSE_HEADERS}.
         */
        public Object getResponseHeaders() {
            return js_getGlobalVariable(VARIABLE_RESPONSE_HEADERS);
        }

        /**
         * Get javascript global variable {@link #VARIABLE_RESPONSE_TEXT}.
         */
        public String getResponseText() {
            return String.valueOf(js_getGlobalVariable(VARIABLE_RESPONSE_TEXT));
        }

        /**
         * Get javascript global variable {@link #VARIABLE_RESPONSE_XML}.
         */
        public Object getResponseXml() {
            return js_getGlobalVariable(VARIABLE_RESPONSE_XML);
        }

        /**
         * Get javascript global variable {@link #VARIABLE_STATUS}.
         */
        public String getHttpStatus() {
            return String.valueOf(js_getGlobalVariable(VARIABLE_STATUS));
        }

        /**
         * Get javascript global variable {@link #VARIABLE_STATUS_TEXT}.
         */
        public String getHttpStatusText() {
            return String.valueOf(js_getGlobalVariable(VARIABLE_STATUS_TEXT));
        }
    }

    /**
     * @return String, the name of the browser where test is running. Or null if something wrong happens.
     */
    public static String getBrowserName() {
        WebDriver wd = WDLibrary.getWebDriver();
        if (wd instanceof RemoteDriver) {
            return ((RemoteDriver) wd).getBrowserName();
        }
        return null;
    }

    /**
     * @return String, the version of the browser where test is running. Or null if something wrong happens.
     */
    public static String getBrowserVersion() {
        WebDriver wd = WDLibrary.getWebDriver();
        if (wd instanceof RemoteDriver) {
            return ((RemoteDriver) wd).getBrowserVersion();
        }
        return null;
    }

    /**
     * @return String, the name of the platform where the browser is running. Or null if something wrong happens.
     */
    public static String getPlatform() {
        WebDriver wd = WDLibrary.getWebDriver();
        if (wd instanceof RemoteDriver) {
            return ((RemoteDriver) wd).getPlatform();
        }
        return null;
    }

    /**
     * @return String, the version of 'selenium server' with which the test is running. Or null if something wrong happens.
     */
    public static String getDriverVersion() {
        WebDriver wd = WDLibrary.getWebDriver();
        if (wd instanceof RemoteDriver) {
            return ((RemoteDriver) wd).getDriverVersion();
        }
        return null;
    }

    /**
     * Check some known issue for a certain keyword.<br>
     * @param keyword String, the keyword to check for, such like "GetURL"
     * @throws SeleniumPlusException will be thrown out if a known issue is checked.
     */
    public static void checkKnownIssue(String keyword) throws SeleniumPlusException {

        if (DDDriverCommands.GETURL_KEYWORD.equalsIgnoreCase(keyword)
                || DDDriverCommands.SAVEURLTOFILE_KEYWORD.equalsIgnoreCase(keyword)
                || DDDriverCommands.VERIFYURLCONTENT_KEYWORD.equalsIgnoreCase(keyword)
                || DDDriverCommands.VERIFYURLTOFILE_KEYWORD.equalsIgnoreCase(keyword)) {
            //Check the known issue with selenium-standalone2.47.1 and Firefox 42.0
            //These keywords will be skipped for FireFox until we find the reason why 'AJAX execution is stuck with FireFox'.
            if (SelectBrowser.BROWSER_NAME_FIREFOX.equalsIgnoreCase(getBrowserName())
            //            && "42.0".equals(getBrowserVersion())
            //            && "2.47.1".equals(getDriverVersion())
            ) {
                //            throw new SeleniumPlusException("For keyword '"+keyword+"': known issue with selenium-standalone2.47.1 and Firefox ");
                throw new SeleniumPlusException(
                        "For keyword '" + keyword + "': execution stuck : known issue with Firefox!");
            }
        }
    }

    /**
     * Click a component with an offset. This API will not verify that the click does happen.<br>
     * If you want to make sure of that, please call {@link #click(WebElement, Point)} instead.<br>
     * <br>
     * Sometimes we want to click without verification, for example, to show an Alert.<br> 
     * With presence of Alert, any call to Selenium API will throw out UnhandledAlertException<br>
     * and close the Alert automatically. Our API {@link #click(WebElement, Point)} will call<br>
     * Selenium API for verification, so it is unable to open the Alert successfully.<br>  
     * 
     * @param component WebElement, the component to click
     * @param offset Point, the offset (relative to component) to click at
     * @param optional String[], the optional parameters
     * <ul>
     * <li> optional[0] autoscroll boolean, if the component will be scrolled into view automatically before clicking.
     *                                      if not provided, the default value is true.
     * </ul>
     * @return boolean true if succeed.
     * @see #click(WebElement, Point)
     */
    public static boolean clickUnverified(WebElement component, Point offset, String... optional) {
        String debugmsg = StringUtils.debugmsg(false);

        try {
            IndependantLog.debug(debugmsg + " click with parameter componet:" + component + ", offset:" + offset);
            //Create a combined actions according to the parameters
            Actions actions = new Actions(getWebDriver());

            boolean autoscroll = parseAutoScroll(optional);
            if (autoscroll) {
                if (offset != null)
                    actions.moveToElement(component, offset.x, offset.y);
                else
                    actions.moveToElement(component);
            }
            IndependantLog.debug(debugmsg + " Try Selenium API to click.");
            actions.click().perform();

            return true;
        } catch (Exception e) {
            IndependantLog.warn(
                    debugmsg + " Failed with Selenium API, met " + StringUtils.debugmsg(e) + ". Try Robot click.");

            try {
                Point p = WDLibrary.getScreenLocation(component);

                if (offset != null)
                    p.translate(offset.x, offset.y);
                else
                    p.translate(component.getSize().width / 2, component.getSize().height / 2);

                RBT.click(p, null, WDLibrary.MOUSE_BUTTON_LEFT, 1);

                return true;
            } catch (Exception e1) {
                IndependantLog.error(debugmsg + " Failed with Robot click!");
            }
            return false;
        }
    }

    /**
     * Close the Alert-Modal-Dialog associated with a certain browser identified by ID.<br>
     * It will get the cached webdriver according to the browser's id, and close the 'alert' through that webdriver.<br>
     * <b>Note:</b>This API will NOT change the current WebDriver. {@link #getWebDriver()} will still return the same object.<br>
     * 
     * @param accept boolean, if true then accept (click OK) the alert; otherwise dismiss (click Cancel) the alert.
     * @param optionals String
     * <ul>
     * <b>optionals[0] timeoutWaitAlertPresence</b> int, timeout in seconds to wait for the presence of Alert.
     *                                                   If not provided, default is 2 seconds.<br>
     * <b>optionals[1] browserID</b> String, the ID to get the browser on which the 'alert' will be closed.
     *                                       If not provided, the current browser will be used.<br>
     * </ul>
     * @throws SeleniumPlusException, if WebDriver cannot be got according to the parameter browserID.<br>
     *                                if Alert is not present with timeout.<br>
     */
    public static void closeAlert(boolean accept, String... optionals) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        String browserID = null;

        if (optionals != null && optionals.length > 0) {
            if (optionals.length > 1 && StringUtils.isValid(optionals[1]))
                browserID = optionals[1];
        }

        try {
            Alert alert = waitAlert(optionals);

            if (accept) {
                alert.accept();//OK button
            } else {
                alert.dismiss();//Cancel button
            }
        } catch (Exception e) {
            String message = "Fail to " + (accept ? "accept" : "dismiss") + " alert dialog associated with "
                    + (browserID == null ? "current browser." : "browser '" + browserID + "'.");
            IndependantLog.error(debugmsg + message + " due to " + StringUtils.debugmsg(e));
            throw new SeleniumPlusException(message);
        }
    }

    /**
     * 
     * Test the presence of Alert-Modal-Dialog associated with a certain browser identified by ID.<br>
     * It will get the cached webdriver according to the browser's id, and get the 'alert' through that webdriver.<br>
     * <b>Note:</b>This API will NOT change the current WebDriver. {@link #getWebDriver()} will still return the same object.<br>
     * 
     * @param optionals String...
     * <ul>
     * <b>optionals[0] timeoutWaitAlertPresence</b> int, timeout in seconds to wait for the presence of Alert.
     *                                                   If not provided, default is 2 seconds.<br>
     *                                                   If it is provided as {@link #TIMEOUT_NOWAIT}, this method will try to get Alert without waiting.<br>
     * <b>optionals[1] browserID</b> String, the ID to get the browser on which the 'alert' will be closed.
     *                                       If not provided, the current browser will be used.<br>
     * </ul>
     * @return boolean, true if the Alert is present.
     * @throws SeleniumPlusException if the WebDriver is null
     * @see #waitAlert(String...)
     */
    public static boolean isAlertPresent(String... optionals) throws SeleniumPlusException {
        String debugmsg = "WDLibrary.isAlertPresent(): ";
        try {
            if (waitAlert(optionals) != null) {
                return true;
            } else {
                IndependantLog.debug(debugmsg + "Failed to wait for the Alert. A null object was returned.");
            }
        } catch (SeleniumPlusException e) {
            IndependantLog.warn(debugmsg + "Failed to wait for the Alert. Met " + StringUtils.debugmsg(e));
            //If we didn't get the webdriver, then we will throw the exception out.
            if (SeleniumPlusException.CODE_OBJECT_IS_NULL.equals(e.getCode()))
                throw e;
        }
        return false;
    }

    /**
     * Wait for the presence of Alert-Modal-Dialog associated with a certain browser identified by ID.<br>
     * It will get the cached webdriver according to the browser's id, and get the 'alert' through that webdriver.<br>
     * <b>Note:</b>This API will NOT change the current WebDriver. {@link #getWebDriver()} will still return the same object.<br>
     * 
     * @param optionals String...
     * <ul>
     * <b>optionals[0] timeoutWaitAlertPresence</b> int, timeout in seconds to wait for the presence of Alert.
     *                                                   If not provided, default is 2 seconds.<br>
     *                                                   If it is provided as {@link #TIMEOUT_NOWAIT}, this method will try to get Alert without waiting.<br>
     * <b>optionals[1] browserID</b> String, the ID to get the browser on which the 'alert' will be closed.
     *                                       If not provided, the current browser will be used.<br>
     * </ul>
     * @return Alert, the current Alert on browser; it could be null if it is not present within timeout.
     * @throws SeleniumPlusException, if WebDriver cannot be got according to the parameter browserID.<br>
     *                                if Alert is not present within timeout.<br>
     */
    private static Alert waitAlert(String... optionals) throws SeleniumPlusException {
        String debugmsg = StringUtils.debugmsg(false);
        String browserID = null;
        int timeout = timeoutWaitAlert;
        Alert alert = null;

        if (optionals != null && optionals.length > 0) {
            if (StringUtils.isValid(optionals[0])) {
                try {
                    timeout = Integer.parseInt(optionals[0]);
                } catch (NumberFormatException e) {
                }
            }
            if (optionals.length > 1 && StringUtils.isValid(optionals[1]))
                browserID = optionals[1];
        }

        WebDriver webdriver = getWebDriver(browserID);

        if (webdriver == null) {
            throw new SeleniumPlusException("cannot get webdriver according to id '" + browserID + "'",
                    SeleniumPlusException.CODE_OBJECT_IS_NULL);
        }

        try {
            if (timeout == TIMEOUT_NOWAIT) {
                alert = webdriver.switchTo().alert();//NoAlertPresentException
            } else {
                WebDriverWait wait = new WebDriverWait(webdriver, timeout);
                alert = wait.until(ExpectedConditions.alertIsPresent());//TimeoutException
            }

            return alert;
        } catch (Exception e) {
            String message = "Fail to get alert dialog associated with "
                    + (browserID == null ? "current browser." : "browser '" + browserID + "'.");
            IndependantLog.warn(debugmsg + message + " due to " + StringUtils.debugmsg(e));
            throw new SeleniumPlusException(message);
        }
    }

    /**
     * <b>Before running this test, the Selenium Sever should have already started</b> (it can be launched by "java org.safs.selenium.webdriver.lib.RemoteDriver" ).<br>
     * 
     * @see RemoteDriver#main(String[])
     */
    private static void test_ajax_call(String browser) {
        final String debugmsg = StringUtils.debugmsg(false);
        String url = "http://www.thomas-bayer.com/";
        final String ID = "thomas";
        int timeout = 10;
        boolean isRemote = true;

        final String ajaxRequestURL = "http://www.thomas-bayer.com/sqlrest/";
        final Map<String, String> headers = new HashMap<String, String>();
        final Map<String, Object> resultMap = new HashMap<String, Object>();
        final AtomicBoolean resultReady = new AtomicBoolean(false);
        //Open the URL by Selenium, on which page the AJAX request will be sent out
        try {
            System.out.println(debugmsg + " launching page '" + url + "' in browser '" + browser + "'.");
            WDLibrary.startBrowser(browser, url, ID, timeout, isRemote);

            checkKnownIssue(DDDriverCommands.GETURL_KEYWORD);

            Thread threadGetUrl = new Thread(new Runnable() {
                public void run() {
                    try {
                        //For firefox42.0 with selenium2.47.1, following call will block for ever
                        Map<String, Object> results = WDLibrary.AJAX.getURL(ajaxRequestURL, headers);
                        for (String key : results.keySet()) {
                            resultMap.put(key, results.get(key));
                        }
                        resultReady.set(true);
                    } catch (Throwable e) {
                        System.err.println(debugmsg + " AJAX.getURL Thread: Met " + StringUtils.debugmsg(e));
                    }
                }
            });
            threadGetUrl.setDaemon(true);
            threadGetUrl.start();
            System.out.println(debugmsg + "Waitting for the response from ajax request.");
            threadGetUrl.join(10 * 1000);
            if (!resultReady.get()) {
                System.err.println("Cannot get result ready from url '" + ajaxRequestURL + "'");
            } else {
                String content = String.valueOf(resultMap.get(Key.RESPONSE_TEXT.value()));
                System.out.println(debugmsg + " Got http response\n" + content);
            }
        } catch (Exception e) {
            System.err.println(debugmsg + " Met " + StringUtils.debugmsg(e));
        } finally {
            Thread threadGetUrl = new Thread(new Runnable() {
                public void run() {
                    try {
                        //For firefox42.0 with selenium2.47.1, following call will block for ever
                        WDLibrary.stopBrowser(ID);
                        System.out.println(debugmsg + " page '" + ID + "' has been stopped.\n");
                    } catch (Throwable e) {
                        System.err.println(debugmsg + " Stop browser: Met " + StringUtils.debugmsg(e));
                    }
                }
            });
            threadGetUrl.setDaemon(true);
            threadGetUrl.start();
        }
    }

    private static void test_ajax_call() {
        String[] browsers = { SelectBrowser.BROWSER_NAME_CHROME, SelectBrowser.BROWSER_NAME_IE,
                SelectBrowser.BROWSER_NAME_FIREFOX };
        for (String browser : browsers) {
            test_ajax_call(browser);
        }
    }

    /**
     * Before calling this method, we could start chromedriver.exe firstly.
     */
    private static void test_kill_chromedirver() {
        try {
            String host = "";
            List<ProcessInfo> killedList = killChromeDriver(host);
            for (ProcessInfo p : killedList) {
                System.out.println("on host " + host + ", process " + p.getId()
                        + " has been terminated. The return code is " + p.getWmiTerminateRC());
            }

            host = "tadsrv";
            killedList = killChromeDriver(host);
            for (ProcessInfo p : killedList) {
                System.out.println("on host " + host + ", process " + p.getId()
                        + " has been terminated. The return code is " + p.getWmiTerminateRC());
            }

        } catch (SAFSException e) {
            e.printStackTrace();
        }

    }

    /**
     * This class represents a Robot, which can do work locally (thru local org.safs.robot.Robot) or remotely (thru a RMI agent).<br>
     * 
     * @author sbjlwa
     * 
     */
    public static class RBT {
        /**
         * Use Robot to click locally or remotely (through RMI).
         * Use the current WebDriver as a RemoteDriver to provide an RMI agent.
         * 
         * @param location   Point, The screen location to click at.
         * @param specialKey   Keys, The selenium Keys value, representing the key (such as Ctrl, Alt) pressed during mouse click.
         * @param mouseButtonNumber   int, Representing the mouse button to click, such as {@link WDLibrary#MOUSE_BUTTON_LEFT}.
         * @param nclicks   int, How many times to click mouse.
         * 
         * @throws Exception
         */
        protected static void click(Point location, Keys specialKey, int mouseButtonNumber, int nclicks)
                throws Exception {
            WebDriver wd = getWebDriver();
            RemoteDriver rd = (wd instanceof RemoteDriver) ? (RemoteDriver) wd : null;
            click(rd, location, specialKey, mouseButtonNumber, nclicks);
        }

        /**
         * Use Robot to click locally or remotely (through RMI).
         * 
         * @param rd   RemoteDriver, The Remote Driver with an RMI agent.
         * @param location   Point, The screen location to click at.
         * @param specialKey   Keys, The selenium Keys value, representing the key (such as Ctrl, Alt) pressed during mouse click.
         * @param mouseButtonNumber   int, Representing the mouse button to click, such as {@link WDLibrary#MOUSE_BUTTON_LEFT}.
         * @param nclicks   int, How many times to click mouse.
         * 
         * @throws Exception
         */
        public static void click(RemoteDriver rd, Point location, Keys specialKey, int mouseButtonNumber,
                int nclicks) throws Exception {
            if (rd == null || rd.isLocalServer()) {
                if (specialKey == null) {
                    org.safs.robot.Robot.click(location.x, location.y, mouseButtonNumber, nclicks);
                } else {
                    org.safs.robot.Robot.clickWithKeyPress(location.x, location.y, mouseButtonNumber,
                            toJavaKeyCode(specialKey), nclicks);
                }
            } else {// handle remote selenium
                if (rd.rmiAgent != null) {
                    if (specialKey == null) {
                        rd.rmiAgent.remoteClick(location.x, location.y, mouseButtonNumber, nclicks);
                    } else {
                        rd.rmiAgent.remoteClick(location.x, location.y, toJavaKeyCode(specialKey), nclicks);
                    }
                } else {
                    throw new ServerException("RMI Agent is not available.");
                }
            }
        }
    }

    /**
     * Before running this method, please read java doc of {@link #test_ajax_call(String)}
     * @param args
     */
    public static void main(String[] args) {
        test_ajax_call();
        test_kill_chromedirver();
    }
}