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.