All Projects → sergueik → selenium_cdp

sergueik / selenium_cdp

Licence: MIT license
Selenium 4x, executing Chrome DevTools Protocol commands

Programming Languages

java
68154 projects - #9 most used programming language

Projects that are alternatives of or similar to selenium cdp

Chrome Developer Mode Extension Warning Patcher
⇒ Disable Chrome's Developer Mode Extension Warning Popup & Elision WWW/HTTPS Hiding & Debugging Extension Popup
Stars: ✭ 240 (+627.27%)
Mutual labels:  chromium, edge
flutter-webview-windows
A WebView2-powered Flutter WebView implementation for the Windows platform.
Stars: ✭ 83 (+151.52%)
Mutual labels:  chromium, edge
NativeMessaging
C# Chome Native Messaging Library
Stars: ✭ 55 (+66.67%)
Mutual labels:  chromium, edge
Edge Selenium Tools
An updated EdgeDriver implementation for Selenium 3 with newly-added support for Microsoft Edge (Chromium).
Stars: ✭ 41 (+24.24%)
Mutual labels:  chromium, edge
Netflix 4k Ddplus
MicrosoftEdge(Chromium core) extension to play Netflix in 4K(Restricted)and DDplus audio
Stars: ✭ 128 (+287.88%)
Mutual labels:  chromium, edge
Mue
Fast, open and free-to-use new tab page for modern browsers
Stars: ✭ 56 (+69.7%)
Mutual labels:  chromium, edge
Chrome Charset
An extension used to modify the page default encoding for Chromium 55+ based browsers.
Stars: ✭ 346 (+948.48%)
Mutual labels:  chromium, edge
Extension Create
Create modern cross-browser extensions with no build configuration.
Stars: ✭ 167 (+406.06%)
Mutual labels:  chromium, edge
WebView4Delphi
WebView4Delphi is an open source project created by Salvador Díaz Fau to embed Chromium-based browsers in applications made with Delphi or Lazarus/FPC for Windows.
Stars: ✭ 157 (+375.76%)
Mutual labels:  chromium, edge
docker-node-ci
The Docker image based on the official "node" image optimized for a CI environment
Stars: ✭ 18 (-45.45%)
Mutual labels:  chromium
cefgo
Go bindings for the Chromium Embedded Framework (CEF)
Stars: ✭ 20 (-39.39%)
Mutual labels:  chromium
WebView-Advanced
A collection of android webview hack
Stars: ✭ 25 (-24.24%)
Mutual labels:  chromium
Chromium-Gost
Chromium с поддержкой алгоритмов ГОСТ
Stars: ✭ 286 (+766.67%)
Mutual labels:  chromium
Browser
A productivity focused web browser
Stars: ✭ 49 (+48.48%)
Mutual labels:  chromium
matic-docs
The official documentation for all Polygon products.
Stars: ✭ 181 (+448.48%)
Mutual labels:  edge
Colorblinding
An extension for Google Chrome (and Chromium) that simulates the website as a color vision impaired person would see.
Stars: ✭ 25 (-24.24%)
Mutual labels:  chromium
orb
Orb is a dynamic network observability platform
Stars: ✭ 437 (+1224.24%)
Mutual labels:  edge
browserslist-config-google
Google / Google Workspace Browserslist Shared Config
Stars: ✭ 27 (-18.18%)
Mutual labels:  edge
khadas-openwrt
openwrt for Khadas boards
Stars: ✭ 25 (-24.24%)
Mutual labels:  edge
HandyBrowser
A Handshake enabled Chromium web browser and reference client; works with HSD and the HNSD light client. Use our example to build or integrate Handshake into any browser.
Stars: ✭ 101 (+206.06%)
Mutual labels:  chromium

Info

The project practices Java Selenium 4.0.x release ChromiumDriver to execute the Chrome DevTools Protocol a.k.a. cdp commands - an entirely different set of API communicated to the Chrome browser family via POST requests to /session/$sessionId/goog/cdp/execute with API-specific payload) feature (many of the cdp methods e.g. the DOM ones like

  • performSearch,
  • getSearchResults
  • getNodeForLocation
  • getOuterHTML
  • `querySelectorAll
  • querySelector
  • getAttributes

overlap with classic Selenium in Classic Javascript and there are few specific ones like:

  • addCustomHeaders
  • getFrameTree
  • setGeolocationOverride
  • setDownloadBehavior

to name a few, and various event listeners

This functionality is named in official Selenium Developer Documentation as BiDirectional functionality and BiDi API

The project also exercised other new Selenium 4 API e.g. relative nearby locators whidh did not apear powerful enough yet.

For accessing the Chrome Devtools API with Selenium driver 3.x see cdp_webdriver project

Examples

Async Code Execution by XHR Fetch events in the Browser

xhr_test_capture.png

This test is opening Wikipedia page and hovers over few links using "classic" Selenium Actions class:

driver.findElement(By.id("mw-content-text")).findElements(By.tagName("a")).stream().forEach( (WebElement element ) -> {
    new Actions(driver).moveToElement(element).build().perform();
  }
}

To emphacise that the lambda operates "classic" object,the WebElement type was entered explicitly.

In the @Before -annotated method in the test class, the Fetch API is enabled for all requests

@Before
public void beforeTest() throws Exception {
chromeDevTools = ((HasDevTools) driver).getDevTools();

List<RequestPattern> reqPattern = new ArrayList<>();
reqPattern.add(new RequestPattern(Optional.of("*"), Optional.of(ResourceType.XHR), Optional.of(RequestStage.RESPONSE)));
chromeDevTools.send(Fetch.enable(Optional.of(reqPattern), Optional.of(false)));

(If necessary one can limit to subset of reuests via match pattern). Then in the test method callback is set up:

@Test
public void test() {
	chromeDevTools.addListener(Fetch.requestPaused(),
		(RequestPaused event) -> {
      event.getResponseHeaders().get().stream().map(entry -> String.format("%s: %s",
              entry.getName(), entry.getValue())).collect(Collectors.toList());
      Fetch.GetResponseBodyResponse response = chromeDevTools.send(Fetch.getResponseBody(event.getRequestId()));
        String body = new String(Base64.decodeBase64(response.getBody().getBytes("UTF8")));
	System.err.println("response body:\n" + body);
      }
});
// he mouse hover actions to follow

This allows capture every Ajax request response headers,

List<HeaderEntry> headerEntries = event.getResponseHeaders().isPresent() ? event.getResponseHeaders().get() : new ArrayList<>();
List<String> headers = headerEntries.stream().map(entry -> String.format("%s: %s", entry.getName(), entry.getValue())) .collect(Collectors.toList());

along with response status

event.getResponseStatusCode().get()

and body which is usually a base64 encoded JSON with multiple details, processed by browser xhr_logged_capture.png

Fetch.GetResponseBodyResponse response = chromeDevTools.send(Fetch.getResponseBody(event.getRequestId()));
String body = null;
if (response.getBase64Encoded()) {
	try {
		body = new String( Base64.decodeBase64(response.getBody().getBytes("UTF8")));
	} catch (UnsupportedEncodingException e) {
		System.err.println("Exception (ignored): " + e.toString());
	}
} else {
	body = response.getBody();
}

finally the test continues default processing of the request:

chromeDevTools.send(Fetch.continueRequest(
	event.getRequestId(),
	Optional.empty(),
	Optional.empty(), 
	Optional.empty(), 
	Optional.empty(), 
	Optional.empty()));
  • the arguments to the Java adapter method match the Javascript Fetch.continueResponse parameter definition:
requestId
RequestId
An id the client received in requestPaused event.
responseCode
integer
An HTTP response code. If absent, original response code will be used.
responsePhrase
string
A textual representation of responseCode. If absent, a standard phrase matching responseCode is used.
responseHeaders
array[ HeaderEntry ]
Response headers. If absent, original response headers will be used.
binaryResponseHeaders
string
Alternative way of specifying response headers as a \0-separated series of name: value pairs. Prefer the above method unless you need to represent some non-UTF8 values that can't be transmitted over the protocol as text. (Encoded as a base64 string when passed over JSON)

Access Browser Console Logs

Browser console logs may accessed asynchronuosly in asimilar fashion:

@Before
public void beforeTest() throws Exception {
	chromeDevTools.send(Log.enable());
	chromeDevTools.addListener(Log.entryAdded(),
		(LogEntry event) -> System.err.println(
			String.format( "time stamp: %s line number: %s url: \"%s\" text: %s",
	formatTimestamp(event.getTimestamp()),
	(event.getLineNumber().isPresent() ? event.getLineNumber().get() : ""),
	(event.getUrl().isPresent() ? event.getUrl().get() : ""),
	event.getText())));
}

The properties of the event are taken from Log entry object specification One can also confirm the logging event to have expected properties, e.g. message:

@Test
public void test() {
	final String consoleMessage = "Lorem ipsum";
	chromeDevTools.addListener(Log.entryAdded(),
		(LogEntry event) -> assertThat(event.getText(), containsString(consoleMessage)));
	if (driver instanceof JavascriptExecutor) {
		JavascriptExecutor executor = JavascriptExecutor.class.cast(driver);		
		executor.executeScript("console.log(arguments[0]);", consoleMessage);
	}
}

Print to PDF

This API uses CDP command:

public void test1() {
	PrintToPDFResponse response;
	boolean landscape = false;
	boolean displayHeaderFooter = false;
	boolean printBackground = false;
	Page.PrintToPDFTransferMode transferMode = Page.PrintToPDFTransferMode.RETURNASBASE64;
	int scale = 1;

	// Act
	response = chromeDevTools.send(Page.printToPDF(
	Optional.of(landscape), 
	Optional.of(displayHeaderFooter),
	Optional.of(printBackground), 
	Optional.of(scale), 
	Optional.empty(), 
	Optional.empty(), 
	Optional.empty(),
	Optional.empty(), 
	Optional.empty(), 
	Optional.empty(), 
	Optional.empty(), 
	Optional.empty(),
	Optional.empty(),
	Optional.empty(), 
	Optional.empty(), 
	Optional.of(transferMode)));
	assertThat(response, notNullValue());
	String body = new String(Base64.decodeBase64(response.getData().getBytes("UTF8")));
	assertThat(body, notNullValue());
	String magic = body.substring(0, 9);
	assertThat(magic, containsString("%PDF"));

the browser needs to run headless mode for the call to succeed the alternative call signature is

response = chromeDevTools.send(new Command<PrintToPDFResponse>("Page.printToPDF", ImmutableMap.of("landscape", landscape), o -> o.read(PrintToPDFResponse.class)));
assertThat(response, notNullValue());

for some calls (but not specifically for Page.printToPDF) yet anoher alternavie signature via static method exists

response = chromeDevTools.send(new Command<PrintToPDFResponse>("Page.printToPDF", ImmutableMap.of("landscape", landscape), ConverterFunctions.map("data", PrintToPDFResponse.class)));

Zoom the Browser window

in additon to legacy-like keyboard zoom, the CDP supports Page.setDeviceMetricsOverride method and Emulation.setDeviceMetricsOverride method:

  @Before
  public void before() throws Exception {
    baseURL = "https://www.wikipedia.org";
    driver.get(baseURL);
  }

  @Test
  public void test1() {
    for (int cnt = 0; cnt != deviceScaleFactors.length; cnt++) {
      double deviceScaleFactor = deviceScaleFactors[cnt];
      screenshotFileName = String.format("test1_%03d.jpg",
          (int) (100 * deviceScaleFactor));
      layoutMetrics = chromeDevTools.send(Page.getLayoutMetrics());
      rect = layoutMetrics.getContentSize();
      width = rect.getWidth().intValue();
      height = rect.getHeight().intValue();
      System.err.println(String.format("Content size: %dx%d", width, height));
      chromeDevTools.send(
        // @formatter:off
        Emulation.setDeviceMetricsOverride(
          rect.getWidth().intValue(), 
          rect.getHeight().intValue(),
          deviceScaleFactor, 
          false, 
          Optional.empty(), 
          Optional.empty(),
          Optional.empty(), 
          Optional.empty(), 
          Optional.empty(), 
          Optional.empty(),
          Optional.empty(), 
          Optional.empty(), 
          Optional.empty()
        )
        // @formatter:on
      );
      String dataString = chromeDevTools.send(
        // @formatter:off
        Page.captureScreenshot(
            Optional.of(Page.CaptureScreenshotFormat.JPEG), 
            Optional.of(100),
            Optional.empty(), 
            Optional.of(true), 
            Optional.of(true)
        )
        // @formatter:off
    );
    chromeDevTools.send(Emulation.clearDeviceMetricsOverride());

    byte[] image = base64.decode(dataString);
    try {
      BufferedImage o = ImageIO.read(new ByteArrayInputStream(image));
      System.err.println(String.format("Screenshot dimensions: %dx%d",
          o.getWidth(), o.getHeight()));
      assertThat((int) (width * deviceScaleFactor) - o.getWidth(),
          not(greaterThan(2)));
      assertThat((int) (height * deviceScaleFactor) - o.getHeight(),
          not(greaterThan(2)));
    } catch (IOException e) {
      System.err.println("Exception loading image (ignored): " + e.toString());
    }
    try {
      FileOutputStream fileOutputStream = new FileOutputStream(
          screenshotFileName);
      fileOutputStream.write(image);
      fileOutputStream.close();
    } catch (IOException e) {
      System.err.println("Exception saving image (ignored): " + e.toString());
    }
    }
  }


@After
public void clearPage() {
  chromeDevTools.send(CSS.disable());
  try {
    chromeDevTools.send(DOM.disable());
  } catch (DevToolsException e) {
    // DOM agent hasn't been enabled
  }
  driver.get("about:blank");
}

this test gets gradually magnified out page screen shots:

capture-multi-zoom.png

alternatively use CDP commands for the same:

  @SuppressWarnings("unchecked")
  @Test
  public void test() {
    // Assert
    params = new HashMap<>();
    for (int cnt = 0; cnt != deviceScaleFactors.length; cnt++) {
      double deviceScaleFactor = deviceScaleFactors[cnt];
      filename = String.format("test2_%03d.jpg",
          (int) (100 * deviceScaleFactor));

      try {
        command = "Page.getLayoutMetrics";
        result = driver.executeCdpCommand(command, new HashMap<>());
        System.err
            .println("Page.getLayoutMetrics: " + result.get("contentSize"));
        rect = (Map<String, Long>) result.get("contentSize");
        height = rect.get("height");
        width = rect.get("width");
        command = "Emulation.setDeviceMetricsOverride";
        // Act
        System.err.println(String.format("Scaling to %02d%% %s",
            (int) (100 * deviceScaleFactor), filename));
        params.clear();
        params.put("deviceScaleFactor", deviceScaleFactor);
        params.put("width", width);
        params.put("height", height);
        params.put("mobile", false);
        params.put("scale", 1);
        driver.executeCdpCommand(command, params);

        Utils.sleep(delay);
        command = "Page.captureScreenshot";
        // Act
        result = driver.executeCdpCommand(command,
            new HashMap<String, Object>());

        command = "Emulation.clearDeviceMetricsOverride";
        driver.executeCdpCommand(command, new HashMap<String, Object>());

        // Assert
        assertThat(result, notNullValue());
        assertThat(result, hasKey("data"));
        dataString = (String) result.get("data");
        assertThat(dataString, notNullValue());

        byte[] image = base64.decode(dataString);
        BufferedImage o = ImageIO.read(new ByteArrayInputStream(image));
        assertThat(o.getWidth(), greaterThan(0));
        assertThat(o.getHeight(), greaterThan(0));
        FileOutputStream fileOutputStream = new FileOutputStream(filename);
        fileOutputStream.write(image);
        fileOutputStream.close();
      } catch (IOException e) {
        System.err.println("Exception saving image (ignored): " + e.toString());
      } catch (JsonSyntaxException e) {
        System.err.println("JSON Syntax exception in " + command
            + " (ignored): " + e.toString());
      } catch (WebDriverException e) {
        // willbe thrown if the required arguments are not provided.
        // TODO: add failing test
        System.err.println(
            "Web Driver exception in " + command + " (ignored): " + Utils
                .processExceptionMessage(e.getMessage() + "  " + e.toString()));
      } catch (Exception e) {
        System.err.println("Exception in " + command + "  " + e.toString());
        throw (new RuntimeException(e));
      }
    }
  }

Filter URL

xhr_logged_capture.png Bandwidth improving filtering of certain mask URLs

chromeDevTools.send(Network.enable(Optional.of(100000000), Optional.empty(), Optional.empty()));
chromeDevTools.send(Network.setBlockedURLs(ImmutableList.of("*.css", "*.png", "*.jpg", "*.gif", "*favicon.ico")));
driver.get("http://arngren.net");

one can also log the *.css, *.jpg *.png and *.ico blocking in action:

// verify that 
chromeDevTools.addListener(Network.loadingFailed(),
	(LoadingFailed event) -> {
		ResourceType resourceType = event.getType();
		if (resourceType.equals(ResourceType.STYLESHEET)
				|| resourceType.equals(ResourceType.IMAGE)
				|| resourceType.equals(ResourceType.OTHER)) {
			Optional<BlockedReason> blockedReason = event.getBlockedReason();
			assertThat(blockedReason.isPresent(), is(true));
			assertThat(blockedReason.get(), is(BlockedReason.INSPECTOR));
		}
	System.err.println("Blocked event: " + event.getType());
});

finally one can disable filtering:

// set request interception only for css requests
RequestPattern requestPattern = new RequestPattern(Optional.of("*.gif"), Optional.of(ResourceType.IMAGE), Optional.of(InterceptionStage.HEADERSRECEIVED));
chromeDevTools.send(Network.setRequestInterception(ImmutableList.of(requestPattern)));
chromeDevTools.send(Page.navigate(baseURL, Optional.empty(),Optional.empty(), Optional.empty(), Optional.empty()));

xhr_logged_capture.png

Override User Agent

One can call cdp protocol to invoke setUserAgentOverride method and dynmically modify the user-agent header during the test:

  import org.openqa.selenium.chrome.ChromeDriver;
  import org.openqa.selenium.chromium.ChromiumDriver;

  ChromiumDriver driver = new ChromeDriver();
  driver.get("https://www.whoishostingthis.com/tools/user-agent/");
  By locator = By.cssSelector(".user-agent");
  WebElement element = driver.findElement(locato);
  assertThat(element.getAttribute("innerText"), containsString("Mozilla"));
  Map<String, Object> params = new HashMap<String, Object>();
  params.put("userAgent", "python 2.7");
  params.put("platform", "Windows");
  driver.executeCdpCommand("Network.setUserAgentOverride", params);
  driver.navigate().refresh();
  sleep(100);

  element = driver.findElement(locator);
  assertThat(element.isDisplayed(), is(true));
  assertThat(element.getAttribute("innerText"), is("python 2.7"));

demonstrates that the user-agent is indeed changing

Cookies

The example shows alternative API to collect the cookies available to page Javascript

  Map<String, Object> result = driver.executeCdpCommand("Page.getCookies", new HashMap<String, Object>());
  ArrayList<Map<String, Object>> cookies = (ArrayList<Map<String, Object>>) result.get("cookies");
  cookies.stream().limit(100).map(o -> o.keySet()).forEach(System.err::println);

Capture Screenshot

  String result = driver.executeCdpCommand("Page.captureScreenshot", new HashMap<>());
  String data = (String) result.get("data");
  byte[] image = new (Base64()).decode(data);
  assertThat(ImageIO.read(new ByteArrayInputStream(image)).getWidth(), greaterThan(0));
  (new FileOutputStream("temp.png")).write(image);

Capture Element Screenshot

implements the clipping to viewport functioality

command = "Page.captureScreenshot";
params = new HashMap<String, Object>();
Map<String, Object> viewport = new HashMap<>();
System.err.println("Specified viewport: " + String
    .format("x=%d, y=%d, width=%d, height=%d", x, y, width, height));
viewport.put("x", (double) x);
viewport.put("y", (double) y);
viewport.put("width", (double) width);
viewport.put("height", (double) height);
viewport.put("scale", scale);
params.put("clip", viewport);
result = driver.executeCdpCommand(command, params);
dataString = (String) result.get("data");
assertThat(dataString, notNullValue());
Base64 base64 = new Base64();
byte[] image = base64.decode(dataString);
String screenshotFileName = String.format("card%02d.png", cnt);
FileOutputStream fileOutputStream = new FileOutputStream( screenshotFileName);
fileOutputStream.write(image);
fileOutputStream.close();

Note: some CDP API notably Page.printToPDF are not curently implemented:

unhandled inspector error: {"code":-32000,"message":"PrintToPDF is not implemented"}(..)

Custom Headers

This can be done both at the wrapper methods

    // enable Network
    chromeDevTools.send(Network.enable(Optional.empty(), Optional.empty(), Optional.empty()));
    headers = new HashMap<>();
    headers.put("customHeaderName", "customHeaderValue");
    Headers headersData = new Headers(headers);
    chromeDevTools.send(Network.setExtraHTTPHeaders(headersData));

The validation can be done through hooking assert and log message to the event:

    // add event listener to log that requests are sending with the custom header
    chromeDevTools.addListener(Network.requestWillBeSent(),
        o -> Assert.assertEquals(o.getRequest().getHeaders().get("customHeaderName"), "customHeaderValue"));
    chromeDevTools.addListener(Network.requestWillBeSent(), o -> System.err.println(
        "addCustomHeaders Listener invoked with " + o.getRequest().getHeaders().get("customHeaderName")));

and low level "commands":

    String command = "Network.enable";
    params = new HashMap<>();
    params.put("maxTotalBufferSize", 0);
    params.put("maxPostDataSize", 0);
    params.put("maxPostDataSize", 0);
    result = driver.executeCdpCommand(command, params);
    command = "Network.setExtraHTTPHeaders";

    params = new HashMap<>();
    Map<String, String> headers = new HashMap<>();
    headers.put("customHeaderName", this.getClass().getName() + " addCustomHeadersTest");
    params.put("headers", headers);
    result = driver.executeCdpCommand(command, params);

To test one can e.g. fire a tomcat server with request header logging and send the GET request

driver.get("http://127.0.0.1:8080/demo/Demo");

The actual validation will be done through console logs inspection of the server

DOM Node Navigation

The following somewhat long test exercises steps one has to perform with CDP to get a specific DOM Node focused and act upon:

It appears every node search starts with getting the document:

	@SuppressWarnings("unchecked")
	@Test
	public void getDocumentTest() {
		// Arrange
		driver.get("https://www.google.com");
		String command = "DOM.getDocument";
		try {
			// Act
			result = driver.executeCdpCommand(command, new HashMap<>());
			// Assert
			assertThat(result, hasKey("root"));
			Map<String, Object> data =  (Map<String, Object>) result.get("root");
			assertThat(data, hasKey("nodeId"));
			assertTrue(Long.parseLong(data.get("nodeId").toString()) != 0);
			err.println("Command " + command + " return node: "
					+ new Gson().toJson(data, Map.class));
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
		}
	}

This test logs:

Command DOM.getDocument return node:
{
  "backendNodeId": 1,
  "baseURL": "https://www.google.com/",
  "childNodeCount": 2,
  "children": [
    {
      "backendNodeId": 2,
      "localName": "",
      "nodeId": 10,
      "nodeName": "html",
      "nodeType": 10,
      "nodeValue": "",
      "parentId": 9,
      "publicId": "",
      "systemId": ""
    },
    {
      "attributes": [
        "itemscope",
        "",
        "itemtype",
        "http://schema.org/WebPage",
        "lang",
        "en"
      ],
      "backendNodeId": 3,
      "childNodeCount": 2,
      "children": [
        {
          "attributes": [],
          "backendNodeId": 21,
          "childNodeCount": 12,
          "localName": "head",
          "nodeId": 12,
          "nodeName": "HEAD",
          "nodeType": 1,
          "nodeValue": "",
          "parentId": 11
        },
        {
          "attributes": [
            "jsmodel",
            " ",
            "class",
            "hp vasq",
            "id",
            "gsr"
          ],
          "backendNodeId": 22,
          "childNodeCount": 8,
          "localName": "body",
          "nodeId": 13,
          "nodeName": "BODY",
          "nodeType": 1,
          "nodeValue": "",
          "parentId": 11
        }
      ],
      "frameId": "C3CE739B971DD10AFECA84F6C1554308",
      "localName": "html",
      "nodeId": 11,
      "nodeName": "HTML",
      "nodeType": 1,
      "nodeValue": "",
      "parentId": 9
    }
  ],
  "documentURL": "https://www.google.com/",
  "localName": "",
  "nodeId": 9,
  "nodeName": "#document",
  "nodeType": 9,
  "nodeValue": "",
  "xmlVersion": ""
}

now one can

		command = "DOM.querySelector";
		params.clear();
		params.put("nodeId", nodeId);
		params.put("selector", "img#hplogo");

		try {
			result = driver.executeCdpCommand(command, params);
			assertThat(result, hasKey("nodeId"));
			nodeId = (Long) result.get("nodeId");
			assertTrue(nodeId != 0);
			err.println("Command " + command + " returned  nodeId: " + nodeId);
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
		}
		command = "DOM.getOuterHTML";
		params.clear();
		params.put("nodeId", nodeId);
		
		try {
			result = driver.executeCdpCommand(command, params);
			assertThat(result, notNullValue());
			assertThat(result, hasKey("outerHTML"));
			String dataString = (String) result.get("outerHTML");
			assertThat(dataString, notNullValue());
			err.println("Command " + command + " return outerHTML: " + dataString);
		} catch (Exception e) {
			err.println("Exception in " + command + " (ignored): " + e.toString());
		}
	}

This will log:

Command DOM.querySelector returned  nodeId: 162
Command DOM.getOuterHTML return outerHTML: 
<img alt="Google" height="92" id="hplogo"  src="/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"  style="padding-top:109px" width="272" onload="typeof google==='object'&amp;&amp;google.aft&amp;&amp;google.aft(this)" data-iml="1576602836994" data-atf="1">

collapsing multiple command calls together will lead to somewhat bloated test method

	@Test
	public void multiCommandTest() {
		// Arrange
		baseURL = "https://www.google.com";
		driver.get(baseURL);
		String command = "DOM.getDocument";
		try {
			// Act
			result = driver.executeCdpCommand(command, new HashMap<>());
			// Assert
			assertThat(result, hasKey("root"));
			@SuppressWarnings("unchecked")
			Map<String, Object> node = (Map<String, Object>) result.get("root");
			assertThat(node, hasKey("nodeId"));
			nodeId = Long.parseLong(node.get("nodeId").toString());
			assertTrue(nodeId != 0);
			err.println("Command " + command + " returned nodeId: " + nodeId);
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
		}
		command = "DOM.describeNode";
		params = new HashMap<>();
		params.put("nodeId", nodeId);
		params.put("depth", 1);
		try {
			result = driver.executeCdpCommand(command, params);
			// Assert
			assertThat(result, hasKey("node"));
			@SuppressWarnings("unchecked")
			Map<String, Object> data = (Map<String, Object>) result.get("node");
			for (String field : Arrays.asList(
					new String[] { "nodeType", "nodeName", "localName", "nodeValue" })) {
				assertThat(data, hasKey(field));
			}
			System.err.println("Command " + command + " returned node: " + data);
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
		}

		command = "DOM.querySelector";
		params = new HashMap<>();
		params.put("nodeId", nodeId);
		// params.put("selector", "img#hplogo");
		params.put("selector", "input[name='q']");

		try {
			result = driver.executeCdpCommand(command, params);
			// depth, 1
			// Assert
			assertThat(result, hasKey("nodeId"));
			// @SuppressWarnings("unchecked")
			nodeId = Long.parseLong(result.get("nodeId").toString());
			assertTrue(nodeId != 0);
			err.println("Command " + command + " returned  nodeId: " + nodeId);
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
		}

		command = "DOM.resolveNode";
		params = new HashMap<>();
		params.put("nodeId", nodeId);

		try {
			result = driver.executeCdpCommand(command, params);
			// depth, 1
			// Assert
			assertThat(result, hasKey("object"));
			// object
			@SuppressWarnings("unchecked")
			Map<String, Object> data = (Map<String, Object>) result.get("object");
			for (String field : Arrays.asList(
					new String[] { "type", "subtype", "className", "objectId" })) {
				assertThat(data, hasKey(field));
			}
			String objectId = (String) data.get("objectId");
			assertThat(objectId, notNullValue());
			System.err
					.println("Command " + command + " returned objectId: " + objectId);
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
		}

		command = "DOM.something not defined";
		try {
			// Act
			result = driver.executeCdpCommand(command, new HashMap<>());
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
			// wasn't found
		}
		// DOM.removeNode
		command = "DOM.focus";
		params = new HashMap<>();
		params.put("nodeId", nodeId);
		try {
			// Act
			result = driver.executeCdpCommand(command, params);
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
			// : unknown error: unhandled inspector error:
			// {"code":-32000,"message":"Element is not focusable"}
		}
		command = "DOM.highlightNode";
		try {
			// Act
			result = driver.executeCdpCommand(command, new HashMap<>());
			Utils.sleep(10000);
		} catch (org.openqa.selenium.WebDriverException e) {
			err.println(
					"Exception in command " + command + " (ignored): " + e.toString());
		}
		// TODO: command = "Runtime.callFunctionOn";
	}

Relative Locators

Selenum release dependency

The selenium-chromium-driver that is only available for Selenum release 4 is the critical dependency jar of this project. The selenium-chromium-driver repository search page.

The devtools and chromium subprojects of selenium client of official seleniumhq/selenium project have no dependencies and can be cloned and built locally allowing one to use CDP API with Selenium 3.x e.g. Selenium 3.13.0. This is currently attempted this way in this project. Moving away form default 4.0.0.alpha maven profiles is a work in progress.

Breaking Changes in Selenium 4.0.0-alpha-7

With Selenium driver release 4.0.0-alpha-7 just to make the project compile changes imported package names need to change all org.openqa.selenium.devtools.browser references with org.openqa.selenium.devtools.v87.browser and similar to other packages inside org.openqa.selenium.devtools were requied. Without this multiple compile errors like:

package org.openqa.selenium.devtools.browser does not exist

are observed

Also the following run time errors indicate that selenium-api-4.0.0-alpha-7.jar was build on JDK 11 and is notloadable in JDK 8.

This manifests through the runtime exception

java.lang.NoClassDefFoundError: Could not initialize class org.openqa.selenium.net.PortProber
  at org.openqa.selenium.remote.service.DriverService$Builder.build(DriverService.java:401)
  at org.openqa.selenium.chrome.ChromeDriverService.createServiceWithConfig(ChromeDriverService.java:133)

the usual classpath scan reveals the jar containing the class in question, to be actually present in classpath

find ~/.m2/repository/ -iname 'selenium*jar' |xargs -IX sh -c "echo X; jar tvf X" | tee a

and method signature exception

java.lang.NoSuchMethodError: java.io.FileReader.<init>(Ljava/io/File;Ljava/nio/charset/Charset;)V
  at org.openqa.selenium.net.LinuxEphemeralPortRangeDetector.getInstance(LinuxEphemeralPortRangeDetector.java:36)
  at org.openqa.selenium.net.PortProber.<clinit>(PortProber.java:42)

the method the exception is complainign was added in Java 11

Note

  • To get Google Chrome updates past version 108, one needs Windows 10 or later. Some development environment computers are using Windows 8.1

See Also

License

This project is licensed under the terms of the MIT license.

Author

Serguei Kouzmine

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].