All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.specrunner.htmlunit.PluginBrowser Maven / Gradle / Ivy

There is a newer version: 1.5.17
Show newest version
/*
    SpecRunner - Acceptance Test Driven Development Tool
    Copyright (C) 2011-2016  Thiago Santos

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see 
 */
package org.specrunner.htmlunit;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

import org.specrunner.SRServices;
import org.specrunner.context.IContext;
import org.specrunner.context.IDestructable;
import org.specrunner.features.IFeatureManager;
import org.specrunner.htmlunit.listeners.PageListener;
import org.specrunner.listeners.IListenerManager;
import org.specrunner.plugins.ActionType;
import org.specrunner.plugins.ENext;
import org.specrunner.plugins.PluginException;
import org.specrunner.plugins.core.AbstractPluginScoped;
import org.specrunner.plugins.type.Command;
import org.specrunner.result.IResultSet;
import org.specrunner.result.status.Success;
import org.specrunner.reuse.IReusable;
import org.specrunner.reuse.IReuseManager;
import org.specrunner.reuse.core.AbstractReusable;
import org.specrunner.util.UtilLog;
import org.specrunner.util.expression.UtilExpression;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.Cache;
import com.gargoylesoftware.htmlunit.DefaultCredentialsProvider;
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
import com.gargoylesoftware.htmlunit.ProxyConfig;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebClientOptions;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebResponse;

/**
 * This plugin creates an HTML browser and add it to the test context with a
 * default name, or a given name. It means you can have more then one browser in
 * the same specification.
 * 
 * @author Thiago Santos
 * 
 */
public class PluginBrowser extends AbstractPluginScoped {

    /**
     * Default browser name. To set a different name use the attribute
     * 'name=<name>'.
     */
    public static final String BROWSER_NAME = "browser";

    /**
     * Feature to set browser version.
     */
    public static final String FEATURE_VERSION = PluginBrowser.class.getName() + ".version";
    /**
     * The browser version.
     */
    private String version = "CHROME";

    /**
     * Feature to set host (for proxies).
     */
    public static final String FEATURE_HOST = PluginBrowser.class.getName() + ".host";
    /**
     * The host.
     */
    private String host;

    /**
     * Feature to set port (for proxies).
     */
    public static final String FEATURE_PORT = PluginBrowser.class.getName() + ".port";
    /**
     * The port.
     */
    private Integer port;

    /**
     * Feature to set user name, if the browser requires authentication.
     */
    public static final String FEATURE_USERNAME = PluginBrowser.class.getName() + ".username";
    /**
     * The username.
     */
    private String username;

    /**
     * Feature to set password, if the browser requires authentication.
     */
    public static final String FEATURE_PASSWORD = PluginBrowser.class.getName() + ".password";
    /**
     * The password.
     */
    private String password;

    /**
     * Set default browser http timeout.
     */
    public static final String FEATURE_HTTPTIMEOUT = PluginBrowser.class.getName() + ".httptimeout";
    /**
     * The browser http timeout. Default is '0'.
     */
    private Integer httptimeout = 0;

    /**
     * Feature to set browser connection type.
     */
    public static final String FEATURE_CONNECTION = PluginBrowser.class.getName() + ".connection";
    /**
     * IWebConnection type to be used.
     */
    private String connection;
    /**
     * Connection instance.
     */
    private Class connectionType;

    /**
     * Default browser cache (class name) setting feature.
     */
    public static final String FEATURE_CACHE = PluginBrowser.class.getName() + ".cache";
    /**
     * Cache cache class to be used.
     */
    private String cache;
    /**
     * Cache instance.
     */
    private Cache cacheInstance;

    /**
     * Feature to set browser cached option (true/false).
     */
    public static final String FEATURE_CACHED = PluginBrowser.class.getName() + ".cached";
    /**
     * The cache status. Default is 'true'.
     */
    private Boolean cached = true;

    /**
     * Feature to enable browser recording.
     */
    public static final String FEATURE_RECORDING = PluginBrowser.class.getName() + ".recording";
    /**
     * Recording status. Default is "DEBUG log enabled".
     */
    private Boolean recording = UtilLog.LOG.isDebugEnabled();

    /**
     * Feature to set web driver reuse.
     */
    public static final String FEATURE_REUSE = PluginBrowser.class.getName() + ".reuse";
    /**
     * The reuse status.
     */
    private Boolean reuse = false;

    /**
     * Creates a browser.
     */
    public PluginBrowser() {
        setName(BROWSER_NAME);
    }

    /**
     * The browser version. Valid values are those in BrowserVersion class.
     * Default is "FIREFOX_24".
     * 
     * @return The HtmlUnit browser version.
     */
    public String getVersion() {
        return version;
    }

    /**
     * Set the browser version.
     * 
     * @param version
     *            The version.
     */
    public void setVersion(String version) {
        this.version = version;
    }

    /**
     * Gets the host for proxy settings. Default is null, to use a proxy set
     * 'host' an 'port' attributes.
     * 
     * @return The proxy host.
     */
    public String getHost() {
        return host;
    }

    /**
     * Sets the host.
     * 
     * @param host
     *            The host.
     */
    public void setHost(String host) {
        this.host = host;
    }

    /**
     * Gets the port for proxy settings. Default is null, to use a proxy set
     * 'host' and 'port' attributes.
     * 
     * @return The proxy port.
     */
    public Integer getPort() {
        return port;
    }

    /**
     * Sets the port.
     * 
     * @param port
     *            The port.
     */
    public void setPort(Integer port) {
        this.port = port;
    }

    /**
     * Gets username for authentication. Default is null, to use authentication
     * set 'username' and 'password' attributes.
     * 
     * @return The username.
     */
    public String getUsername() {
        return username;
    }

    /**
     * Sets the username.
     * 
     * @param username
     *            The username.
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * Gets the password for authentication. Default is null, to use
     * authentication set 'username' and 'password' attributes.
     * 
     * @return The password.
     */
    public String getPassword() {
        return password;
    }

    /**
     * Sets the password.
     * 
     * @param password
     *            The password.
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Gets the default browser Http timeout. This timeout is passed to HtmlUnit
     * and it uses it as Socket connection time limitation. Default is '0',
     * which is infinite.
     * 
     * @return The timeout.
     */
    public Integer getHttptimeout() {
        return httptimeout;
    }

    /**
     * Sets the HTTP timeout.
     * 
     * @param httptimeout
     *            The timeout.
     */
    public void setHttptimeout(Integer httptimeout) {
        this.httptimeout = httptimeout;
    }

    /**
     * Gets the web connection type, valid values are classes which implements
     * com.gargoylesoftware.htmlunit.WebConnection. Default is
     * null.
     * 
     * @return The connection instance class.
     */
    public String getConnection() {
        return connection;
    }

    /**
     * Sets the connection class.
     * 
     * @param connection
     *            The connection.
     */
    public void setConnection(String connection) {
        this.connection = connection;
    }

    /**
     * Gets the cache type, valid values are classes which extend
     * com.gargoylesoftware.htmlunit.Cache. Default is null.
     * 
     * @return The cache class.
     */
    public String getCache() {
        return cache;
    }

    /**
     * Sets the cache class name.
     * 
     * @param cache
     *            The cache class name.
     */
    public void setCache(String cache) {
        this.cache = cache;
    }

    /**
     * Gets if the browser uses a cache strategy. Default is false, to enable
     * cache set 'cache' attribute to true.
     * 
     * @return The cache status.
     */
    public Boolean getCached() {
        return cached;
    }

    /**
     * Set cached version.
     * 
     * @param cached
     *            The cached status.
     */
    public void setCached(Boolean cached) {
        this.cached = cached;
    }

    /**
     * Gets if the browser will take a snapshot after each action or assertion.
     * Default is true.
     * 
     * @return The recording status.
     */
    public Boolean getRecording() {
        return recording;
    }

    /**
     * Set recording status.
     * 
     * @param recording
     *            The status.
     */
    public void setRecording(Boolean recording) {
        this.recording = recording;
    }

    /**
     * Gets the reuse status. If reuse is true, the browser can be reused by
     * multiple tests if the browser name is the same.
     * 
     * @return The reuse status.
     */
    public Boolean getReuse() {
        return reuse;
    }

    /**
     * Set reuse status.
     * 
     * @param reuse
     *            The reuse status.
     */
    public void setReuse(Boolean reuse) {
        this.reuse = reuse;
    }

    @Override
    public ActionType getActionType() {
        return Command.INSTANCE;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void initialize(IContext context) throws PluginException {
        super.initialize(context);
        IFeatureManager fm = SRServices.getFeatureManager();
        fm.set(FEATURE_VERSION, this);
        if (host == null) {
            fm.set(FEATURE_HOST, this);
        }
        if (port == null) {
            fm.set(FEATURE_PORT, this);
        }
        if (username == null) {
            fm.set(FEATURE_USERNAME, this);
        }
        if (password == null) {
            fm.set(FEATURE_PASSWORD, this);
        }
        fm.set(FEATURE_HTTPTIMEOUT, this);
        if (connection == null) {
            fm.set(FEATURE_CONNECTION, this);
        }
        if (cache == null) {
            fm.set(FEATURE_CACHE, this);
        }
        if (cached == null) {
            fm.set(FEATURE_CACHED, this);
        }
        fm.set(FEATURE_RECORDING, this);
        fm.set(FEATURE_REUSE, this);

        if (connection != null) {
            try {
                connectionType = (Class) Class.forName(connection);
            } catch (ClassNotFoundException e) {
                throw new PluginException("Connection class '" + connection + "' not found, or is not a subtype of " + WebConnection.class.getName() + ".", e);
            }
        }
        if (cache != null) {
            try {
                Class cacheType = (Class) Class.forName(cache);
                cacheInstance = cacheType.newInstance();
            } catch (ClassNotFoundException e) {
                throw new PluginException("Cache class '" + cache + "' not found, or is not a subtype of " + Cache.class.getName() + ".", e);
            } catch (Exception e) {
                throw new PluginException("Cache class '" + cache + "' instance could not be created.", e);
            }
        }
    }

    @Override
    @SuppressWarnings("serial")
    public ENext doStart(IContext context, IResultSet result) throws PluginException {
        try {
            IListenerManager fac = SRServices.get(IListenerManager.class);
            if (recording) {
                fac.add(new PageListener(getName()));
            } else {
                fac.remove(getName());
            }
            IReuseManager reusables = SRServices.get(IReuseManager.class);
            if (reuse) {
                Map cfg = new HashMap();
                cfg.put("version", version);
                cfg.put("name", getName());
                IReusable reusable = reusables.get(getName());
                if (reusable != null && reusable.canReuse(cfg)) {
                    reusable.reset();
                    saveGlobal(context, getName(), reusable.getObject());
                    result.addResult(Success.INSTANCE, context.peek());
                    if (UtilLog.LOG.isInfoEnabled()) {
                        UtilLog.LOG.info("Browser (" + getName() + ") reused.");
                    }
                    return ENext.DEEP;
                }
            }

            BrowserVersion bVersion = null;
            try {
                bVersion = (BrowserVersion) UtilExpression.evaluate(BrowserVersion.class.getName() + "." + version, context, false);
            } catch (Exception e) {
                throw new PluginException("The plugin version " + version + " is invalid.", e);
            }
            if (UtilLog.LOG.isInfoEnabled()) {
                UtilLog.LOG.info("Browser named '" + getName() + "' version '" + bVersion.getNickname() + "'.");
            }
            final WebClient client = new WebClient(bVersion);

            // change web connection
            if (connectionType != null) {
                WebConnection connectionInstance = null;
                try {
                    Constructor constr = connectionType.getConstructor(WebClient.class);
                    connectionInstance = constr.newInstance(client);
                } catch (Exception e) {
                    connectionInstance = connectionType.newInstance();
                }
                client.setWebConnection(connectionInstance);
            }
            setDefaultClientBehaviors(client);

            // synchronize Ajax calls
            client.setAjaxController(new NicelyResynchronizingAjaxController());
            WebClientOptions options = client.getOptions();
            options.setUseInsecureSSL(true);
            if (host != null && port != null) {
                ProxyConfig config = new ProxyConfig(host, port);
                if (UtilLog.LOG.isInfoEnabled()) {
                    UtilLog.LOG.info("Browser named '" + getName() + "' proxy '" + host + ":" + port + "'.");
                }
                options.setProxyConfig(config);
            }
            if (username != null && password != null) {
                DefaultCredentialsProvider provider = new DefaultCredentialsProvider();
                provider.addCredentials(username, password);
                client.setCredentialsProvider(provider);
                if (UtilLog.LOG.isInfoEnabled()) {
                    UtilLog.LOG.info("Browser named '" + getName() + "' credentials '" + username + "|" + password + "'.");
                }
            }
            if (cached || cacheInstance != null) {
                if (UtilLog.LOG.isInfoEnabled()) {
                    UtilLog.LOG.info("Browser named '" + getName() + "' cached.");
                }
                Cache cacheLocal = null;
                if (cacheInstance == null) {
                    if (UtilLog.LOG.isInfoEnabled()) {
                        UtilLog.LOG.info("Using default cache.");
                    }
                    cacheLocal = new Cache() {
                        @Override
                        protected boolean isCacheableContent(WebResponse response) {
                            String type = response.getContentType();
                            boolean dynamic = ("".equals(type) || "text/html".equals(type)) && !super.isCacheableContent(response);
                            if (UtilLog.LOG.isInfoEnabled()) {
                                String url = response.getWebRequest().getUrl().toString();
                                UtilLog.LOG.info("Dynamic '" + type + "' = " + dynamic + ", loaded in " + response.getLoadTime() + "mls, URL: " + url + ".");
                            }
                            return !dynamic;
                        }
                    };
                } else {
                    if (UtilLog.LOG.isInfoEnabled()) {
                        UtilLog.LOG.info("Using customized cache '" + cacheInstance.getClass() + "'.");
                    }
                    cacheLocal = cacheInstance;
                }
                client.setCache(cacheLocal);
            } else {
                if (UtilLog.LOG.isInfoEnabled()) {
                    UtilLog.LOG.info("Browser named '" + getName() + "' not cached.");
                }
            }

            options.setTimeout(httptimeout);
            if (UtilLog.LOG.isInfoEnabled()) {
                UtilLog.LOG.info("Browser named '" + getName() + "' bound to '" + client + "'.");
            }
            save(context, client);
            result.addResult(Success.INSTANCE, context.peek());
            if (reuse) {
                if (UtilLog.LOG.isInfoEnabled()) {
                    UtilLog.LOG.info("WebClient reuse enabled.");
                }
                reusables.put(getName(), new AbstractReusable(getName(), client) {
                    @Override
                    public boolean canReuse(Map extra) {
                        Object localVersion = extra.get("version");
                        Object nameVersion = extra.get("name");
                        return localVersion != null && localVersion.equals(version) && nameVersion != null && nameVersion.equals(getName());
                    }

                    @Override
                    public void reset() {
                        if (UtilLog.LOG.isInfoEnabled()) {
                            UtilLog.LOG.info("WebClient recycling '" + getName() + "'.");
                        }
                        client.close();
                        if (UtilLog.LOG.isInfoEnabled()) {
                            UtilLog.LOG.info("WebClient '" + getName() + "' windows reset.");
                        }
                    }

                    @Override
                    public void release() {
                        if (UtilLog.LOG.isInfoEnabled()) {
                            UtilLog.LOG.info("WebClient '" + getName() + "' windows release.");
                        }
                    }
                });
            }
            return ENext.DEEP;
        } catch (Exception e) {
            throw new PluginException(e);
        }
    }

    /**
     * Save client on context.
     * 
     * @param context
     *            A context.
     * @param client
     *            A client.
     */
    protected void save(IContext context, final WebClient client) {
        if (reuse) {
            saveGlobal(context, getName(), client);
        } else {
            // not reuse mean close resources on context destroy
            saveGlobal(context, getName(), new IDestructable() {

                @Override
                public Object getObject() {
                    return client;
                }

                @Override
                public void destroy() {
                    client.close();
                }
            });
        }
    }

    /**
     * Set common properties.
     * 
     * @param client
     *            The client to be set.
     */
    protected void setDefaultClientBehaviors(final WebClient client) {
        // print content on error
        WebClientOptions options = client.getOptions();
        options.setPrintContentOnFailingStatusCode(true);
        options.setThrowExceptionOnFailingStatusCode(false);
        options.setThrowExceptionOnScriptError(false);
        // some handlers are logging without test log enabled, fixing.
        client.setCssErrorHandler(new OptimizedCssErrorHandler());
        client.setIncorrectnessListener(new OptimizedIncorrectnessListener());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy