org.paxml.selenium.rc.XSelenium.java Source code

Java tutorial

Introduction

Here is the source code for org.paxml.selenium.rc.XSelenium.java

Source

/**
 * This file is part of PaxmlSelenium.
 *
 * PaxmlSelenium is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PaxmlSelenium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with PaxmlSelenium.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.paxml.selenium.rc;

import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.KeyStroke;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.server.RemoteControlConfiguration;
import org.openqa.selenium.server.SeleniumServer;
import org.paxml.core.Context;
import org.paxml.core.Context.Stack.IStackTraverser;
import org.paxml.core.IEntity;
import org.paxml.core.PaxmlRuntimeException;
import org.paxml.tag.ITag;

import com.thoughtworks.selenium.DefaultSelenium;

/**
 * Add extensions support for Selenium.
 * 
 * @author Xuetao Niu
 */
public class XSelenium {
    private final DefaultSelenium selenium;
    private volatile SeleniumServer seleniumServer;

    /**
     * Different browser start command supported by Selenium RC Server.
     */
    private enum BrowserStrings {
        IEXPLORE {
            /**
             * {@inheritDoc}
             */

            public String toString() {
                return "*iexplore";
            }
        },
        FIREFOX {
            /**
             * {@inheritDoc}
             */

            public String toString() {
                return "*firefox";
            }
        },
        CHROME {
            /**
             * {@inheritDoc}
             */

            public String toString() {
                return "*chrome";
            }
        },
        GOOGLECHROME {
            /**
             * {@inheritDoc}
             */

            public String toString() {
                return "*googlechrome";
            }
        }
    }

    private static enum Keys {
        SNAPSHOTS
    }

    public static class CallStack {
        private IEntity entity;
        private ITag tag;

        public IEntity getEntity() {
            return entity;
        }

        public void setEntity(IEntity entity) {
            this.entity = entity;
        }

        public ITag getTag() {
            return tag;
        }

        public void setTag(ITag tag) {
            this.tag = tag;
        }

    }

    public static class SnapshotInfo {
        private File file;
        private List<CallStack> callStack;

        public File getFile() {
            return file;
        }

        public void setFile(File file) {
            this.file = file;
        }

        public List<CallStack> getCallStack() {
            if (callStack == null) {
                callStack = new ArrayList<CallStack>();
            }
            return callStack;
        }

        public void setCallStack(List<CallStack> callStack) {
            this.callStack = callStack;
        }

    }

    /**
     * Warning keyword.
     */
    public static final String WARNING = "WARNING ";
    /**
     * Error keyword.
     */
    public static final String ERROR = "ERROR ";
    private static final Set<String> SCREENSHOTS_CAPABLE_BROWSERS;

    static {
        Set<String> tmp = new HashSet<String>();
        tmp.add(BrowserStrings.FIREFOX.toString());
        tmp.add(BrowserStrings.CHROME.toString());
        SCREENSHOTS_CAPABLE_BROWSERS = Collections.unmodifiableSet(tmp);
    }

    public static final long WAIT_FOR_AJAX_START_TIMEOUT = 1000;

    private static final Log log = LogFactory.getLog(XSelenium.class);

    /**
     * Used by the framework for translating characters into key codes as used
     * by {@link java.awt.event.KeyEvent}.
     */
    private static final Map<String, String> KEY_CODES_MAP;
    /**
     * Used by the framework for translating characters into key codes as used
     * by {@link java.awt.event.KeyEvent}.
     */
    private static final Map<String, Integer> CHAR_TO_KEY_CODE_MAP;
    static {
        Map<String, String> tmp = new HashMap<String, String>();
        tmp.put("!", "1");
        tmp.put("@", "2");
        tmp.put("#", "3");
        tmp.put("$", "4");
        tmp.put("%", "5");
        tmp.put("^", "6");
        tmp.put("&", "7");
        tmp.put("*", "8");
        tmp.put("(", "9");
        tmp.put(")", "10");
        tmp.put("+", "=");
        tmp.put("_", "-");
        tmp.put(":", ";");
        tmp.put("\"", "'");
        tmp.put("|", "\\");
        tmp.put("?", "/");
        KEY_CODES_MAP = Collections.unmodifiableMap(tmp);

        Map<String, Integer> tmp2 = new HashMap<String, Integer>();
        try {
            tmp2.put("-", KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0).getKeyCode());
            tmp2.put("=", KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, 0).getKeyCode());
            tmp2.put(";", KeyStroke.getKeyStroke(KeyEvent.VK_SEMICOLON, 0).getKeyCode());
            tmp2.put(",", KeyStroke.getKeyStroke(KeyEvent.VK_COMMA, 0).getKeyCode());
            tmp2.put(".", KeyStroke.getKeyStroke(KeyEvent.VK_PERIOD, 0).getKeyCode());
            tmp2.put(" ", KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0).getKeyCode());
            tmp2.put("'", KeyStroke.getKeyStroke(KeyEvent.VK_QUOTE, 0).getKeyCode());
            tmp2.put("\\", KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SLASH, 0).getKeyCode());
            tmp2.put("/", KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, 0).getKeyCode());
        } catch (Throwable ex) {
            log.fatal(ex);
        }
        CHAR_TO_KEY_CODE_MAP = Collections.unmodifiableMap(tmp2);
    }

    private boolean ajaxAware = true;
    private long waitForTimeout = 5000;
    private long waitForReloadTimeout = 60000;
    private long waitForCheckInterval = 1000;
    private long ajaxTimeout = 60000;
    private String browserStartCommand = "";
    private boolean errorClosing;

    private boolean initialized;
    private boolean terminated;
    private String snapshotsPath;

    /**
     * Ceate Selenium instance.
     * 
     * @param serverHost
     *            selenium host
     * @param serverPort
     *            selenium port
     * @param browserStartCommand
     *            which browser to use
     * @param browserURL
     *            the url
     */
    public XSelenium(String serverHost, int serverPort, String browserStartCommand, String browserURL) {

        if (StringUtils.isBlank(serverHost)) {
            // start the selenium server programmatically

            synchronized (XSelenium.class) {
                if (seleniumServer == null) {
                    seleniumServer = startServer(serverPort);
                }
                serverPort = seleniumServer.getPort();
            }

        }
        if (StringUtils.isBlank(serverHost)) {
            serverHost = "localhost";
        }
        selenium = new DefaultSelenium(serverHost, serverPort, browserStartCommand, browserURL);

        this.browserStartCommand = browserStartCommand;
    }

    private static int getAvailablePort() {
        ServerSocket s = null;

        try {
            s = new ServerSocket(0);
            return s.getLocalPort();
        } catch (IOException e) {
            throw new PaxmlRuntimeException(e);
        } finally {
            try {
                if (s != null) {
                    s.close();
                }
            } catch (Exception e) {
                log.warn("Cannot close server socket of port: " + s.getLocalPort(), e);
            }
        }

    }

    public static SeleniumServer startServer(int port) {

        RemoteControlConfiguration rcc = new RemoteControlConfiguration();
        if (port <= 0) {
            port = getAvailablePort(); // RemoteControlConfiguration.DEFAULT_PORT;
        }
        rcc.setPort(port);
        SeleniumServer server;
        try {
            server = new SeleniumServer(false, rcc);
        } catch (Exception e1) {
            throw new PaxmlRuntimeException("Cannot create embedded selenium server", e1);
        }

        try {
            server.start();
            log.info("Embedded Selenium server started at port " + port);
        } catch (Exception e) {

            throw new PaxmlRuntimeException("Cannot start embedded selenium server at port: " + port, e);
        }
        return server;

    }

    public String getBrowserStartCommand() {
        return browserStartCommand;
    }

    public void setSnapshotsPath(String snapshotsPath) {
        this.snapshotsPath = snapshotsPath;
    }

    public boolean isInitialized() {
        return initialized;
    }

    public boolean isSnapshotSupported() {
        return SCREENSHOTS_CAPABLE_BROWSERS.contains(browserStartCommand);
    }

    /**
     * Make screenshot of the AUT to png file.
     * 
     * @param event
     *            the name of the event.
     * @param remote
     *            true to take remote snapshots, false local.
     * @return the snapshot file, null if not taken
     */
    public File takeSnapshot(Context context) {

        if (isSnapshotSupported()) {
            final String snapshotFolder = getSnapshotsPath();
            if (snapshotFolder == null) {
                if (log.isErrorEnabled()) {
                    log.error("Snapshot folder not given in property: " + SeleniumTag.SELENIUM_SNAPSHOT_FOLDER);
                }
                return null;
            }
            final String entity = context.getProcessId() + "-" + context.getCurrentEntity().getResource().getName();
            final String fn = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS").format(new Date());
            File dir = new File(snapshotFolder);
            dir.mkdirs();
            File file = new File(dir, entity + "." + fn + ".png");
            for (int i = 1; file.exists(); i++) {
                file = new File(dir, entity + "." + fn + "." + i + ".png");
            }
            final String base64 = selenium.captureEntirePageScreenshotToString("");
            try {
                FileUtils.writeByteArrayToFile(file, Base64.decodeBase64(base64.getBytes()));
                addSnapshot(context, file);
            } catch (IOException e) {
                throw new PaxmlRuntimeException("Cannot save snapshot to file: " + file.getAbsolutePath(), e);
            }
            if (log.isInfoEnabled()) {
                log.info("Remote snapshot taken and saved to local file: " + file.getAbsolutePath());
            }

            return file;
        } else {

            if (log.isWarnEnabled()) {
                log.warn("Snapshot cannot be taken upon error"
                        + " because the current browser does not support taking snapshots: "
                        + getBrowserStartCommand());
            }

        }

        return null;
    }

    /**
     * Should not be called by paxml.
     * 
     * @param url
     *            the url
     */
    void open(String url) {
        terminated = false;
        if (!initialized) {
            start();
        }
        selenium.open(url);
    }

    public boolean isTerminated() {
        return terminated;
    }

    public void terminate() {
        if (terminated) {
            return;
        }
        try {
            selenium.close();
        } catch (Exception e) {
            log.warn("Error closing selenium", e);
        } finally {
            try {
                selenium.stop();
            } catch (Exception e) {
                log.warn("Error stopping selenium", e);
            } finally {
                try {
                    if (seleniumServer != null && seleniumServer.getServer().isStarted()) {
                        seleniumServer.stop();
                    }
                } catch (Exception e) {
                    log.warn("Error stopping selenium server", e);
                }
            }
        }

        terminated = true;
        if (log.isInfoEnabled()) {
            log.info("Selenium session terminated: " + this);
        }
    }

    /**
     * Should not be called by paxml.
     */
    void close() {

        errorClosing = false;
        if (log.isDebugEnabled()) {
            log.debug("Attempt to close selenium");
        }
        if (initialized) {
            if (log.isDebugEnabled()) {
                log.debug("Closing selenium");
            }
            try {
                selenium.close();
            } catch (Exception e) {
                errorClosing = true;
                log.warn("Error closing selenium", e);
            }
        }
    }

    /**
     * Should not be called by paxml.
     */
    void stop() {
        if (errorClosing) {
            if (log.isWarnEnabled()) {
                log.warn("Skipping stopping selenium due to error in closing");
            }
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug("Attempt to stop selenium");
        }
        if (initialized) {
            initialized = false;
            if (log.isDebugEnabled()) {
                log.debug("Stopping selenium");
            }
            try {
                selenium.stop();
            } catch (Exception e) {
                log.warn("Error stopping selenium", e);
            }
        }
    }

    public long getAjaxTimeout() {
        return ajaxTimeout;
    }

    public void setAjaxTimeout(long ajaxTimeout) {
        this.ajaxTimeout = ajaxTimeout;
    }

    public boolean isAjaxAware() {
        if (!ajaxAware) {
            if (log.isInfoEnabled()) {
                log.info("The current page is not ajax aware.");
            }
        }
        return ajaxAware;
    }

    public void setAjaxAware(boolean ajaxAware) {
        this.ajaxAware = ajaxAware;
    }

    /**
     * Should not be called by paxml
     */
    void start() {
        selenium.start();
        if (this.browserStartCommand.equals("*iexplore")) {
            log.debug("We are running explorer, lets use javascript-xpath");
            selenium.useXpathLibrary("javascript-xpath");
        }
        initialized = true;
    }

    static String[] getKeyCodes(char ch) {
        boolean shiftRequired = false;
        String key = String.valueOf(ch);
        String value = KEY_CODES_MAP.get(key);

        if (value != null) {
            shiftRequired = true;
            key = value;
        } else if (Character.isUpperCase(key.charAt(0))) {
            shiftRequired = true;
        } else {
            key = key.toUpperCase();
        }

        KeyStroke ks = KeyStroke.getKeyStroke("pressed " + key.toUpperCase());
        int keyCode = 0;
        if (ks != null && ks.getKeyCode() != 0) {
            keyCode = ks.getKeyCode();
        } else {
            keyCode = CHAR_TO_KEY_CODE_MAP.get(key);
        }

        return shiftRequired ? new String[] { String.valueOf(KeyEvent.VK_SHIFT), String.valueOf(keyCode) }
                : new String[] { String.valueOf(keyCode) };
    }

    public long getWaitForTimeout() {
        return waitForTimeout;
    }

    public void setWaitForTimeout(long waitForTimeout) {
        this.waitForTimeout = waitForTimeout >= 0 ? waitForTimeout : 0;
    }

    public long getWaitForCheckInterval() {
        return waitForCheckInterval;
    }

    public void setWaitForCheckInterval(long waitForCheckInterval) {
        this.waitForCheckInterval = waitForCheckInterval >= 0 ? waitForCheckInterval : 0;
    }

    public long getWaitForReloadTimeout() {
        return waitForReloadTimeout;
    }

    public void setWaitForReloadTimeout(long waitForReloadTimeout) {
        this.waitForReloadTimeout = waitForReloadTimeout;
    }

    /**
     * Add a screenshot to a context.
     * 
     * @param file
     * @return
     */
    private static int addSnapshot(Context context, File file) {

        List<SnapshotInfo> list = (List<SnapshotInfo>) context.getInternalObject(Keys.SNAPSHOTS, true);
        if (list == null) {
            list = new ArrayList<SnapshotInfo>(1);
            context.setInternalObject(Keys.SNAPSHOTS, list, true);
        }
        final SnapshotInfo si = new SnapshotInfo();
        si.setFile(file);
        context.getStack().traverse(new IStackTraverser() {

            public boolean onItem(IEntity entity, ITag tag) {
                CallStack cs = new CallStack();
                cs.setEntity(entity);
                cs.setTag(tag);
                si.getCallStack().add(cs);
                return true;
            }
        });
        list.add(si);
        return list.size();
    }

    /**
     * Get the snapshot from a context. Should not be invoked by paxml code.
     * 
     * @param context
     *            the context
     * @return the list of snapshots
     */
    public static List<SnapshotInfo> getSnapshots(Context context) {

        if (context == null) {
            return new ArrayList<SnapshotInfo>(0);
        }
        List<SnapshotInfo> list = (List<SnapshotInfo>) context.getInternalObject(Keys.SNAPSHOTS, true);
        if (list == null) {
            list = new ArrayList<SnapshotInfo>(0);
        }
        return Collections.unmodifiableList(list);
    }

    public String getSnapshotsPath() {
        return snapshotsPath;
    }

    /**
     * Get the actual default selenium. Should not be called by paxml
     * 
     * @return the default selenium
     */
    DefaultSelenium getSelenium() {
        return selenium;
    }
}