Chrome DevTools Protocol version 115 bindings for Selenium Java WebDriver enabling programmatic browser debugging capabilities
—
JavaScript code injection, binding management, and script execution coordination with full lifecycle control and event monitoring through the v115Javascript class.
Manages JavaScript code injection and bidirectional communication between browser and Java code through bindings.
/**
* JavaScript domain for script injection and binding management
* Extends the idealized Javascript class with CDP v115 specific implementations
*/
public class v115Javascript extends Javascript<ScriptIdentifier, BindingCalled> {
/**
* Creates JavaScript domain with DevTools connection
* @param devtools DevTools instance for CDP communication
*/
public v115Javascript(DevTools devtools);
/**
* Enable runtime domain for JavaScript execution
* @return Command to enable runtime domain
*/
protected Command<Void> enableRuntime();
/**
* Disable runtime domain and stop JavaScript functionality
* @return Command to disable runtime domain
*/
protected Command<Void> disableRuntime();
/**
* Add JavaScript binding for bidirectional communication
* @param scriptName Name of the binding function to create
* @return Command to add the binding
*/
protected Command<Void> doAddJsBinding(String scriptName);
/**
* Remove JavaScript binding
* @param scriptName Name of the binding function to remove
* @return Command to remove the binding
*/
protected Command<Void> doRemoveJsBinding(String scriptName);
/**
* Enable page domain for script injection
* @return Command to enable page domain
*/
protected Command<Void> enablePage();
/**
* Disable page domain and stop script injection
* @return Command to disable page domain
*/
protected Command<Void> disablePage();
/**
* Add script to evaluate on every new document load
* @param script JavaScript code to inject
* @return Command returning ScriptIdentifier for the injected script
*/
protected Command<ScriptIdentifier> addScriptToEvaluateOnNewDocument(String script);
/**
* Remove previously injected script
* @param id ScriptIdentifier of the script to remove
* @return Command to remove the script
*/
protected Command<Void> removeScriptToEvaluateOnNewDocument(ScriptIdentifier id);
/**
* Get binding called event stream for monitoring binding invocations
* @return Event stream for binding called events
*/
protected Event<BindingCalled> bindingCalledEvent();
/**
* Extract payload from binding called event
* @param event BindingCalled event from CDP
* @return String payload sent from JavaScript
*/
protected String extractPayload(BindingCalled event);
}Inject JavaScript code that runs on every new document, useful for setting up global functions and variables.
/**
* Pin script for execution on new documents (inherited from Javascript base class)
* @param exposeScriptAs Name to expose the script as (for management)
* @param script JavaScript code to inject
* @return ScriptId for managing the injected script
*/
public ScriptId pin(String exposeScriptAs, String script);Usage Examples:
import org.openqa.selenium.devtools.v115.v115Javascript;
import org.openqa.selenium.devtools.idealized.ScriptId;
// Create JavaScript domain
v115Javascript javascript = new v115Javascript(devTools);
// Inject global utility functions
ScriptId utilsScript = javascript.pin("utils", """
window.myUtils = {
getCurrentTimestamp: function() {
return Date.now();
},
highlightElement: function(selector) {
const element = document.querySelector(selector);
if (element) {
element.style.border = '3px solid red';
return true;
}
return false;
},
getPageInfo: function() {
return {
title: document.title,
url: window.location.href,
elementCount: document.querySelectorAll('*').length
};
}
};
console.log('Utility functions loaded');
""");
// Navigate to pages - script will be available immediately
driver.get("https://example.com");
// Use the injected functions
Object timestamp = driver.executeScript("return window.myUtils.getCurrentTimestamp();");
Boolean highlighted = (Boolean) driver.executeScript(
"return window.myUtils.highlightElement('h1');"
);
System.out.println("Timestamp: " + timestamp);
System.out.println("Highlighted: " + highlighted);Create bidirectional communication channels between browser JavaScript and Java code.
/**
* Add JavaScript binding (inherited from Javascript base class)
* @param scriptName Name of the function to create in browser global scope
*/
public void addJsBinding(String scriptName);
/**
* Remove JavaScript binding (inherited from Javascript base class)
* @param scriptName Name of the binding function to remove
*/
public void removeJsBinding(String scriptName);
/**
* Add listener for binding calls (inherited from Javascript base class)
* @param listener Consumer to handle binding invocations with payload
*/
public void addBindingCalledListener(Consumer<String> listener);Usage Examples:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
// Create JavaScript domain
v115Javascript javascript = new v115Javascript(devTools);
// Set up data collection from browser
CompletableFuture<String> dataFuture = new CompletableFuture<>();
// Add binding and listener
javascript.addJsBinding("sendDataToJava");
javascript.addBindingCalledListener(payload -> {
System.out.println("Received from browser: " + payload);
dataFuture.complete(payload);
});
// Navigate and inject collection script
driver.get("https://example.com");
driver.executeScript("""
// Collect page data and send to Java
const data = {
title: document.title,
links: Array.from(document.links).length,
images: Array.from(document.images).length,
scripts: Array.from(document.scripts).length,
timestamp: Date.now()
};
// Send to Java via binding
window.sendDataToJava(JSON.stringify(data));
""");
// Wait for data from browser
try {
String pageData = dataFuture.get(5, TimeUnit.SECONDS);
System.out.println("Page analysis complete: " + pageData);
} catch (Exception e) {
System.err.println("Timeout waiting for page data");
}
// Clean up
javascript.removeJsBinding("sendDataToJava");Control JavaScript domain lifecycle and disable functionality when needed.
/**
* Disable JavaScript domain (inherited from Javascript base class)
* Stops runtime and page domains, removes all bindings and scripts
*/
public void disable();Usage Examples:
// JavaScript functionality is automatically enabled when used
javascript.addJsBinding("myBinding");
ScriptId script = javascript.pin("myScript", "console.log('loaded');");
// Disable when automation is complete
javascript.disable();Implement request/response communication using bindings and promises:
// Java side - set up request handler
javascript.addJsBinding("handleRequest");
Map<String, CompletableFuture<String>> pendingRequests = new ConcurrentHashMap<>();
javascript.addBindingCalledListener(payload -> {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode request = mapper.readTree(payload);
String requestId = request.get("id").asText();
String operation = request.get("operation").asText();
// Process request
String response = processRequest(operation, request.get("data"));
// Send response back to browser
driver.executeScript("""
if (window.pendingRequests && window.pendingRequests['%s']) {
window.pendingRequests['%s'].resolve('%s');
delete window.pendingRequests['%s'];
}
""".formatted(requestId, requestId, response, requestId));
} catch (Exception e) {
System.err.println("Error processing request: " + e.getMessage());
}
});
// Inject client-side request helper
javascript.pin("requestHelper", """
window.pendingRequests = {};
window.sendRequest = function(operation, data) {
const requestId = 'req_' + Date.now() + '_' + Math.random();
return new Promise((resolve, reject) => {
window.pendingRequests[requestId] = { resolve, reject };
const request = {
id: requestId,
operation: operation,
data: data
};
window.handleRequest(JSON.stringify(request));
// Timeout after 10 seconds
setTimeout(() => {
if (window.pendingRequests[requestId]) {
delete window.pendingRequests[requestId];
reject(new Error('Request timeout'));
}
}, 10000);
});
};
""");Broadcast events from Java to all pages:
// Set up event broadcasting
javascript.pin("eventSystem", """
window.eventHandlers = {};
window.addEventListener = function(eventType, handler) {
if (!window.eventHandlers[eventType]) {
window.eventHandlers[eventType] = [];
}
window.eventHandlers[eventType].push(handler);
};
window.broadcastEvent = function(eventType, data) {
const handlers = window.eventHandlers[eventType] || [];
handlers.forEach(handler => {
try {
handler(data);
} catch (e) {
console.error('Event handler error:', e);
}
});
};
""");
// Broadcast events from Java
public void broadcastEvent(String eventType, Object data) {
String script = String.format(
"window.broadcastEvent('%s', %s);",
eventType,
objectToJson(data)
);
driver.executeScript(script);
}Binding calls can fail if the browser context is invalid or the binding doesn't exist. The system provides graceful degradation:
// Bindings are automatically cleaned up when pages navigate
// Always check binding availability before use
driver.executeScript("""
if (typeof window.myBinding === 'function') {
window.myBinding('data');
} else {
console.warn('Binding not available');
}
""");Script injection can fail if the page domain is not enabled or if the script is malformed:
try {
ScriptId script = javascript.pin("myScript", "invalid javascript syntax");
} catch (Exception e) {
System.err.println("Script injection failed: " + e.getMessage());
}Install with Tessl CLI
npx tessl i tessl/maven-org-seleniumhq-selenium--selenium-devtools-v115