Search code examples
javaseleniumselenium-webdriverfirefox-marionette

Selenium Marionette driver UnreachableBrowserException upon second launch


I'm currently playing around with the Selenium Marionette WebDriver. In my application, I want to open multiple Marionette drivers sequentially. Basically something like this:

MarionetteDriver driver = new MarionetteDriver();
// do some stuff
driver.quit();

// a while later

driver = new MarionetteDriver();
// do some stuff
driver.quit();

Now I'm facing the problem, that only the first Marionette instance can be successfully started and for each later attempt, I'm getting the following exception. The problem happens every time and the used port always changes, so there is obviously no port conflict.

Exception in thread "main" org.openqa.selenium.remote.UnreachableBrowserException: Could not start a new session. Possible causes are invalid address of the remote server or browser start-up failure.
Build info: version: '2.48.2', revision: '41bccdd10cf2c0560f637404c2d96164b67d9d67', time: '2015-10-09 13:08:06'
System info: host: 'qqilihq.local', ip: '192.168.1.2', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.11.2', java.version: '1.7.0_71'
Driver info: driver.version: MarionetteDriver
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:641)
    at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:247)
    at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:232)
    at org.openqa.selenium.firefox.MarionetteDriver.run(MarionetteDriver.java:84)
    at org.openqa.selenium.firefox.MarionetteDriver.<init>(MarionetteDriver.java:73)
    at org.openqa.selenium.firefox.MarionetteDriver.<init>(MarionetteDriver.java:45)
    at MyMainClass.main(MyMainClass.java:131)
Caused by: org.openqa.selenium.WebDriverException: org.apache.http.conn.HttpHostConnectException: Connect to localhost:41886 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused
Build info: version: '2.48.2', revision: '41bccdd10cf2c0560f637404c2d96164b67d9d67', time: '2015-10-09 13:08:06'
System info: host: 'qqilihq.local', ip: '192.168.1.2', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.11.2', java.version: '1.7.0_71'
Driver info: driver.version: MarionetteDriver
    at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:91)
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:620)
    ... 6 more
Caused by: org.apache.http.conn.HttpHostConnectException: Connect to localhost:41886 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:151)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:71)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
    at org.openqa.selenium.remote.internal.ApacheHttpClient.fallBackExecute(ApacheHttpClient.java:143)
    at org.openqa.selenium.remote.internal.ApacheHttpClient.execute(ApacheHttpClient.java:89)
    at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:142)
    at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:82)
    ... 7 more
Caused by: java.net.ConnectException: Connection refused
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:579)
    at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:74)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.    java:134)
    ... 20 more

Any pointers appreciated!


Solution

  • After digging a little deeper, I came to the following findings which eventually solved my issue:

    1. Marionette resp. wires uses two ports (see wires --help); a marionette-port and a webdriver-port:

      Usage:
          ./wires [OPTIONS]
      
      WebDriver to marionette proxy.
      
      optional arguments:
        -h,--help             show this help message and exit
        -b,--binary BINARY    Path to the Firefox binary
        --webdriver-host WEBDRIVER_HOST
                              Host to run webdriver server on
        --webdriver-port WEBDRIVER_PORT
                              Port to run webdriver on
        --marionette-port MARIONETTE_PORT
                              Port to run marionette on
        --connect-existing    Connect to an existing firefox process
      

      When running multiple MarionetteDrivers simultaneously, both ports have to be different from the already running instance obviously. However, when using the default constructor new MarionetteDriver() the marionette-port remains constant (and is not determined based on some free port). We used some workaround (see below) for the GeckoDriverService.Builder to always pick two randomly-chosen available ports.

    2. The current (version 2.48.2) GeckoDriverService has an empty implementation of waitUntilAvailable() (which should check, if the WebDriver is ready to go). Sporadically, this lead to the UnreachableBrowserException posted above.

    To circumvent these issues, we did something like this at the end:

    // determine free ports for Marionette and WebDriver
    final int marionettePort = PortProber.findFreePort();
    final int webDriverPort = PortProber.findFreePort();
    // override, as GeckoDriverService provides no direct way to set the Marionette port
    GeckoDriverService.Builder builder = new GeckoDriverService.Builder() {
        @Override
        protected ImmutableList<String> createArgs() {
            Builder<String> argsBuilder = ImmutableList.builder();
            argsBuilder.addAll(super.createArgs());
            argsBuilder.add(String.format("--marionette-port=%d", marionettePort));
            return argsBuilder.build();
        }
    };
    builder.usingPort(webDriverPort);
    builder.usingDriverExecutable(pathToDriver);
    GeckoDriverService driverService = builder.build();
    try {
        driverService.start();
    } catch (IOException e) {
        throw new IllegalStateException("Could not start the GeckoDriverService", e);
    }
    try {
        // keep checking the WebDriver port via Socket until it's available;
        // as far as I could tell, there is nothing more "high level", e.g. REST API
        waitUntilReady(webDriverPort, TimeUnit.SECONDS.toMillis(30));
    } catch (InterruptedException e) {
        // ignore
    }
    return new MarionetteDriver(driverService, capabilities);