Skip to content

Commit

Permalink
selenium: add support for Firefox 48+
Browse files Browse the repository at this point in the history
Change ExtensionSelenium to use geckodriver/marionette, allowing to use
newer versions of Firefox (>= 48).
Change options to allow to specify the path to geckodriver.
Backport GeckoDriverService to not require updating Selenium (which
needs Java 8).
Update help pages to mention the supported versions of Firefox, fix
typos and add suggested workaround for PhantomJS.
Bump version and update changes in ZapAddOn.xml file.

Fix zaproxy/zaproxy#2743 - Unable to use Firefox 48+ with AJAX Spider
  • Loading branch information
thc202 committed Jan 19, 2017
1 parent fd3bbfe commit 7dc5968
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 13 deletions.
2 changes: 1 addition & 1 deletion build/build.xml
Expand Up @@ -539,7 +539,7 @@
<!-- Add the extra classes required -->
<jar jarfile="${dist}/${file}" update="true" compress="true">
<zipfileset dir="${build}" prefix="">
<include name="org/openqa/selenium/phantomjs/**"/>
<include name="org/openqa/selenium/**"/>
</zipfileset>
</jar>
</build-addon>
Expand Down
133 changes: 133 additions & 0 deletions src/org/openqa/selenium/firefox/GeckoDriverService.java
@@ -0,0 +1,133 @@
// 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 java.util.concurrent.TimeUnit.SECONDS;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.firefox.internal.Executable;
import org.openqa.selenium.remote.service.DriverService;

import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;

/**
* Manages the life and death of an GeckoDriver aka 'wires'.
*/
public class GeckoDriverService extends DriverService {

/**
* System property that defines the location of the GeckoDriver executable
* that will be used by the {@link #createDefaultService() default service}.
*/
public static final String GECKO_DRIVER_EXE_PROPERTY = "webdriver.gecko.driver";

/**
*
* @param executable The GeckoDriver executable.
* @param port Which port to start the GeckoDriver on.
* @param args The arguments to the launched server.
* @param environment The environment for the launched server.
* @throws IOException If an I/O error occurs.
*/
public GeckoDriverService(File executable, int port, ImmutableList<String> args,
ImmutableMap<String, String> environment) throws IOException {
super(executable, port, args, environment);
}

/**
* Configures and returns a new {@link GeckoDriverService} using the default configuration. In
* this configuration, the service will use the GeckoDriver executable identified by the
* {@link #GECKO_DRIVER_EXE_PROPERTY} system property. Each service created by this method will
* be configured to use a free port on the current system.
*
* @return A new GeckoDriverService using the default configuration.
*/
public static GeckoDriverService createDefaultService() {
return new Builder().usingAnyFreePort().build();
}

@Override
protected void waitUntilAvailable() throws MalformedURLException {
waitForPortUp(getUrl().getPort(), 20, SECONDS);
}

private static void waitForPortUp(int port, int timeout, TimeUnit unit) {
long end = System.currentTimeMillis() + unit.toMillis(timeout);
while (System.currentTimeMillis() < end) {
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("localhost", port), 1000);
socket.close();
return;
} catch (ConnectException e) {
// Ignore this
} catch (SocketTimeoutException e) {
// Ignore this
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

/**
* Builder used to configure new {@link GeckoDriverService} instances.
*/
public static class Builder extends DriverService.Builder<
GeckoDriverService, GeckoDriverService.Builder> {

@Override
protected File findDefaultExecutable() {
return findExecutable("wires", GECKO_DRIVER_EXE_PROPERTY,
"https://github.com/jgraham/wires",
"https://github.com/jgraham/wires");
}

@Override
protected ImmutableList<String> createArgs() {
ImmutableList.Builder<String> argsBuilder = ImmutableList.builder();
argsBuilder.add(String.format("--webdriver-port=%d", getPort()));
if (getLogFile() != null) {
argsBuilder.add(String.format("--log-file=\"%s\"", getLogFile().getAbsolutePath()));
}
argsBuilder.add("-b");
argsBuilder.add(new Executable(null).getPath());
return argsBuilder.build();
}

@Override
protected GeckoDriverService createDriverService(File exe, int port,
ImmutableList<String> args,
ImmutableMap<String, String> environment) {
try {
return new GeckoDriverService(exe, port, args, environment);
} catch (IOException e) {
throw new WebDriverException(e);
}
}
}
}
44 changes: 44 additions & 0 deletions src/org/zaproxy/zap/extension/selenium/ExtensionSelenium.java
Expand Up @@ -29,6 +29,7 @@
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.MarionetteDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
Expand All @@ -42,6 +43,8 @@
import org.zaproxy.zap.Version;
import org.zaproxy.zap.extension.api.API;

import com.google.gson.JsonObject;

/**
* An {@code Extension} that provides {@code WebDriver} implementations for several {@code Browser}s.
*
Expand Down Expand Up @@ -238,6 +241,47 @@ private static WebDriver getWebDriverImpl(Browser browser, String proxyAddress,
case CHROME:
return new ChromeDriver(capabilities);
case FIREFOX:
String geckoDriver = System.getProperty(SeleniumOptions.FIREFOX_DRIVER_SYSTEM_PROPERTY);
if (geckoDriver != null && !geckoDriver.isEmpty()) {
capabilities.setCapability("marionette", Boolean.TRUE);

JsonObject prefs = new JsonObject();
prefs.addProperty("network.proxy.no_proxies_on", "");

// Since Firefox 53 (in desired capabilities), https://bugzilla.mozilla.org/show_bug.cgi?id=1282873
if (proxyAddress != null) {
JsonObject json = new JsonObject();
json.addProperty("proxyType", "manual");
json.addProperty("httpProxy", proxyAddress);
json.addProperty("httpProxyPort", proxyPort);
json.addProperty("sslProxy", proxyAddress);
json.addProperty("sslProxyPort", proxyPort);

capabilities.setCapability("proxy", json);
}
// For now set (also) the preferences manually...
prefs.addProperty("network.proxy.type", 1);
prefs.addProperty("network.proxy.http", proxyAddress);
prefs.addProperty("network.proxy.http_port", proxyPort);
prefs.addProperty("network.proxy.ssl", proxyAddress);
prefs.addProperty("network.proxy.ssl_port", proxyPort);
prefs.addProperty("network.proxy.share_proxy_settings", Boolean.TRUE);

JsonObject options = new JsonObject();
options.add("prefs", prefs);

String binaryPath = System.getProperty(SeleniumOptions.FIREFOX_BINARY_SYSTEM_PROPERTY);
if (binaryPath != null && !binaryPath.isEmpty()) {
options.addProperty("binary", binaryPath);
}
capabilities.setCapability("moz:firefoxOptions", options);

// Since Firefox 53, https://bugzilla.mozilla.org/show_bug.cgi?id=1103196
capabilities.setCapability("acceptInsecureCerts", true);

return new MarionetteDriver(capabilities);
}

String binaryPath = System.getProperty(SeleniumOptions.FIREFOX_BINARY_SYSTEM_PROPERTY);
if (binaryPath != null && !binaryPath.isEmpty()) {
capabilities.setCapability(FirefoxDriver.BINARY, binaryPath);
Expand Down
40 changes: 39 additions & 1 deletion src/org/zaproxy/zap/extension/selenium/SeleniumOptions.java
Expand Up @@ -34,7 +34,8 @@
* It allows to change, programmatically, the following options:
* <ul>
* <li>The path to ChromeDriver;</li>
* <li>The path to Firefox binary.</li>
* <li>The path to Firefox binary;</li>
* <li>The path to Firefox driver (geckodriver);</li>
* <li>The path to IEDriverServer;</li>
* <li>The path to PhantomJS binary.</li>
* </ul>
Expand All @@ -46,6 +47,7 @@ public class SeleniumOptions extends VersionedAbstractParam {

public static final String CHROME_DRIVER_SYSTEM_PROPERTY = ChromeDriverService.CHROME_DRIVER_EXE_PROPERTY;
public static final String FIREFOX_BINARY_SYSTEM_PROPERTY = "zap.selenium.webdriver.firefox.bin";
public static final String FIREFOX_DRIVER_SYSTEM_PROPERTY = "webdriver.gecko.driver";
public static final String IE_DRIVER_SYSTEM_PROPERTY = InternetExplorerDriverService.IE_DRIVER_EXE_PROPERTY;
public static final String PHANTOM_JS_BINARY_SYSTEM_PROPERTY = PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY;

Expand Down Expand Up @@ -82,6 +84,11 @@ public class SeleniumOptions extends VersionedAbstractParam {
*/
private static final String FIREFOX_BINARY_KEY = SELENIUM_BASE_KEY + ".firefoxBinary";

/**
* The configuration key to read/write the path Firefox driver (geckodriver).
*/
private static final String FIREFOX_DRIVER_KEY = SELENIUM_BASE_KEY + ".firefoxDriver";

/**
* The configuration key to read/write the path to IEDriverServer.
*/
Expand All @@ -102,6 +109,11 @@ public class SeleniumOptions extends VersionedAbstractParam {
*/
private String firefoxBinaryPath = "";

/**
* The path to Firefox driver (geckodriver).
*/
private String firefoxDriverPath = "";

/**
* The path to IEDriverServer.
*/
Expand All @@ -128,6 +140,7 @@ protected String getConfigVersionKey() {
protected void parseImpl() {
chromeDriverPath = readSystemPropertyWithOptionFallback(CHROME_DRIVER_SYSTEM_PROPERTY, CHROME_DRIVER_KEY);
firefoxBinaryPath = readSystemPropertyWithOptionFallback(FIREFOX_BINARY_SYSTEM_PROPERTY, FIREFOX_BINARY_KEY);
firefoxDriverPath = readSystemPropertyWithOptionFallback(FIREFOX_DRIVER_SYSTEM_PROPERTY, FIREFOX_DRIVER_KEY);
ieDriverPath = readSystemPropertyWithOptionFallback(IE_DRIVER_SYSTEM_PROPERTY, IE_DRIVER_KEY);
phantomJsBinaryPath = readSystemPropertyWithOptionFallback(PHANTOM_JS_BINARY_SYSTEM_PROPERTY, PHANTOM_JS_BINARY_KEY);
}
Expand Down Expand Up @@ -229,6 +242,31 @@ public void setFirefoxBinaryPath(String firefoxBinaryPath) {
}
}

/**
* Gets the path to Firefox driver (geckodriver).
*
* @return the path to Firefox driver, or empty if not set.
*/
public String getFirefoxDriverPath() {
return firefoxDriverPath;
}

/**
* Sets the path to Firefox driver (geckodriver).
*
* @param firefoxDriverPath the path to Firefox driver, or empty if not known.
* @throws IllegalArgumentException if {@code firefoxDriverPath} is {@code null}.
*/
public void setFirefoxDriverPath(String firefoxDriverPath) {
Validate.notNull(firefoxDriverPath, "Parameter firefoxDriverPath must not be null.");

if (!this.firefoxDriverPath.equals(firefoxDriverPath)) {
this.firefoxDriverPath = firefoxDriverPath;

saveAndSetSystemProperty(FIREFOX_DRIVER_KEY, FIREFOX_DRIVER_SYSTEM_PROPERTY, firefoxDriverPath);
}
}

/**
* Gets the path to IEDriverServer.
*
Expand Down
19 changes: 19 additions & 0 deletions src/org/zaproxy/zap/extension/selenium/SeleniumOptionsPanel.java
Expand Up @@ -40,6 +40,7 @@
* <ul>
* <li>The path to ChromeDriver;</li>
* <li>The path to Firefox binary.</li>
* <li>The path to Firefox driver (geckodriver).</li>
* <li>The path to IEDriverServer;</li>
* <li>The path to PhantomJS binary.</li>
* </ul>
Expand All @@ -52,6 +53,7 @@ class SeleniumOptionsPanel extends AbstractParamPanel {

private final JTextField chromeDriverTextField;
private final JTextField firefoxBinaryTextField;
private final JTextField firefoxDriverTextField;
private final JTextField ieDriverTextField;
private final JTextField phantomJsBinaryTextField;

Expand All @@ -76,6 +78,11 @@ public SeleniumOptionsPanel(ResourceBundle resourceBundle) {
JLabel firefoxBinaryLabel = new JLabel(resourceBundle.getString("selenium.options.label.firefox.binary"));
firefoxBinaryLabel.setLabelFor(firefoxBinaryTextField);

firefoxDriverTextField = createTextField();
JButton firefoxDriverButton = createButtonFileChooser(selectFileButtonLabel, firefoxDriverTextField);
JLabel firefoxDriverLabel = new JLabel(resourceBundle.getString("selenium.options.label.firefox.driver"));
firefoxDriverLabel.setLabelFor(firefoxDriverTextField);

ieDriverTextField = createTextField();
JButton ieDriverButton = createButtonFileChooser(selectFileButtonLabel, ieDriverTextField);
JLabel ieDriverLabel = new JLabel(resourceBundle.getString("selenium.options.label.driver.ie"));
Expand All @@ -91,6 +98,7 @@ public SeleniumOptionsPanel(ResourceBundle resourceBundle) {
layout.createParallelGroup(GroupLayout.Alignment.TRAILING)
.addComponent(chromeDriverLabel)
.addComponent(firefoxBinaryLabel)
.addComponent(firefoxDriverLabel)
.addComponent(ieDriverLabel)
.addComponent(phantomJsBinaryLabel))
.addGroup(
Expand All @@ -103,6 +111,10 @@ public SeleniumOptionsPanel(ResourceBundle resourceBundle) {
layout.createSequentialGroup()
.addComponent(firefoxBinaryTextField)
.addComponent(firefoxBinaryButton))
.addGroup(
layout.createSequentialGroup()
.addComponent(firefoxDriverTextField)
.addComponent(firefoxDriverButton))
.addGroup(
layout.createSequentialGroup()
.addComponent(ieDriverTextField)
Expand All @@ -123,6 +135,11 @@ public SeleniumOptionsPanel(ResourceBundle resourceBundle) {
.addComponent(firefoxBinaryLabel)
.addComponent(firefoxBinaryTextField)
.addComponent(firefoxBinaryButton))
.addGroup(
layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(firefoxDriverLabel)
.addComponent(firefoxDriverTextField)
.addComponent(firefoxDriverButton))
.addGroup(
layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(ieDriverLabel)
Expand Down Expand Up @@ -153,6 +170,7 @@ public void initParam(Object obj) {

chromeDriverTextField.setText(seleniumOptions.getChromeDriverPath());
firefoxBinaryTextField.setText(seleniumOptions.getFirefoxBinaryPath());
firefoxDriverTextField.setText(seleniumOptions.getFirefoxDriverPath());
ieDriverTextField.setText(seleniumOptions.getIeDriverPath());
phantomJsBinaryTextField.setText(seleniumOptions.getPhantomJsBinaryPath());
}
Expand All @@ -168,6 +186,7 @@ public void saveParam(Object obj) throws Exception {

seleniumOptions.setChromeDriverPath(chromeDriverTextField.getText());
seleniumOptions.setFirefoxBinaryPath(firefoxBinaryTextField.getText());
seleniumOptions.setFirefoxDriverPath(firefoxDriverTextField.getText());
seleniumOptions.setIeDriverPath(ieDriverTextField.getText());
seleniumOptions.setPhantomJsBinaryPath(phantomJsBinaryTextField.getText());
}
Expand Down
7 changes: 3 additions & 4 deletions src/org/zaproxy/zap/extension/selenium/ZapAddOn.xml
@@ -1,16 +1,15 @@
<zapaddon>
<name>Selenium</name>
<version>7</version>
<version>8</version>
<semver>1.0.0</semver><!-- This version should be kept in sync with the one of the extension. -->
<status>release</status>
<description>WebDriver provider and includes HtmlUnit browser</description>
<author>ZAP Dev Team</author>
<url></url>
<changes>
<![CDATA[
Allow to use Firefox 47.0.1 (Issue 2739).<br>
Allow to manually specify the paths to binaries and WebDrivers in the options.<br>
Allow to choose which Firefox binary is used.<br>
Allow to use Firefox 48+ (Issue 2743).<br>
Allow to specify the path to geckodriver.<br>
]]>
</changes>
<extensions>
Expand Down

0 comments on commit 7dc5968

Please sign in to comment.