Selenium Marionette 驱动程序第二次启动时出现 UnreachableBrowserException

Selenium Marionette driver UnreachableBrowserException upon second launch

我目前正在使用 Selenium Marionette WebDriver。在我的应用程序中,我想依次打开多个 Marionette 驱动程序。基本上是这样的:

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

// a while later

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

现在我遇到了问题,只有第一个 Marionette 实例可以成功启动,并且对于以后的每次尝试,我都会收到以下异常。每次都会出现这个问题,而且使用的端口总是在变化,所以显然没有端口冲突。

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

感谢任何指点!

大多数情况下 UnreachableBrowserException 发生在 Selenium 找不到驱动程序所需的 exe 文件时。由于您尚未设置驱动程序功能,我假设以下步骤将解决您的问题。

根据 Mozilla MDN link Marionette 设置为 DesiredCapability

DesiredCapabilities capabilities = DesiredCapabilities.firefox();
// Set Marionette on so the Grid will use this instead of normal FirefoxDriver
capabilities.setCapability("marionette", true);

WebDriver driver = new RemoteWebDriver(capabilities); 

此外,Marionette 可执行文件已添加到路径(在 Windows 中):

Adding the executable to the PATH

Selenium will try use the executable in the path. You will need to add it to the path using the following.

最后,另一个 SO question 处理了 UnreachableBrowserException 问题。

深入挖掘后,我得出以下发现,最终解决了我的问题:

  1. Marionette 分别。 wires 使用两个端口(参见 wires --help);一个 marionette-port 和一个 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
    

    当运行同时多个MarionetteDrivers时,两个端口显然必须与已经运行的实例不同。但是,当使用默认构造函数 new MarionetteDriver() 时,marionette-port 保持不变(并且不是根据某些空闲端口确定的)。我们对 GeckoDriverService.Builder 使用了一些解决方法(见下文)以始终选择两个 randomly-chosen 可用端口。

  2. 当前(版本 2.48.2)GeckoDriverService 有一个空的 waitUntilAvailable() 实现(应该检查 WebDriver 是否准备就绪) .偶尔,这会导致上面发布的 UnreachableBrowserException

为了避免这些问题,我们在最后做了这样的事情:

// 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);

您需要处理 Marionette 所需的二进制文件。您可以手动完成(自行下载二进制文件并导出适当的变量),也可以使用 WebDriverManager 自动完成。只需添加以下依赖项:

<dependency>
   <groupId>io.github.bonigarcia</groupId>
   <artifactId>webdrivermanager</artifactId>
    <version>1.6.0</version>
</dependency>

然后,在您的代码调用中:

FirefoxDriverManager.getInstance().setup();

我遇到了类似的问题,通过使用覆盖的构建器实现创建 MarionetteDriver 的覆盖实例来解决。接受的答案是更优雅的解决方案。确实有很多时间深入根本原因

public static class CustomBuilder extends org.openqa.selenium.firefox.GeckoDriverService.Builder {

    @Override
    protected GeckoDriverService createDriverService(File exe, int port, ImmutableList<String> args, ImmutableMap<String, String> environment) {
        try {
            return new GeckoDriverService(exe, port, args, environment) {
                @Override
                protected void waitUntilAvailable() throws MalformedURLException {
                    logger.info("Waiting until avaliable");
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                    }
                    super.waitUntilAvailable();

                    logger.info("Finished waiting until avaliable");
                }
            };
        } catch (IOException e) {
            throw new WebDriverException(e);
        }
    }
}