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.in");
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.in");
}
And this attempt just works great.