Re-using existing browser session in Selenium using Java

Yesterday I wrote an article on how to re-use a session in Selenium using Python. @Aditya Baraskar asked if the same was possible in Java.

Once you know the concepts, languages usually is no barrier. So I tried using the same approach that we did in Python and see how it works out in Java

Attempt 1

ChromeDriver driver = new ChromeDriver();
HttpCommandExecutor executor = (HttpCommandExecutor) driver.getCommandExecutor();
URL url = executor.getAddressOfRemoteServer();
SessionId session_id = driver.getSessionId();

RemoteWebDriver driver2 = new RemoteWebDriver(executor, new DesiredCapabilities());

When we run the above code, it errors out with below exception

objc[52278]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/bin/java (0x1040284c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1040f04e0). One of the two will be used. Which one is undefined.
Starting ChromeDriver 2.27.440174 (e97a722caafc2d3a8b807ee115bfb307f7d2cfd9) on port 29572
Only local connections are allowed.
Jun 01, 2017 12:24:17 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected dialect: OSS
Exception in thread "main" org.openqa.selenium.SessionNotCreatedException: Session already exists
Build info: version: '3.4.0', revision: 'unknown', time: 'unknown'
Driver info: driver.version: RemoteWebDriver
	at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:138)
	at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:82)
	at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:637)
	at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:250)
	at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:236)
	at org.openqa.selenium.remote.RemoteWebDriver.<init>(RemoteWebDriver.java:137)
	at TestApp.main(TestApp.java:79)

Attempt 2

Next guess was to override “org.openqa.selenium.remote.HttpCommandExecutor.execute” and override the response for the newSession command

public static RemoteWebDriver createDriverFromSession(final SessionId sessionId, URL command_executor){
    CommandExecutor executor = new HttpCommandExecutor(command_executor) {

        @Override
        public Response execute(Command command) throws IOException {
            Response response = null;
            if (command.getName() == "newSession") {
                response = new Response();
                response.setSessionId(sessionId.toString());
                response.setStatus(0);
                response.setValue(Collections.<String, String>emptyMap());


            } else {
                response = super.execute(command);
            }
            return response;
        }
    };

    return new RemoteWebDriver(executor, new DesiredCapabilities());
}

public static void main(String [] args) {

    ChromeDriver driver = new ChromeDriver();
    HttpCommandExecutor executor = (HttpCommandExecutor) driver.getCommandExecutor();
    URL url = executor.getAddressOfRemoteServer();
    SessionId session_id = driver.getSessionId();


    RemoteWebDriver driver2 = createDriverFromSession(session_id, url);
}

The driver2 objects get created, which was a wow moment. Next thing was to test a simple command on the driver

driver2.get("http://tarunlalwani.com");

And BOOM!!!!, exception

objc[53332]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/bin/java (0x10ec5c4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10ed244e0). One of the two will be used. Which one is undefined.
Starting ChromeDriver 2.27.440174 (e97a722caafc2d3a8b807ee115bfb307f7d2cfd9) on port 5528
Only local connections are allowed.
Jun 01, 2017 12:41:02 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected dialect: OSS
Exception in thread "main" org.openqa.selenium.WebDriverException: No command or response codec has been defined. Unable to proceed
Build info: version: '3.4.0', revision: 'unknown', time: 'unknown'
Driver info: driver.version: RemoteWebDriver
	at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:154)
	at TestApp$1.execute(TestApp.java:54)
	at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:637)
	at org.openqa.selenium.remote.RemoteWebDriver.get(RemoteWebDriver.java:364)
	at TestApp.main(TestApp.java:74)

So I digged into Selenium source code for file HttpCommandExecutor.java and found the below lines of code

    if (NEW_SESSION.equals(command.getName())) {
      if (commandCodec != null) {
        throw new SessionNotCreatedException("Session already exists");
      }
      ProtocolHandshake handshake = new ProtocolHandshake();
      log(LogType.PROFILER, new HttpProfilerLogEntry(command.getName(), true));
      ProtocolHandshake.Result result = handshake.createSession(client, command);
      Dialect dialect = result.getDialect();
      commandCodec = dialect.getCommandCodec();
      for (Map.Entry<String, CommandInfo> entry : additionalCommands.entrySet()) {
        defineCommand(entry.getKey(), entry.getValue());
      }
      responseCodec = dialect.getResponseCodec();
      log(LogType.PROFILER, new HttpProfilerLogEntry(command.getName(), false));
      return result.createResponse();
    }

    if (commandCodec == null || responseCodec == null) {
      throw new WebDriverException(
        "No command or response codec has been defined. Unable to proceed");
    }

So we can see that when we override the newSession command, we just return a response. But we don’t set the commandCodec and responseCodec. Which then errors out at the next command we executed.

If we look at the definition of these two objects in the class

  private CommandCodec<HttpRequest> commandCodec;
  private ResponseCodec<HttpResponse> responseCodec;

They both are private field.

Attempt 3

So in our overriden HttpCommandExecutor, we need to set the commandCodec and responseCodec variables.

I believe these changes were made for supporting both W3C and the old command and response formats. Since I am using the latest version, I will override these to the W3C objects required.

public static RemoteWebDriver createDriverFromSession(final SessionId sessionId, URL command_executor){
    CommandExecutor executor = new HttpCommandExecutor(command_executor) {

    @Override
    public Response execute(Command command) throws IOException {
        Response response = null;
        if (command.getName() == "newSession") {
            response = new Response();
            response.setSessionId(sessionId.toString());
            response.setStatus(0);
            response.setValue(Collections.<String, String>emptyMap());

            try {
                Field commandCodec = null;
                commandCodec = this.getClass().getSuperclass().getDeclaredField("commandCodec");
                commandCodec.setAccessible(true);
                commandCodec.set(this, new W3CHttpCommandCodec());

                Field responseCodec = null;
                responseCodec = this.getClass().getSuperclass().getDeclaredField("responseCodec");
                responseCodec.setAccessible(true);
                responseCodec.set(this, new W3CHttpResponseCodec());
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        } else {
            response = super.execute(command);
        }
        return response;
    }
    };

    return new RemoteWebDriver(executor, new DesiredCapabilities());
}

public static void main(String [] args) {

    ChromeDriver driver = new ChromeDriver();
    HttpCommandExecutor executor = (HttpCommandExecutor) driver.getCommandExecutor();
    URL url = executor.getAddressOfRemoteServer();
    SessionId session_id = driver.getSessionId();


    RemoteWebDriver driver2 = createDriverFromSession(session_id, url);
    driver2.get("http://tarunlalwani.com");
}

And this attempt just works great.