Skip to content

Commit

Permalink
Add shooting strategy to take screenshot using CDP (#526)
Browse files Browse the repository at this point in the history
  • Loading branch information
uarlouski authored Dec 23, 2024
1 parent b1a85d3 commit 512a9ee
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 1 deletion.
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<selenium.version>4.27.0</selenium.version>
</properties>

<name>AShot WebDriver Utility</name>
Expand All @@ -35,7 +36,12 @@
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>4.27.0</version>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chromium-driver</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/pazone/ashot/CdpShootingStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package pazone.ashot;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chromium.HasCdp;

import pazone.ashot.coordinates.Coords;
import pazone.ashot.util.ImageTool;

/**
* Gets a screenshot using
* <a href="https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot">
* capture screenshot</a> function provided by Chrome DevTools protocol. {@link WebDriver} instance provided
* to the class methods must be an instance of {@link HasCdp} and support Chrome DevTools protocol.
*/
public class CdpShootingStrategy implements ShootingStrategy {

private static final long serialVersionUID = -4371668803381640029L;

@Override
public BufferedImage getScreenshot(WebDriver driver) {
return getScreenshot(driver, Set.of());
}

@Override
public BufferedImage getScreenshot(WebDriver driver, Set<Coords> coords) {
if (!HasCdp.class.isAssignableFrom(driver.getClass())) {
throw new IllegalArgumentException("WebDriver instance must support Chrome DevTools protocol");
}

Map<String, Object> args = new HashMap<>();
args.put("captureBeyondViewport", true);

if (!coords.isEmpty()) {
Coords elementCoords = coords.iterator().next();
args.put("clip", Map.of(
"x", elementCoords.x,
"y", elementCoords.y,
"width", elementCoords.width,
"height", elementCoords.height,
"scale", 1)
);
}

Map<String, Object> results = ((HasCdp) driver).executeCdpCommand("Page.captureScreenshot", args);
String base64 = (String) results.get("data");
byte[] bytes = OutputType.BYTES.convertFromBase64Png(base64);

try {
return ImageTool.toBufferedImage(bytes);
} catch (IOException thrown) {
throw new UncheckedIOException(thrown);
}
}

@Override
public Set<Coords> prepareCoords(Set<Coords> coordsSet) {
return coordsSet;
}
}
66 changes: 66 additions & 0 deletions src/test/java/pazone/ashot/CdpShootingStrategyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package pazone.ashot;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Base64;
import java.util.Map;
import java.util.Set;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chromium.HasCdp;

import pazone.ashot.coordinates.Coords;
import pazone.ashot.util.ImageTool;
import pazone.ashot.util.TestImageUtils;

@ExtendWith(MockitoExtension.class)
class CdpShootingStrategyTest {
private final ShootingStrategy strategy = new CdpShootingStrategy();

@Mock(extraInterfaces = HasCdp.class)
private WebDriver webDriver;

@Test
void testPageScreenshot() throws IOException {
BufferedImage expected = TestImageUtils.IMAGE_A_SMALL;
String base = Base64.getEncoder().encodeToString(ImageTool.toByteArray(expected));

when(((HasCdp) webDriver).executeCdpCommand("Page.captureScreenshot", Map.of("captureBeyondViewport", true)))
.thenReturn(Map.of("data", base));

BufferedImage actual = strategy.getScreenshot(webDriver);
TestImageUtils.assertImageEquals(actual, expected);
}

@Test
void testElementScreenshot() throws IOException {
BufferedImage expected = TestImageUtils.IMAGE_A_SMALL;
String base = Base64.getEncoder().encodeToString(ImageTool.toByteArray(expected));
Coords coords = new Coords(1, 2, 3, 4);

when(((HasCdp) webDriver).executeCdpCommand("Page.captureScreenshot", Map.of("captureBeyondViewport", true,
"clip",
Map.of("x", coords.x, "y", coords.y, "width", coords.width, "height", coords.height, "scale", 1))))
.thenReturn(Map.of("data", base));

BufferedImage actual = strategy.getScreenshot(webDriver, Set.of(coords));
TestImageUtils.assertImageEquals(actual, expected);
}

@Test
void testUnsupportedCdp() {
WebDriver driver = mock(WebDriver.class);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> strategy.getScreenshot(driver));
assertEquals("WebDriver instance must support Chrome DevTools protocol", thrown.getMessage());
}
}

0 comments on commit 512a9ee

Please sign in to comment.