Screenshots with Selenium WebDriver BiDi in Java

Published on December 5, 2024

Taking Screenshots with Selenium WebDriver BiDi in Java

BiDi Browsing Context Domainโ€” Screen Capture in Java

Thumbnail for article ๐“๐š๐ค๐ข๐ง๐  ๐’๐œ๐ซ๐ž๐ž๐ง๐ฌ๐ก๐จ๐ญ๐ฌ ๐ฐ๐ข๐ญ๐ก ๐’๐ž๐ฅ๐ž๐ง๐ข๐ฎ๐ฆ ๐–๐ž๐›๐ƒ๐ซ๐ข๐ฏ๐ž๐ซ ๐๐ข๐ƒ๐ข ๐ข๐ง ๐‰๐š๐ฏ๐š
๐˜ˆ๐˜จ๐˜ฆ๐˜ฏ๐˜ฅ๐˜ข: ๐’๐œ๐ซ๐ž๐ž๐ง๐ฌ๐ก๐จ๐ญ๐ฌ ๐ฐ๐ข๐ญ๐ก ๐’๐ž๐ฅ๐ž๐ง๐ข๐ฎ๐ฆ ๐–๐ž๐›๐ƒ๐ซ๐ข๐ฏ๐ž๐ซ ๐๐ข๐ƒ๐ข ๐ข๐ง ๐‰๐š๐ฏ๐š

Browsing Context

Selenium WebDriver provides functionality to take screenshots within a browsing context, meaning the window, tab or frame where content is rendered. But before we can use this feature, we must enable WebDriver to create a bidirectional connection between our test script and the browser. This is done when instantiating our driver. So we can add a new option here, which is options.enableBiDi().

var options = new ChromeOptions();
options.enableBiDi();
WebDriver driver = new ChromeDriver(options);

Then we navigate to โ€œ๐˜›๐˜ฉ๐˜ฆ ๐˜๐˜ฏ๐˜ต๐˜ฆ๐˜ณ๐˜ฏ๐˜ฆ๐˜ตโ€ app, a test bed for aspiring automators developed by Dave Haeffner:

driver.get("https://the-internet.herokuapp.com/challenging_dom");

Next, we set the browsing context to the window that weโ€™re on. So we type var BrowsingContext = new BrowsingContext() and pass in our driver instance and the context we want to set. In our case, letโ€™s set the context to the current window and we can do so by typing driver.getWindowsHandle().

var browsingContext = new BrowsingContext(driver, driver.getWindowHandle());

Full Page Screenshot

Full page found at URL https://the-internet.herokuapp.com/challenging_dom

To capture a screenshot of the entire page, we type browsingContext.captureScreenshot(). This returns a Base64 encoded string of the binary image data. Letโ€™s store this in a variable. Its type will be String and we can call this fullScreenshot.

String fullScreenshot = browsingContext.captureScreenshot();

To view the screenshot as an image, we need to decode the Base64 string into binary format. We can accomplish it with a custom saveScreenshot method.

private static void saveScreenshot(String screenshot, String filename) {
var decodedScreenshot = Base64.getDecoder().decode(screenshot);
try {
String path = "/Users/lanabegunova/eclipse-workspace/TakingScreenshotsBiDi/src/screenshots/";
Files.write(Paths.get(path + filename), decodedScreenshot);
} catch (IOException e) {
e.printStackTrace();
}
}

Line var decodedScreenshot = Base64.getDecoder().decode(screenshot) gives us the binary format of the image, which we can then save as an image file into a folder called โ€˜screenshotsโ€™. And we can create that folder right under โ€˜srcโ€™.

Create a โ€˜๐ฌ๐œ๐ซ๐ž๐ž๐ง๐ฌ๐ก๐จ๐ญ๐ฌโ€™ subfolder in the project โ€˜srcโ€™ folder. The script will save screenshot PNGs here.

So within our main method, after we save the full-page screenshot, letโ€™s call that method. We type saveScreenshot() and pass in our fullScreenshot and then a name of the file. Weโ€™ll call this one full_screenshot.png.

saveScreenshot(fullScreenshot, "full_screenshot.png")

When we run this, we should look in the Explorer under the โ€˜screenshotsโ€™ folder. Notice now we have a new file with the name that we provided. If we click on it, we see that it has taken a screenshot of the full page.

๐˜ง๐˜ถ๐˜ญ๐˜ญ_๐˜ด๐˜ค๐˜ณ๐˜ฆ๐˜ฆ๐˜ฏ๐˜ด๐˜ฉ๐˜ฐ๐˜ต.๐˜ฑ๐˜ฏ๐˜จ generated by ๐›๐ซ๐จ๐ฐ๐ฌ๐ข๐ง๐ ๐‚๐จ๐ง๐ญ๐ž๐ฑ๐ญ.๐œ๐š๐ฉ๐ญ๐ฎ๐ซ๐ž๐’๐œ๐ซ๐ž๐ž๐ง๐ฌ๐ก๐จ๐ญ()

Element Screenshot

Capture an element located under ๐˜ช๐˜ฅ ๐œ๐š๐ง๐ฏ๐š๐ฌ

We can also scope our screenshot to a specific element. Letโ€™s take a screenshot of this element, whose ๐˜ช๐˜ฅ is ๐œ๐š๐ง๐ฏ๐š๐ฌ. First, we find the element. Letโ€™s type WebElement, call this object canvas, and assign to it the element located with driver.findElement(By.cssSelector(โ€œ#canvasโ€)).

WebElement canvas = driver.findElement(By.cssSelector("#canvas"));

Then we call the captureElementScreenshot() method from browsingContext. So we can type String elementScreenshot = browsingContext.captureElementScreenshot(). And this takes the internal ID of the element, not to be confused with the elementโ€™s ID attribute. We need to cast the element to a remote web element for this.

String elementScreenshot = browsingContext.captureElementScreenshot();

Letโ€™s see how thatโ€™s done. We type String internalElementId =, then in parentheses we add RemoteWebElement, and then we put this entire thing in another set of parentheses. And next to the RemoteWebElement, weโ€™ll add the canvas. From here we can type getid().

String internalElementId = ((RemoteWebElement) canvas).getId();

We can pass this into the aforementioned captureElementScreenshot() method. So weโ€™ll type internalElementId.

String elementScreenshot = browsingContext.captureElementScreenshot(internalElementId);

Now letโ€™s save the screenshot. We call saveScreenshot(), pass in the elementScreenshot and then give it a name. Letโ€™s call this one element_screenshot.png.

saveScreenshot(elementScreenshot, "element_screenshot.png");

Letโ€™s run this. If we check our โ€˜screenshotsโ€™ folder, notice we have a new screenshot that captured just the element.

๐˜ฆ๐˜ญ๐˜ฆ๐˜ฎ๐˜ฆ๐˜ฏ๐˜ต_๐˜ด๐˜ค๐˜ณ๐˜ฆ๐˜ฆ๐˜ฏ๐˜ด๐˜ฉ๐˜ฐ๐˜ต.๐˜ฑ๐˜ฏ๐˜จ generated by ๐›๐ซ๐จ๐ฐ๐ฌ๐ข๐ง๐ ๐‚๐จ๐ง๐ญ๐ž๐ฑ๐ญ.๐œ๐š๐ฉ๐ญ๐ฎ๐ซ๐ž๐„๐ฅ๐ž๐ฆ๐ž๐ง๐ญ๐’๐œ๐ซ๐ž๐ž๐ง๐ฌ๐ก๐จ๐ญ()

Viewport Screenshot

Capture a viewport box around an element located under ๐˜ค๐˜ญ๐˜ข๐˜ด๐˜ด๐˜•๐˜ข๐˜ฎ๐˜ฆ ๐ฅ๐š๐ซ๐ ๐ž-2

WebDriver also allows us to capture a screenshot of a region of the page by specifying its coordinates. Letโ€™s take a screenshot of this large column element whose ๐˜ค๐˜ญ๐˜ข๐˜ด๐˜ด๐˜•๐˜ข๐˜ฎ๐˜ฆ is ๐ฅ๐š๐ซ๐ ๐ž-2.

Letโ€™s first find the column element. So we type var largeColumn = driver.findElement(), this time By.className, and its class name is large-2.

var largeColumn = driver.findElement(By.className("large-2"));

Once we find the element, we want to get its rectangle, which contains the data needed to screenshot it. So weโ€™re going to type .getRect().

var largeColumn = driver.findElement(By.className("large-2")).getRect();

Now we can call captureBoxScreenshot(), which takes the X and Y coordinates as well as the width and height of the viewport to capture. To do so, weโ€™re going to type String viewportScreenshot and set this equal to browsingContext.captureBoxScreenshot().

String viewportScreenshot = browsingContext.captureBoxScreenshot();

For the X coordinate, we can type largeColumn.getX(). For the Y coordinate - largeColumn.getY(). For the width - largeColumn.getWidth(). And for the height we can say largeColumn.getHeight().

String viewportScreenshot = browsingContext.captureBoxScreenshot(
largeColumn.getX(),
largeColumn.getY(),
largeColumn.getWidth(),
largeColumn.getHeight()
);

We can even add padding to the width and height if weโ€™d like to capture a larger region surrounding the element. For example, if we wanted more width and height, we can just add extra pixels here. We can also subtract pixels from the X and Y coordinates to shift the top-left corner of the box/rectangle as desired.

String viewportScreenshot = browsingContext.captureBoxScreenshot(
largeColumn.getX() - 30,
largeColumn.getY() - 30,
largeColumn.getWidth() + 30,
largeColumn.getHeight() + 30
);

Finally, we want to save the screenshot. So we call saveScreenshot(), pass in our viewportScreenshot and then give it a name. Letโ€™s call this one viewport_screenshot.png.

saveScreenshot(viewportScreenshot, "viewport_screenshot.png");

Letโ€™s run this and check our โ€˜screenshotsโ€™ folder. We see that thereโ€™s now a new screenshot for the viewport.

๐˜ท๐˜ช๐˜ฆ๐˜ธ๐˜ฑ๐˜ฐ๐˜ณ๐˜ต_๐˜ด๐˜ค๐˜ณ๐˜ฆ๐˜ฆ๐˜ฏ๐˜ด๐˜ฉ๐˜ฐ๐˜ต.๐˜ฑ๐˜ฏ๐˜จ generated by ๐›๐ซ๐จ๐ฐ๐ฌ๐ข๐ง๐ ๐‚๐จ๐ง๐ญ๐ž๐ฑ๐ญ.๐œ๐š๐ฉ๐ญ๐ฎ๐ซ๐ž๐๐จ๐ฑ๐’๐œ๐ซ๐ž๐ž๐ง๐ฌ๐ก๐จ๐ญ()

Full Code

Screenshots.java:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.bidi.browsingcontext.BrowsingContext;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebElement;

import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver;

public class Screenshots {

static protected WebDriver driver;

public static void main(String[] args) {
chromedriver().setup();
var options = new ChromeOptions();
options.enableBiDi();
WebDriver driver = new ChromeDriver(options);

driver.get("https://the-internet.herokuapp.com/challenging_dom");
var browsingContext = new BrowsingContext(driver, driver.getWindowHandle());

// full page
String fullScreenshot = browsingContext.captureScreenshot();
saveScreenshot(fullScreenshot, "full_screenshot.png");

// element
WebElement canvas = driver.findElement(By.cssSelector("#canvas"));
String internalElementId = ((RemoteWebElement) canvas).getId();
String elementScreenshot = browsingContext.captureElementScreenshot(internalElementId);
saveScreenshot(elementScreenshot, "element_screenshot.png");

// viewport
var largeColumn = driver.findElement(By.className("large-2")).getRect();
String viewportScreenshot = browsingContext.captureBoxScreenshot(largeColumn.getX() - 30,
largeColumn.getY() - 30, largeColumn.getWidth() + 30, largeColumn.getHeight() + 30);

saveScreenshot(viewportScreenshot, "viewport_screenshot.png");

driver.quit();
}

private static void saveScreenshot(String screenshot, String filename) {
var decodedScreenshot = Base64.getDecoder().decode(screenshot);
try {
String path = "/Users/lanabegunova/eclipse-workspace/TakingScreenshotsBiDi/src/screenshots/";
Files.write(Paths.get(path + filename), decodedScreenshot);
} catch (IOException e) {
e.printStackTrace();
}
}
}

๐“—๐’ถ๐“…๐“…๐“Ž ๐“‰๐“ฎ๐“ˆ๐“‰๐’พ๐“ƒ๐“ฐ ๐’ถ๐“ƒ๐’น ๐’น๐“ฎ๐’ท๐“Š๐“ฐ๐“ฐ๐’พ๐“ƒ๐“ฐ!

I welcome any comments and contributions to the subject. Connect with me on LinkedIn, X , GitHub, or Insta. Check out my website.

If you find this post useful, please consider buying me a coffee.