org.openqa.selenium.firefox.FirefoxDriver.java Source code

Java tutorial

Introduction

Here is the source code for org.openqa.selenium.firefox.FirefoxDriver.java

Source

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

package org.openqa.selenium.firefox;

import static org.openqa.selenium.Platform.WINDOWS;
import static org.openqa.selenium.remote.CapabilityType.ACCEPT_SSL_CERTS;
import static org.openqa.selenium.remote.CapabilityType.HAS_NATIVE_EVENTS;
import static org.openqa.selenium.remote.CapabilityType.LOGGING_PREFS;
import static org.openqa.selenium.remote.CapabilityType.PROXY;
import static org.openqa.selenium.remote.CapabilityType.SUPPORTS_WEB_STORAGE;

import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import org.openqa.selenium.Capabilities;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Platform;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.firefox.internal.NewProfileExtensionConnection;
import org.openqa.selenium.firefox.internal.ProfilesIni;
import org.openqa.selenium.internal.Killable;
import org.openqa.selenium.internal.Lock;
import org.openqa.selenium.internal.SocketLock;
import org.openqa.selenium.logging.LocalLogs;
import org.openqa.selenium.logging.LoggingPreferences;
import org.openqa.selenium.logging.NeedsLocalLogs;
import org.openqa.selenium.remote.BeanToJsonConverter;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.CommandExecutor;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.FileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.Response;
import org.openqa.selenium.remote.SessionNotFoundException;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * An implementation of the {#link WebDriver} interface that drives Firefox. This works through a
 * firefox extension, which gets installed automatically if necessary.
 */
public class FirefoxDriver extends RemoteWebDriver implements Killable {

    public static final class SystemProperty {

        /**
         * System property that defines the location of the Firefox executable file.
         */
        public static final String BROWSER_BINARY = "webdriver.firefox.bin";

        /**
         * System property that defines the location of the file where Firefox log should be stored.
         */
        public static final String BROWSER_LOGFILE = "webdriver.firefox.logfile";

        /**
         * System property that defines the additional library path (Linux only).
         */
        public static final String BROWSER_LIBRARY_PATH = "webdriver.firefox.library.path";

        /**
         * System property that defines the profile that should be used as a template.
         * When the driver starts, it will make a copy of the profile it is using,
         * rather than using that profile directly.
         */
        public static final String BROWSER_PROFILE = "webdriver.firefox.profile";

        /**
         * System property that defines the location of the webdriver.xpi browser extension to install
         * in the browser. If not set, the prebuilt extension bundled with this class will be used.
         */
        public static final String DRIVER_XPI_PROPERTY = "webdriver.firefox.driver";

        /**
         * Boolean system property that instructs FirefoxDriver to use Marionette backend.
         */
        public static final String DRIVER_USE_MARIONETTE = "webdriver.firefox.marionette";
    }

    public static final String BINARY = "firefox_binary";
    public static final String PROFILE = "firefox_profile";

    // For now, only enable native events on Windows
    public static final boolean DEFAULT_ENABLE_NATIVE_EVENTS = Platform.getCurrent().is(WINDOWS);

    // Accept untrusted SSL certificates.
    @Deprecated
    public static final boolean ACCEPT_UNTRUSTED_CERTIFICATES = true;
    // Assume that the untrusted certificates will come from untrusted issuers
    // or will be self signed.
    @Deprecated
    public static final boolean ASSUME_UNTRUSTED_ISSUER = true;

    protected FirefoxBinary binary;

    public FirefoxDriver() {
        this(new FirefoxBinary(), null);
    }

    public FirefoxDriver(FirefoxProfile profile) {
        this(new FirefoxBinary(), profile);
    }

    public FirefoxDriver(Capabilities desiredCapabilities) {
        this(getBinary(desiredCapabilities), extractProfile(desiredCapabilities, null), desiredCapabilities);
    }

    public FirefoxDriver(Capabilities desiredCapabilities, Capabilities requiredCapabilities) {
        this(getBinary(desiredCapabilities), extractProfile(desiredCapabilities, requiredCapabilities),
                desiredCapabilities, requiredCapabilities);
    }

    private static FirefoxProfile extractProfile(Capabilities desiredCapabilities,
            Capabilities requiredCapabilities) {

        FirefoxProfile profile = null;
        Object raw = null;
        if (desiredCapabilities != null && desiredCapabilities.getCapability(PROFILE) != null) {
            raw = desiredCapabilities.getCapability(PROFILE);
        }
        if (requiredCapabilities != null && requiredCapabilities.getCapability(PROFILE) != null) {
            raw = requiredCapabilities.getCapability(PROFILE);
        }
        if (raw != null) {
            if (raw instanceof FirefoxProfile) {
                profile = (FirefoxProfile) raw;
            } else if (raw instanceof String) {
                try {
                    profile = FirefoxProfile.fromJson((String) raw);
                } catch (IOException e) {
                    throw new WebDriverException(e);
                }
            }
        }
        profile = getProfile(profile);

        populateProfile(profile, desiredCapabilities);
        populateProfile(profile, requiredCapabilities);

        return profile;
    }

    static void populateProfile(FirefoxProfile profile, Capabilities capabilities) {
        if (capabilities == null) {
            return;
        }
        if (capabilities.getCapability(SUPPORTS_WEB_STORAGE) != null) {
            Boolean supportsWebStorage = (Boolean) capabilities.getCapability(SUPPORTS_WEB_STORAGE);
            profile.setPreference("dom.storage.enabled", supportsWebStorage.booleanValue());
        }
        if (capabilities.getCapability(ACCEPT_SSL_CERTS) != null) {
            Boolean acceptCerts = (Boolean) capabilities.getCapability(ACCEPT_SSL_CERTS);
            profile.setAcceptUntrustedCertificates(acceptCerts);
        }
        if (capabilities.getCapability(LOGGING_PREFS) != null) {
            LoggingPreferences logsPrefs = (LoggingPreferences) capabilities.getCapability(LOGGING_PREFS);
            for (String logtype : logsPrefs.getEnabledLogTypes()) {
                profile.setPreference("webdriver.log." + logtype, logsPrefs.getLevel(logtype).intValue());
            }
        }

        if (capabilities.getCapability(HAS_NATIVE_EVENTS) != null) {
            Boolean nativeEventsEnabled = (Boolean) capabilities.getCapability(HAS_NATIVE_EVENTS);
            profile.setEnableNativeEvents(nativeEventsEnabled);
        }
    }

    private static FirefoxBinary getBinary(Capabilities capabilities) {
        if (capabilities != null && capabilities.getCapability(BINARY) != null) {
            Object raw = capabilities.getCapability(BINARY);
            if (raw instanceof FirefoxBinary) {
                return (FirefoxBinary) raw;
            }
            File file = new File((String) raw);
            return new FirefoxBinary(file);
        }
        return new FirefoxBinary();
    }

    public FirefoxDriver(FirefoxBinary binary, FirefoxProfile profile) {
        this(binary, profile, DesiredCapabilities.firefox());
    }

    public FirefoxDriver(FirefoxBinary binary, FirefoxProfile profile, Capabilities capabilities) {
        this(binary, profile, capabilities, null);
    }

    public FirefoxDriver(FirefoxBinary binary, FirefoxProfile profile, Capabilities desiredCapabilities,
            Capabilities requiredCapabilities) {
        super(new LazyCommandExecutor(binary, profile), dropCapabilities(desiredCapabilities, BINARY, PROFILE),
                dropCapabilities(requiredCapabilities, BINARY, PROFILE));
        this.binary = binary;
    }

    @Override
    public void setFileDetector(FileDetector detector) {
        throw new WebDriverException("Setting the file detector only works on remote webdriver instances obtained "
                + "via RemoteWebDriver");
    }

    /**
     * Attempt to forcibly kill this Killable at the OS level. Useful where the extension has
     * stopped responding, and you don't want to leak resources. Should not ordinarily be called.
     */
    public void kill() {
        binary.quit();
    }

    @Override
    public Options manage() {
        return new RemoteWebDriverOptions() {
            @Override
            public Timeouts timeouts() {
                return new RemoteTimeouts() {
                    public Timeouts implicitlyWait(long time, TimeUnit unit) {
                        execute(DriverCommand.SET_TIMEOUT, ImmutableMap.of("type", "implicit", "ms",
                                TimeUnit.MILLISECONDS.convert(time, unit)));
                        return this;
                    }

                    public Timeouts setScriptTimeout(long time, TimeUnit unit) {
                        execute(DriverCommand.SET_TIMEOUT,
                                ImmutableMap.of("type", "script", "ms", TimeUnit.MILLISECONDS.convert(time, unit)));
                        return this;
                    }
                };
            }
        };
    }

    @Override
    protected void startClient() {
        LazyCommandExecutor exe = (LazyCommandExecutor) getCommandExecutor();
        FirefoxProfile profileToUse = getProfile(exe.profile);

        // TODO(simon): Make this not sinfully ugly
        ExtensionConnection connection = connectTo(exe.binary, profileToUse, "localhost");
        exe.setConnection(connection);

        try {
            connection.start();
        } catch (IOException e) {
            throw new WebDriverException("An error occurred while connecting to Firefox", e);
        }
    }

    private static FirefoxProfile getProfile(FirefoxProfile profile) {
        FirefoxProfile profileToUse = profile;
        String suggestedProfile = System.getProperty(SystemProperty.BROWSER_PROFILE);
        if (profileToUse == null && suggestedProfile != null) {
            profileToUse = new ProfilesIni().getProfile(suggestedProfile);
            if (profileToUse == null) {
                throw new WebDriverException(
                        String.format("Firefox profile '%s' named in system property '%s' not found",
                                suggestedProfile, SystemProperty.BROWSER_PROFILE));
            }
        } else if (profileToUse == null) {
            profileToUse = new FirefoxProfile();
        }
        return profileToUse;
    }

    protected ExtensionConnection connectTo(FirefoxBinary binary, FirefoxProfile profile, String host) {
        Lock lock = obtainLock(profile);
        try {
            FirefoxBinary bin = binary == null ? new FirefoxBinary() : binary;
            return new NewProfileExtensionConnection(lock, bin, profile, host);
        } catch (Exception e) {
            throw new WebDriverException(e);
        }
    }

    protected Lock obtainLock(FirefoxProfile profile) {
        return new SocketLock();
    }

    @Override
    protected void stopClient() {
        ((LazyCommandExecutor) this.getCommandExecutor()).quit();
    }

    /**
     * Drops capabilities that we shouldn't send over the wire.
     *
     * Used for capabilities which aren't BeanToJson-convertable, and are only used by the local
     * launcher.
     */
    private static Capabilities dropCapabilities(Capabilities capabilities, String... keysToRemove) {
        if (capabilities == null) {
            return new DesiredCapabilities();
        }
        final Set<String> toRemove = Sets.newHashSet(keysToRemove);
        DesiredCapabilities caps = new DesiredCapabilities(
                Maps.filterKeys(capabilities.asMap(), new Predicate<String>() {
                    public boolean apply(String key) {
                        return !toRemove.contains(key);
                    }
                }));

        // Ensure that the proxy is in a state fit to be sent to the extension
        Proxy proxy = Proxy.extractFrom(capabilities);
        if (proxy != null) {
            caps.setCapability(PROXY, new BeanToJsonConverter().convert(proxy));
        }

        return caps;
    }

    public <X> X getScreenshotAs(OutputType<X> target) {
        // Get the screenshot as base64.
        String base64 = execute(DriverCommand.SCREENSHOT).getValue().toString();
        // ... and convert it.
        return target.convertFromBase64Png(base64);
    }

    public static class LazyCommandExecutor implements CommandExecutor, NeedsLocalLogs {
        private ExtensionConnection connection;
        private final FirefoxBinary binary;
        private final FirefoxProfile profile;
        private LocalLogs logs = LocalLogs.getNullLogger();

        private LazyCommandExecutor(FirefoxBinary binary, FirefoxProfile profile) {
            this.binary = binary;
            this.profile = profile;
        }

        public void setConnection(ExtensionConnection connection) {
            this.connection = connection;
            connection.setLocalLogs(logs);
        }

        public void quit() {
            if (connection != null) {
                connection.quit();
                connection = null;
            }
            if (profile != null) {
                profile.cleanTemporaryModel();
            }
        }

        public Response execute(Command command) throws IOException {
            if (connection == null) {
                if (command.getName().equals(DriverCommand.QUIT)) {
                    return new Response();
                }
                throw new SessionNotFoundException("The FirefoxDriver cannot be used after quit() was called.");
            }
            return connection.execute(command);
        }

        public void setLocalLogs(LocalLogs logs) {
            this.logs = logs;
            if (connection != null) {
                connection.setLocalLogs(logs);
            }
        }

        public URI getAddressOfRemoteServer() {
            return connection.getAddressOfRemoteServer();
        }
    }
}