This is the multi-page printable view of this section.
Click here to print.
Return to the regular view of this page.
BiDirectional functionality
Selenium is working with browser vendors to create the
WebDriver BiDirectional Protocol
as a means to provide a stable, cross-browser API that uses the bidirectional
functionality useful for both browser automation generally and testing specifically.
Before now, users seeking this functionality have had to rely on CDP (Chrome DevTools Protocol)
with all of its frustrations and limitations.
The traditional WebDriver model of strict request/response commands will be supplemented
with the ability to stream events from the user agent to the controlling software via WebSockets,
better matching the evented nature of the browser DOM.
As it is not a good idea to tie your tests to a specific version of any browser, the
Selenium project recommends using WebDriver BiDi wherever possible.
While the specification is in works, the browser vendors are parallely implementing
the WebDriver BiDirectional Protocol.
Refer web-platform-tests dashboard
to see how far along the browser vendors are.
Selenium is trying to keep up with the browser vendors and has started implementing W3C BiDi APIs.
The goal is to ensure APIs are W3C compliant and uniform among the different language bindings.
However, until the specification and corresponding Selenium implementation is complete there are many useful things that
CDP offers. Selenium offers some useful helper classes that use CDP.
1 - BiDirectional API (CDP implementation)
The following list of APIs will be growing as the Selenium
project works through supporting real world use cases. If there
is additional functionality you’d like to see, please raise a
feature request.
Register Basic Auth
Some applications make use of browser authentication to secure pages.
With Selenium, you can automate the input of basic auth credentials whenever they arise.
Predicate<URI> uriPredicate = uri -> uri.getHost().contains("your-domain.com");
((HasAuthentication) driver).register(uriPredicate, UsernameAndPassword.of("admin", "password"));
driver.get("https://your-domain.com/login");
NetworkAuthenticationHandler handler = new NetworkAuthenticationHandler()
{
UriMatcher = (d) => d.Host.Contains("your-domain.com"),
Credentials = new PasswordCredentials("admin", "password")
};
INetwork networkInterceptor = driver.Manage().Network;
networkInterceptor.AddAuthenticationHandler(handler);
await networkInterceptor.StartMonitoring();
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
begin
driver.devtools.new
driver.register(username: 'username', password: 'password')
driver.get '<your site url>'
ensure
driver.quit
end
const {Builder} = require('selenium-webdriver');
(async function example() {
try {
let driver = await new Builder()
.forBrowser('chrome')
.build();
const pageCdpConnection = await driver.createCDPConnection('page');
await driver.register('username', 'password', pageCdpConnection);
await driver.get('https://the-internet.herokuapp.com/basic_auth');
await driver.quit();
}catch (e){
console.log(e)
}
}())
val uriPredicate = Predicate { uri: URI ->
uri.host.contains("your-domain.com")
}
(driver as HasAuthentication).register(uriPredicate, UsernameAndPassword.of("admin", "password"))
driver.get("https://your-domain.com/login")
Mutation Observation
Mutation Observation is the ability to capture events via
WebDriver BiDi when there are DOM mutations on a specific
element in the DOM.
ChromeDriver driver = new ChromeDriver();
AtomicReference<DomMutationEvent> seen = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
((HasLogEvents) driver).onLogEvent(domMutation(mutation -> {
seen.set(mutation);
latch.countDown();
}));
driver.get("https://www.google.com");
WebElement span = driver.findElement(By.cssSelector("span"));
((JavascriptExecutor) driver).executeScript("arguments[0].setAttribute('cheese', 'gouda');", span);
assertThat(latch.await(10, SECONDS), is(true));
assertThat(seen.get().getAttributeName(), is("cheese"));
assertThat(seen.get().getCurrentValue(), is("gouda"));
driver.quit();
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Chrome()
async with driver.log.mutation_events() as event:
pages.load("dynamic.html")
driver.find_element(By.ID, "reveal").click()
WebDriverWait(driver, 5)\
.until(EC.visibility_of(driver.find_element(By.ID, "revealed")))
assert event["attribute_name"] == "style"
assert event["current_value"] == ""
assert event["old_value"] == "display:none;"
List<DomMutationData> attributeValueChanges = new List<DomMutationData>();
DefaultWait<List<DomMutationData>> wait = new DefaultWait<List<DomMutationData>>(attributeValueChanges);
wait.Timeout = TimeSpan.FromSeconds(3);
IJavaScriptEngine monitor = new JavaScriptEngine(driver);
monitor.DomMutated += (sender, e) =>
{
attributeValueChanges.Add(e.AttributeData);
};
await monitor.StartEventMonitoring();
driver.Navigate().GoToUrl("http://www.google.com");
IWebElement span = driver.FindElement(By.CssSelector("span"));
await monitor.EnableDomMutationMonitoring();
((IJavaScriptExecutor) driver).ExecuteScript("arguments[0].setAttribute('cheese', 'gouda');", span);
wait.Until((list) => list.Count > 0);
Console.WriteLine("Found {0} DOM mutation events", attributeValueChanges.Count);
foreach(var record in attributeValueChanges)
{
Console.WriteLine("Attribute name: {0}", record.AttributeName);
Console.WriteLine("Attribute value: {0}", record.AttributeValue);
}
await monitor.DisableDomMutationMonitoring();
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :firefox
begin
driver.on_log_event(:mutation) { |mutation| mutations.push(mutation) }
driver.navigate.to url_for('dynamic.html')
driver.find_element(id: 'reveal').click
wait.until { mutations.any? }
mutation = mutations.first
expect(mutation.element).to eq(driver.find_element(id: 'revealed'))
expect(mutation.attribute_name).to eq('style')
expect(mutation.current_value).to eq('')
expect(mutation.old_value).to eq('display:none;')
ensure
driver.quit
end
const {Builder, until} = require('selenium-webdriver');
const assert = require("assert");
(async function example() {
try {
let driver = await new Builder()
.forBrowser('chrome')
.build();
const cdpConnection = await driver.createCDPConnection('page');
await driver.logMutationEvents(cdpConnection, event => {
assert.deepStrictEqual(event['attribute_name'], 'style');
assert.deepStrictEqual(event['current_value'], "");
assert.deepStrictEqual(event['old_value'], "display:none;");
});
await driver.get('dynamic.html');
await driver.findElement({id: 'reveal'}).click();
let revealed = driver.findElement({id: 'revealed'});
await driver.wait(until.elementIsVisible(revealed), 5000);
await driver.quit();
}catch (e){
console.log(e)
}
}())
Listen to console.log
events
Listen to the console.log
events and register callbacks to process the event.
ChromeDriver driver = new ChromeDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Log.enable());
devTools.addListener(Log.entryAdded(),
logEntry -> {
System.out.println("log: "+logEntry.getText());
System.out.println("level: "+logEntry.getLevel());
});
driver.get("http://the-internet.herokuapp.com/broken_images");
// Check the terminal output for the browser console messages.
driver.quit();
import trio
from selenium import webdriver
from selenium.webdriver.common.log import Log
async def printConsoleLogs():
chrome_options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
driver.get("http://www.google.com")
async with driver.bidi_connection() as session:
log = Log(driver, session)
from selenium.webdriver.common.bidi.console import Console
async with log.add_listener(Console.ALL) as messages:
driver.execute_script("console.log('I love cheese')")
print(messages["message"])
driver.quit()
trio.run(printConsoleLogs)
IJavaScriptEngine monitor = new JavaScriptEngine(driver);
List<string> consoleMessages = new List<string>();
monitor.JavaScriptConsoleApiCalled += (sender, e) =>
{
Console.WriteLine("Log: {0}", e.MessageContent);
};
await monitor.StartEventMonitoring();
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
begin
driver.get 'http://www.google.com'
logs = []
driver.on_log_event(:console) do |event|
logs.push(event)
puts logs.length
end
driver.execute_script('console.log("here")')
ensure
driver.quit
end
const {Builder} = require('selenium-webdriver');
(async () => {
try {
let driver = new Builder()
.forBrowser('chrome')
.build();
const cdpConnection = await driver.createCDPConnection('page');
await driver.onLogEvent(cdpConnection, function (event) {
console.log(event['args'][0]['value']);
});
await driver.executeScript('console.log("here")');
await driver.quit();
}catch (e){
console.log(e);
}
})()
fun kotlinConsoleLogExample() {
val driver = ChromeDriver()
val devTools = driver.devTools
devTools.createSession()
val logConsole = { c: ConsoleEvent -> print("Console log message is: " + c.messages)}
devTools.domains.events().addConsoleListener(logConsole)
driver.get("https://www.google.com")
val executor = driver as JavascriptExecutor
executor.executeScript("console.log('Hello World')")
val input = driver.findElement(By.name("q"))
input.sendKeys("Selenium 4")
input.sendKeys(Keys.RETURN)
driver.quit()
}
Listen to JS Exceptions
Listen to the JS Exceptions
and register callbacks to process the exception details.
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.devtools.DevTools;
public void jsExceptionsExample() {
ChromeDriver driver = new ChromeDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
List<JavascriptException> jsExceptionsList = new ArrayList<>();
Consumer<JavascriptException> addEntry = jsExceptionsList::add;
devTools.getDomains().events().addJavascriptExceptionListener(addEntry);
driver.get("<your site url>");
WebElement link2click = driver.findElement(By.linkText("<your link text>"));
((JavascriptExecutor) driver).executeScript("arguments[0].setAttribute(arguments[1], arguments[2]);",
link2click, "onclick", "throw new Error('Hello, world!')");
link2click.click();
for (JavascriptException jsException : jsExceptionsList) {
System.out.println("JS exception message: " + jsException.getMessage());
System.out.println("JS exception system information: " + jsException.getSystemInformation());
jsException.printStackTrace();
}
}
async def catchJSException():
chrome_options = webdriver.ChromeOptions()
driver = webdriver.Chrome()
async with driver.bidi_connection() as session:
driver.get("<your site url>")
log = Log(driver, session)
async with log.add_js_error_listener() as messages:
# Operation on the website that throws an JS error
print(messages)
driver.quit()
List<string> exceptionMessages = new List<string>();
IJavaScriptEngine monitor = new JavaScriptEngine(driver);
monitor.JavaScriptExceptionThrown += (sender, e) =>
{
exceptionMessages.Add(e.Message);
};
await monitor.StartEventMonitoring();
driver.Navigate.GoToUrl("<your site url>");
IWebElement link2click = driver.FindElement(By.LinkText("<your link text>"));
((IJavaScriptExecutor) driver).ExecuteScript("arguments[0].setAttribute(arguments[1], arguments[2]);",
link2click, "onclick", "throw new Error('Hello, world!')");
link2click.Click();
foreach (string message in exceptionMessages)
{
Console.WriteLine("JS exception message: {0}", message);
}
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
begin
driver.get '<your-site-url>'
exceptions = []
driver.on_log_event(:exception) do |event|
exceptions.push(event)
puts exceptions.length
end
#Actions causing JS exceptions
ensure
driver.quit
end
const {Builder, By} = require('selenium-webdriver');
(async () => {
try {
let driver = new Builder()
.forBrowser('chrome')
.build();
const cdpConnection = await driver.createCDPConnection('page')
await driver.onLogException(cdpConnection, function (event) {
console.log(event['exceptionDetails']);
})
await driver.get('https://the-internet.herokuapp.com');
const link = await driver.findElement(By.linkText('Checkboxes'));
await driver.executeScript("arguments[0].setAttribute(arguments[1], arguments[2]);", link, "onclick","throw new Error('Hello, world!')");
await link.click();
await driver.quit();
}catch (e){
console.log(e);
}
})()
fun kotlinJsErrorListener() {
val driver = ChromeDriver()
val devTools = driver.devTools
devTools.createSession()
val logJsError = { j: JavascriptException -> print("Javascript error: '" + j.localizedMessage + "'.") }
devTools.domains.events().addJavascriptExceptionListener(logJsError)
driver.get("https://www.google.com")
val link2click = driver.findElement(By.name("q"))
(driver as JavascriptExecutor).executeScript(
"arguments[0].setAttribute(arguments[1], arguments[2]);",
link2click, "onclick", "throw new Error('Hello, world!')"
)
link2click.click()
driver.quit()
}
Network Interception
If you want to capture network events coming into the browser and you want manipulate them you are able to do
it with the following examples.
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.devtools.HasDevTools;
import org.openqa.selenium.devtools.NetworkInterceptor;
import org.openqa.selenium.remote.http.Contents;
import org.openqa.selenium.remote.http.Filter;
import org.openqa.selenium.remote.http.HttpResponse;
import org.openqa.selenium.remote.http.Route;
NetworkInterceptor interceptor = new NetworkInterceptor(
driver,
Route.matching(req -> true)
.to(() -> req -> new HttpResponse()
.setStatus(200)
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
.setContent(utf8String("Creamy, delicious cheese!"))));
driver.get("https://example-sausages-site.com");
String source = driver.getPageSource();
assertThat(source).contains("delicious cheese!");
Currently unavailable in python due the inability to mix certain async and sync commands
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
driver.intercept do |request, &continue|
uri = URI(request.url)
if uri.path.end_with?('one.js')
uri.path = '/devtools_request_interception_test/two.js'
request.url = uri.to_s
end
continue.call(request)
end
driver.navigate.to url_for('devToolsRequestInterceptionTest.html')
driver.find_element(tag_name: 'button').click
expect(driver.find_element(id: 'result').text).to eq('two')
const connection = await driver.createCDPConnection('page')
let url = fileServer.whereIs("/cheese")
let httpResponse = new HttpResponse(url)
httpResponse.addHeaders("Content-Type", "UTF-8")
httpResponse.body = "sausages"
await driver.onIntercept(connection, httpResponse, async function () {
let body = await driver.getPageSource()
assert.strictEqual(body.includes("sausages"), true, `Body contains: ${body}`)
})
driver.get(url)
val driver = ChromeDriver()
val interceptor = new NetworkInterceptor(
driver,
Route.matching(req -> true)
.to(() -> req -> new HttpResponse()
.setStatus(200)
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
.setContent(utf8String("Creamy, delicious cheese!"))))
driver.get(appServer.whereIs("/cheese"))
String source = driver.getPageSource()
2 - RemoteWebDriver BiDirectional API (CDP implementation)
The following examples demonstrate how to leverage BiDi APIs with Remote WebDriver.
Register Basic Auth
Some applications make use of browser authentication to secure pages.
With Selenium, you can automate the input of basic auth credentials whenever they arise.
AtomicReference<DevTools> devToolsAtomicReference = new AtomicReference<>();
driver = new Augmenter()
.addDriverAugmentation("chrome",
HasAuthentication.class,
(caps, exec) -> (whenThisMatches, useTheseCredentials) -> {
devToolsAtomicReference.get()
.createSessionIfThereIsNotOne();
devToolsAtomicReference.get().getDomains()
.network()
.addAuthHandler(whenThisMatches,
useTheseCredentials);
}).augment(driver);
DevTools devTools = ((HasDevTools) driver).getDevTools();
devTools.createSession();
devToolsAtomicReference.set(devTools);
((HasAuthentication) driver).
register(UsernameAndPassword.of("admin", "admin"));
Mutation Observation
Mutation Observation is the ability to capture events via
WebDriver BiDi when there are DOM mutations on a specific
element in the DOM.
AtomicReference<DomMutationEvent> seen = new AtomicReference<>();
AtomicReference<WebDriver> augmentedDriver = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
driver = new Augmenter()
.addDriverAugmentation("chrome",
HasLogEvents.class,
(caps, exec) -> new HasLogEvents() {
@Override
public <X> void onLogEvent(EventType<X> kind) {
kind.initializeListener(augmentedDriver.get());
}
}).augment(driver);
DevTools devTools = ((HasDevTools) driver).getDevTools();
devTools.createSession();
augmentedDriver.set(driver);
((HasLogEvents) driver).onLogEvent(domMutation(mutation -> {
if ("cheese".equals(mutation.getAttributeName())) {
seen.set(mutation);
latch.countDown();
}
}));
Listen to console.log
events
Listen to the console.log
events and register callbacks to process the event.
CountDownLatch latch = new CountDownLatch(4);
driver = new Augmenter().augment(driver);
DevTools devTools = ((HasDevTools) driver).getDevTools();
devTools.createSession();
devTools.send(org.openqa.selenium.devtools.v85.runtime.Runtime.enable());
devTools.send(Log.enable());
devTools.addListener(Log.entryAdded(),
logEntry -> {
System.out.println("log: " + logEntry.getText());
System.out.println("level: " + logEntry.getLevel());
latch.countDown();
});
devTools.addListener(org.openqa.selenium.devtools.v85.runtime.Runtime.consoleAPICalled(),
consoleLog -> System.out.println("Type: " + consoleLog.getType()));
Actions causing JS exceptions
driver = new Augmenter().augment(driver);
DevTools devTools = ((HasDevTools) driver).getDevTools();
devTools.createSession();
List<JavascriptException> jsExceptionsList = new ArrayList<>();
devTools.getDomains().events().addJavascriptExceptionListener(jsExceptionsList::add);
CompletableFuture<JavascriptException> futureJsExc = new CompletableFuture<>();
devTools.getDomains().events().addJavascriptExceptionListener(futureJsExc::complete);
Network Interception
If you want to capture network events coming into the browser and you want manipulate them you are able to do
it with the following examples.
driver = new Augmenter().augment(driver);
DevTools devTools = ((HasDevTools) driver).getDevTools();
devTools.createSession();
try (NetworkInterceptor interceptor = new NetworkInterceptor(
driver,
Route.matching(req -> req.getUri().contains("google"))
.to(() -> req -> new HttpResponse()
.setStatus(200)
.addHeader("Content-Type", MediaType.HTML_UTF_8.toString())
.setContent(utf8String("Creamy, delicious cheese!"))))) {
3 - Chrome DevTools
While Selenium 4 provides direct access to the Chrome DevTools Protocol (CDP), it is
highly encouraged that you use the WebDriver Bidi APIs instead.
Many browsers provide “DevTools” – a set of tools that are integrated with the browser that
developers can use to debug web apps and explore the performance of their pages. Google Chrome’s
DevTools make use of a protocol called the Chrome DevTools Protocol (or “CDP” for short).
As the name suggests, this is not designed for testing, nor to have a stable API, so functionality
is highly dependent on the version of the browser.
WebDriver Bidi is the next generation of the W3C WebDriver protocol and aims to provide a stable API
implemented by all browsers, but it’s not yet complete. Until it is, Selenium provides access to
the CDP for those browsers that implement it (such as Google Chrome, or Microsoft Edge, and
Firefox), allowing you to enhance your tests in interesting ways. Some examples of what you can
do with it are given below.
Emulate Geo Location
Some applications have different features and functionalities across different
locations. Automating such applications is difficult because it is hard to emulate
the geo-locations in the browser using Selenium. But with the help of Devtools,
we can easily emulate them. Below code snippet demonstrates that.
ChromeDriver driver = new ChromeDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Emulation.setGeolocationOverride(Optional.of(52.5043),
Optional.of(13.4501),
Optional.of(1)));
driver.get("https://my-location.org/");
driver.quit();
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
def geoLocationTest():
driver = webdriver.Chrome()
Map_coordinates = dict({
"latitude": 41.8781,
"longitude": -87.6298,
"accuracy": 100
})
driver.execute_cdp_cmd("Emulation.setGeolocationOverride", Map_coordinates)
driver.get("<your site url>")
using System.Threading.Tasks;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.DevTools;
// Replace the version to match the Chrome version
using OpenQA.Selenium.DevTools.V87.Emulation;
namespace dotnet_test {
class Program {
public static void Main(string[] args) {
GeoLocation().GetAwaiter().GetResult();
}
public static async Task GeoLocation() {
ChromeDriver driver = new ChromeDriver();
DevToolsSession devToolsSession = driver.CreateDevToolsSession();
var geoLocationOverrideCommandSettings = new SetGeolocationOverrideCommandSettings();
geoLocationOverrideCommandSettings.Latitude = 51.507351;
geoLocationOverrideCommandSettings.Longitude = -0.127758;
geoLocationOverrideCommandSettings.Accuracy = 1;
await devToolsSession
.GetVersionSpecificDomains<OpenQA.Selenium.DevTools.V87.DevToolsSessionDomains>()
.Emulation
.SetGeolocationOverride(geoLocationOverrideCommandSettings);
driver.Url = "<your site url>";
}
}
}
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
begin
# Latitude and longitude of Tokyo, Japan
coordinates = { latitude: 35.689487,
longitude: 139.691706,
accuracy: 100 }
driver.execute_cdp('Emulation.setGeolocationOverride', coordinates)
driver.get 'https://www.google.com/search?q=selenium'
ensure
driver.quit
end
const { By, Key, Browser} = require('selenium-webdriver');
const { suite } = require('selenium-webdriver/testing');
const assert = require("assert");
suite(function(env) {
describe('Emulate geolocation', function() {
let driver;
before(async function() {
driver = await env.builder().build();
});
after(() => driver.quit());
it('Emulate coordinates of Tokyo', async function() {
const cdpConnection = await driver.createCDPConnection('page');
// Latitude and longitude of Tokyo, Japan
const coordinates = {
latitude: 35.689487,
longitude: 139.691706,
accuracy: 100,
};
await cdpConnection.execute(
"Emulation.setGeolocationOverride",
coordinates
);
await driver.get("https://kawasaki-india.com/dealer-locator/");
});
});
},{ browsers: [Browser.CHROME, Browser.FIREFOX]});
import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.devtools.DevTools
fun main() {
val driver = ChromeDriver()
val coordinates : HashMap<String, Any> = HashMap<String, Any> ()
coordinates.put("latitude", 50.2334)
coordinates.put("longitude", 0.2334)
coordinates.put("accuracy", 1)
driver.executeCdpCommand("Emulation.setGeolocationOverride", coordinates)
driver.get("https://www.google.com")
}
Emulate Geo Location with the Remote WebDriver:
ChromeOptions chromeOptions = new ChromeOptions();
WebDriver driver = new RemoteWebDriver(new URL("<grid-url>"), chromeOptions);
driver = new Augmenter().augment(driver);
DevTools devTools = ((HasDevTools) driver).getDevTools();
devTools.createSession();
devTools.send(Emulation.setGeolocationOverride(Optional.of(52.5043),
Optional.of(13.4501),
Optional.of(1)));
driver.get("https://my-location.org/");
driver.quit();
from selenium import webdriver
#Replace the version to match the Chrome version
import selenium.webdriver.common.devtools.v93 as devtools
async def geoLocationTest():
chrome_options = webdriver.ChromeOptions()
driver = webdriver.Remote(
command_executor='<grid-url>',
options=chrome_options
)
async with driver.bidi_connection() as session:
cdpSession = session.session
await cdpSession.execute(devtools.emulation.set_geolocation_override(latitude=41.8781,longitude=-87.6298,accuracy=100))
driver.get("https://my-location.org/")
driver.quit()
using System.Threading.Tasks;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.DevTools;
// Replace the version to match the Chrome version
using OpenQA.Selenium.DevTools.V87.Emulation;
namespace dotnet_test {
class Program {
public static void Main(string[] args) {
GeoLocation().GetAwaiter().GetResult();
}
public static async Task GeoLocation() {
ChromeOptions chromeOptions = new ChromeOptions();
RemoteWebDriver driver = new RemoteWebDriver(new Uri("<grid-url>"), chromeOptions);
DevToolsSession devToolsSession = driver.CreateDevToolsSession();
var geoLocationOverrideCommandSettings = new SetGeolocationOverrideCommandSettings();
geoLocationOverrideCommandSettings.Latitude = 51.507351;
geoLocationOverrideCommandSettings.Longitude = -0.127758;
geoLocationOverrideCommandSettings.Accuracy = 1;
await devToolsSession
.GetVersionSpecificDomains<OpenQA.Selenium.DevTools.V87.DevToolsSessionDomains>()
.Emulation
.SetGeolocationOverride(geoLocationOverrideCommandSettings);
driver.Url = "https://my-location.org/";
}
}
}
driver = Selenium::WebDriver.for(
:remote,
:url => "<grid-url>",
:capabilities => :chrome)
begin
# Latitude and longitude of Tokyo, Japan
coordinates = { latitude: 35.689487,
longitude: 139.691706,
accuracy: 100 }
devToolsSession = driver.devtools
devToolsSession.send_cmd('Emulation.setGeolocationOverride', coordinates)
driver.get 'https://my-location.org/'
puts res
ensure
driver.quit
end
const webdriver = require('selenium-webdriver');
const BROWSER_NAME = webdriver.Browser.CHROME;
async function getDriver() {
return new webdriver.Builder()
.usingServer('<grid-url>')
.forBrowser(BROWSER_NAME)
.build();
}
async function executeCDPCommands () {
let driver = await getDriver();
await driver.get("<your site url>");
const cdpConnection = await driver.createCDPConnection('page');
//Latitude and longitude of Tokyo, Japan
const coordinates = {
latitude: 35.689487,
longitude: 139.691706,
accuracy: 100,
};
await cdpConnection.execute(
"Emulation.setGeolocationOverride",
coordinates
);
await driver.quit();
}
executeCDPCommands();
import org.openqa.selenium.WebDriver
import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.devtools.HasDevTools
// Replace the version to match the Chrome version
import org.openqa.selenium.devtools.v91.emulation.Emulation
import org.openqa.selenium.remote.Augmenter
import org.openqa.selenium.remote.RemoteWebDriver
import java.net.URL
import java.util.Optional
fun main() {
val chromeOptions = ChromeOptions()
var driver: WebDriver = RemoteWebDriver(URL("<grid-url>"), chromeOptions)
driver = Augmenter().augment(driver)
val devTools = (driver as HasDevTools).devTools
devTools.createSession()
devTools.send(
Emulation.setGeolocationOverride(
Optional.of(52.5043),
Optional.of(13.4501),
Optional.of(1)
)
)
driver["https://my-location.org/"]
driver.quit()
}
Override Device Mode
Using Selenium’s integration with CDP, one can override the current device
mode and simulate a new mode. Width, height, mobile, and deviceScaleFactor
are required parameters. Optional parameters include scale, screenWidth,
screenHeight, positionX, positionY, dontSetVisible, screenOrientation, viewport, and displayFeature.
ChromeDriver driver = new ChromeDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
// iPhone 11 Pro dimensions
devTools.send(Emulation.setDeviceMetricsOverride(375,
812,
50,
true,
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.empty()));
driver.get("https://selenium.dev/");
driver.quit();
from selenium import webdriver
driver = webdriver.Chrome()
// iPhone 11 Pro dimensions
set_device_metrics_override = dict({
"width": 375,
"height": 812,
"deviceScaleFactor": 50,
"mobile": True
})
driver.execute_cdp_cmd('Emulation.setDeviceMetricsOverride', set_device_metrics_override)
driver.get("<your site url>")
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.DevTools;
using System.Threading.Tasks;
using OpenQA.Selenium.DevTools.V91.Emulation;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using DevToolsSessionDomains = OpenQA.Selenium.DevTools.V91.DevToolsSessionDomains;
namespace Selenium4Sample {
public class ExampleDevice {
protected IDevToolsSession session;
protected IWebDriver driver;
protected DevToolsSessionDomains devToolsSession;
public async Task DeviceModeTest() {
new DriverManager().SetUpDriver(new ChromeConfig());
ChromeOptions chromeOptions = new ChromeOptions();
//Set ChromeDriver
driver = new ChromeDriver();
//Get DevTools
IDevTools devTools = driver as IDevTools;
//DevTools Session
session = devTools.GetDevToolsSession();
var deviceModeSetting = new SetDeviceMetricsOverrideCommandSettings();
deviceModeSetting.Width = 600;
deviceModeSetting.Height = 1000;
deviceModeSetting.Mobile = true;
deviceModeSetting.DeviceScaleFactor = 50;
await session
.GetVersionSpecificDomains < OpenQA.Selenium.DevTools.V91.DevToolsSessionDomains > ()
.Emulation
.SetDeviceMetricsOverride(deviceModeSetting);
driver.Url = "<your site url>";
}
}
}
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
begin
metrics = { width: 300,
height: 200,
mobile: true,
deviceScaleFactor: 50 }
driver.execute_cdp('Emulation.setDeviceMetricsOverride', metrics)
driver.get 'https://www.google.com'
ensure
driver.quit
end
const {Builder} = require('selenium-webdriver');
const firefox = require('selenium-webdriver/firefox');
const options = new firefox.Options();
// enable debugger for CDP
options.enableDebugger();
(async function example() {
try {
let driver = await new Builder().forBrowser('firefox').setFirefoxOptions(options).build();
const pageCdpConnection = await driver.createCDPConnection('page');
const metrics = {
width: 300,
height: 200,
deviceScaleFactor: 50,
mobile: true,
};
await pageCdpConnection.execute(
"Emulation.setDeviceMetricsOverride",
metrics
);
await driver.get("https://www.google.com");
await driver.quit();
} catch (e) {
console.log(e);
}
})();
fun kotlinOverridDeviceMode() {
val driver = ChromeDriver()
val deviceMetrics: Map<String, Any> = object : HashMap<String, Any>() {
init {
put("width", 600)
put("height", 1000)
put("mobile", true)
put("deviceScaleFactor", 50)
}
}
driver.executeCdpCommand("Emulation.setDeviceMetricsOverride", deviceMetrics)
driver.get("https://www.google.com")
driver.quit()
}
Collect various performance metrics while navigating the application.
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.devtools.DevTools;
public void performanceMetricsExample() {
ChromeDriver driver = new ChromeDriver();
DevTools devTools = driver.getDevTools();
devTools.createSession();
devTools.send(Performance.enable(Optional.empty()));
List<Metric> metricList = devTools.send(Performance.getMetrics());
driver.get("https://google.com");
driver.quit();
for(Metric m : metricList) {
System.out.println(m.getName() + " = " + m.getValue());
}
}
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://www.duckduckgo.com')
driver.execute_cdp_cmd('Performance.enable', {})
t = driver.execute_cdp_cmd('Performance.getMetrics', {})
print(t)
driver.quit()
// File must contain the following using statements
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.DevTools;
// We must use a version-specific set of domains
using OpenQA.Selenium.DevTools.V94.Performance;
public async Task PerformanceMetricsExample()
{
IWebDriver driver = new ChromeDriver();
IDevTools devTools = driver as IDevTools;
DevToolsSession session = devTools.GetDevToolsSession();
await session.SendCommand<EnableCommandSettings>(new EnableCommandSettings());
var metricsResponse =
await session.SendCommand<GetMetricsCommandSettings, GetMetricsCommandResponse>(
new GetMetricsCommandSettings());
driver.Navigate().GoToUrl("http://www.google.com");
driver.Quit();
var metrics = metricsResponse.Metrics;
foreach (Metric metric in metrics)
{
Console.WriteLine("{0} = {1}", metric.Name, metric.Value);
}
}
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
begin
driver.get 'https://www.duckduckgo.com'
driver.execute_cdp('Performance.enable', {})
metrics = driver.execute_cdp('Performance.getMetrics', {})
puts metrics
ensure
driver.quit
end
await driver.get("https://www.duckduckgo.com");
await driver.sendAndGetDevToolsCommand('Performance.enable')
let result = await driver.sendAndGetDevToolsCommand('Performance.getMetrics')
console.log(result)
await driver.quit();
val driver = ChromeDriver()
val devTools = driver.devTools
devTools.createSession()
devTools.send(Performance.enable(Optional.empty()))
val metricList: List<Metric> = devTools.send(Performance.getMetrics())
driver["https://google.com"]
driver.quit()
for (m in metricList) {
println(m.name.toString() + " = " + m.value)
}
4 - BiDirectional API (W3C compliant)
The following list of APIs will be growing as the WebDriver BiDirectional Protocol grows
and browser vendors implement the same.
Additionally, Selenium will try to support real-world use cases that internally use a combination of W3C BiDi protocol APIs.
If there is additional functionality you’d like to see, please raise a
feature request.
4.1 - Browsing Context
This section contains the APIs related to browsing context commands.
Open a new window
Creates a new browsing context in a new window.
BrowsingContext browsingContext = new BrowsingContext(driver, WindowType.WINDOW);
Open a new tab
Creates a new browsing context in a new tab.
BrowsingContext browsingContext = new BrowsingContext(driver, WindowType.TAB);
Use existing window handle
Creates a browsing context for the existing tab/window to run commands.
String id = driver.getWindowHandle();
BrowsingContext browsingContext = new BrowsingContext(driver, id);
Open a window with a reference browsing context
A reference browsing context is a top-level browsing context.
The API allows to pass the reference browsing context, which is used to create a new window. The implementation is operating system specific.
BrowsingContext
browsingContext =
new BrowsingContext(driver, WindowType.WINDOW, driver.getWindowHandle());
Open a tab with a reference browsing context
A reference browsing context is a top-level browsing context.
The API allows to pass the reference browsing context, which is used to create a new tab. The implementation is operating system specific.
BrowsingContext
browsingContext =
new BrowsingContext(driver, WindowType.TAB, driver.getWindowHandle());
Navigate to a URL
BrowsingContext browsingContext = new BrowsingContext(driver, WindowType.TAB);
NavigationResult info = browsingContext.navigate("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html");
Assertions.assertNotNull(browsingContext.getId());
Assertions.assertNull(info.getNavigationId());
Assertions.assertTrue(info.getUrl().contains("/bidi/logEntryAdded.html"));
Navigate to a URL with readiness state
BrowsingContext browsingContext = new BrowsingContext(driver, WindowType.TAB);
NavigationResult info = browsingContext.navigate("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html",
ReadinessState.COMPLETE);
Assertions.assertNotNull(browsingContext.getId());
Assertions.assertNull(info.getNavigationId());
Assertions.assertTrue(info.getUrl().contains("/bidi/logEntryAdded.html"));
Get browsing context tree
Provides a tree of all browsing contexts descending from the parent browsing context, including the parent browsing context.
String referenceContextId = driver.getWindowHandle();
BrowsingContext parentWindow = new BrowsingContext(driver, referenceContextId);
parentWindow.navigate("https://www.selenium.dev/selenium/web/iframes.html", ReadinessState.COMPLETE);
List<BrowsingContextInfo> contextInfoList = parentWindow.getTree();
Assertions.assertEquals(1, contextInfoList.size());
BrowsingContextInfo info = contextInfoList.get(0);
Assertions.assertEquals(1, info.getChildren().size());
Assertions.assertEquals(referenceContextId, info.getId());
Assertions.assertTrue(info.getChildren().get(0).getUrl().contains("formPage.html"));
Get browsing context tree with depth
Provides a tree of all browsing contexts descending from the parent browsing context, including the parent browsing context upto the depth value passed.
String referenceContextId = driver.getWindowHandle();
BrowsingContext parentWindow = new BrowsingContext(driver, referenceContextId);
parentWindow.navigate("https://www.selenium.dev/selenium/web/iframes.html", ReadinessState.COMPLETE);
List<BrowsingContextInfo> contextInfoList = parentWindow.getTree(0);
Assertions.assertEquals(1, contextInfoList.size());
BrowsingContextInfo info = contextInfoList.get(0);
Assertions.assertNull(info.getChildren()); // since depth is 0
Assertions.assertEquals(referenceContextId, info.getId());
Get All Top level browsing contexts
BrowsingContext window1 = new BrowsingContext(driver, driver.getWindowHandle());
BrowsingContext window2 = new BrowsingContext(driver, WindowType.WINDOW);
List<BrowsingContextInfo> contextInfoList = window1.getTopLevelContexts();
Assertions.assertEquals(2, contextInfoList.size());
Close a tab/window
BrowsingContext window1 = new BrowsingContext(driver, WindowType.WINDOW);
BrowsingContext window2 = new BrowsingContext(driver, WindowType.WINDOW);
window2.close();
Assertions.assertThrows(BiDiException.class, window2::getTree);
4.2 - Log
This section contains the APIs related to logging.
Listen to console.log
events
Listen to the console.log
events and register callbacks to process the event.
try (LogInspector logInspector = new LogInspector(driver)) {
CompletableFuture<ConsoleLogEntry> future = new CompletableFuture<>();
logInspector.onConsoleLog(future::complete);
driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html");
driver.findElement(By.id("consoleLog")).click();
ConsoleLogEntry logEntry = future.get(5, TimeUnit.SECONDS);
Assertions.assertEquals("Hello, world!", logEntry.getText());
Assertions.assertNull(logEntry.getRealm());
Assertions.assertEquals(1, logEntry.getArgs().size());
Assertions.assertEquals("console", logEntry.getType());
Assertions.assertEquals("log", logEntry.getMethod());
Assertions.assertNull(logEntry.getStackTrace());
}
const inspector = await LogInspector(driver)
await inspector.onConsoleEntry(function (log) {
logEntry = log
})
await driver.get('https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html')
await driver.findElement({ id: 'consoleLog' }).click()
assert.equal(logEntry.text, 'Hello, world!')
assert.equal(logEntry.realm, null)
assert.equal(logEntry.type, 'console')
assert.equal(logEntry.level, 'info')
assert.equal(logEntry.method, 'log')
assert.equal(logEntry.stackTrace, null)
assert.equal(logEntry.args.length, 1)
Listen to JS Exceptions
Listen to the JS Exceptions
and register callbacks to process the exception details.
try (LogInspector logInspector = new LogInspector(driver)) {
CompletableFuture<JavascriptLogEntry> future = new CompletableFuture<>();
logInspector.onJavaScriptException(future::complete);
driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html");
driver.findElement(By.id("jsException")).click();
JavascriptLogEntry logEntry = future.get(5, TimeUnit.SECONDS);
Assertions.assertEquals("Error: Not working", logEntry.getText());
Assertions.assertEquals("javascript", logEntry.getType());
}
const inspector = await LogInspector(driver)
await inspector.onJavascriptException(function (log) {
logEntry = log
})
await driver.get('https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html')
await driver.findElement({ id: 'jsException' }).click()
assert.equal(logEntry.text, 'Error: Not working')
assert.equal(logEntry.type, 'javascript')
assert.equal(logEntry.level, 'error')
Listen to JS Logs
Listen to all JS logs at all levels and register callbacks to process the log.
try (LogInspector logInspector = new LogInspector(driver)) {
CompletableFuture<JavascriptLogEntry> future = new CompletableFuture<>();
logInspector.onJavaScriptLog(future::complete);
driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html");
driver.findElement(By.id("jsException")).click();
JavascriptLogEntry logEntry = future.get(5, TimeUnit.SECONDS);
Assertions.assertEquals("Error: Not working", logEntry.getText());
Assertions.assertEquals("javascript", logEntry.getType());
Assertions.assertEquals(LogLevel.ERROR, logEntry.getLevel());
}