Skip to content

Commit 0163e57

Browse files
committed
Use geckodirver endpoint to take full page screenshot
1 parent a31de31 commit 0163e57

File tree

2 files changed

+65
-16
lines changed

2 files changed

+65
-16
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@
5656
<artifactId>lombok</artifactId>
5757
<version>1.18.8</version>
5858
</dependency>
59+
<dependency>
60+
<groupId>com.github.zafarkhaja</groupId>
61+
<artifactId>java-semver</artifactId>
62+
<version>0.9.0</version>
63+
</dependency>
5964
</dependencies>
6065

6166
<build>

src/main/java/com/assertthat/selenium_shutterbug/utils/web/Browser.java

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package com.assertthat.selenium_shutterbug.utils.web;
77

88
import com.assertthat.selenium_shutterbug.utils.file.FileUtil;
9+
import com.github.zafarkhaja.semver.Version;
910
import com.google.common.collect.ImmutableMap;
1011
import org.openqa.selenium.Dimension;
1112
import org.openqa.selenium.JavascriptExecutor;
@@ -15,6 +16,7 @@
1516
import org.openqa.selenium.WebDriver;
1617
import org.openqa.selenium.WebElement;
1718
import org.openqa.selenium.chrome.ChromeDriver;
19+
import org.openqa.selenium.firefox.FirefoxDriver;
1820
import org.openqa.selenium.remote.CommandInfo;
1921
import org.openqa.selenium.remote.HttpCommandExecutor;
2022
import org.openqa.selenium.remote.RemoteWebDriver;
@@ -101,6 +103,7 @@ public BufferedImage takeScreenshot() {
101103
/**
102104
* Using different screenshot strategy dependently on driver:
103105
* for chrome - chrome command will be used
106+
* for firefox - geckodriver endpoint will be used if available
104107
* for others - their default screenshot methods
105108
*
106109
* @return BufferedImage resulting image
@@ -112,9 +115,13 @@ public BufferedImage takeScreenshotEntirePage() {
112115

113116
if (driver instanceof ChromeDriver) {
114117
return takeScreenshotEntirePageUsingChromeCommand();
118+
} else if (driver instanceof FirefoxDriver) {
119+
return takeScreenshotEntirePageUsingGeckoDriver();
115120
} else if (driver instanceof RemoteWebDriver) {
116121
if (((RemoteWebDriver) driver).getCapabilities().getBrowserName().equals("chrome")) {
117122
return takeScreenshotEntirePageUsingChromeCommand();
123+
} else if (((RemoteWebDriver) driver).getCapabilities().getBrowserName().equals("firefox")) {
124+
return takeScreenshotEntirePageUsingGeckoDriver();
118125
}
119126
}
120127
return takeScreenshotEntirePageDefault();
@@ -158,14 +165,7 @@ public BufferedImage takeScreenshotEntirePageUsingChromeCommand() {
158165
Object devicePixelRatio = executeJsScript(DEVICE_PIXEL_RATIO);
159166
this.devicePixelRatio = devicePixelRatio instanceof Double ? (Double) devicePixelRatio : (Long) devicePixelRatio * 1.0;
160167

161-
try {
162-
CommandInfo cmd = new CommandInfo("/session/:sessionId/chromium/send_command_and_get_result", HttpMethod.POST);
163-
Method defineCommand = HttpCommandExecutor.class.getDeclaredMethod("defineCommand", String.class, CommandInfo.class);
164-
defineCommand.setAccessible(true);
165-
defineCommand.invoke(((RemoteWebDriver) this.driver).getCommandExecutor(), "sendCommand", cmd);
166-
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
167-
throw new RuntimeException(e);
168-
}
168+
defineCustomCommand("sendCommand", new CommandInfo("/session/:sessionId/chromium/send_command_and_get_result", HttpMethod.POST));
169169

170170
int verticalIterations = (int) Math.ceil(((double) this.getDocHeight()) / this.getViewportHeight());
171171
for (int j = 0; j < verticalIterations; j++) {
@@ -176,15 +176,27 @@ public BufferedImage takeScreenshotEntirePageUsingChromeCommand() {
176176
this.sendCommand("Emulation.setDeviceMetricsOverride", metrics);
177177
Object result = this.sendCommand("Page.captureScreenshot", ImmutableMap.of("format", "png", "fromSurface", true));
178178
this.sendCommand("Emulation.clearDeviceMetricsOverride", ImmutableMap.of());
179-
String base64EncodedPng = (String) ((Map<String, ?>) result).get("data");
180-
InputStream in = new ByteArrayInputStream(OutputType.BYTES.convertFromBase64Png(base64EncodedPng));
181-
BufferedImage bImageFromConvert;
182-
try {
183-
bImageFromConvert = ImageIO.read(in);
184-
} catch (IOException e) {
185-
throw new RuntimeException("Error while converting results from bytes to BufferedImage");
179+
return decodeBase64EncodedPng((String) ((Map<String, ?>) result).get("data"));
180+
}
181+
182+
public BufferedImage takeScreenshotEntirePageUsingGeckoDriver() {
183+
// Check geckodriver version (>= 0.24.0 is requried)
184+
String version = (String) ((RemoteWebDriver) driver).getCapabilities().getCapability("moz:geckodriverVersion");
185+
if (version == null || Version.valueOf(version).satisfies("<0.24.0")) {
186+
return takeScreenshotEntirePageDefault();
186187
}
187-
return bImageFromConvert;
188+
defineCustomCommand("mozFullPageScreenshot", new CommandInfo("/session/:sessionId/moz/screenshot/full", HttpMethod.GET));
189+
Object result = this.executeCustomCommand("mozFullPageScreenshot");
190+
String base64EncodedPng;
191+
if (result instanceof String) {
192+
base64EncodedPng = (String) result;
193+
} else if (result instanceof byte[]) {
194+
base64EncodedPng = new String((byte[]) result);
195+
} else {
196+
throw new RuntimeException(String.format("Unexpected result for /moz/screenshot/full command: %s",
197+
result == null ? "null" : result.getClass().getName() + "instance"));
198+
}
199+
return decodeBase64EncodedPng(base64EncodedPng);
188200
}
189201

190202
public WebDriver getUnderlyingDriver() {
@@ -258,4 +270,36 @@ public Object evaluate(String script) {
258270
Object result = ((Map<String, ?>) response).get("result");
259271
return ((Map<String, ?>) result).get("value");
260272
}
273+
274+
public Object executeCustomCommand(String commandName) {
275+
try {
276+
Method execute = RemoteWebDriver.class.getDeclaredMethod("execute", String.class);
277+
execute.setAccessible(true);
278+
Response res = (Response) execute.invoke(this.driver, commandName);
279+
return res.getValue();
280+
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
281+
throw new RuntimeException(e);
282+
}
283+
}
284+
285+
private void defineCustomCommand(String name, CommandInfo info) {
286+
try {
287+
Method defineCommand = HttpCommandExecutor.class.getDeclaredMethod("defineCommand", String.class, CommandInfo.class);
288+
defineCommand.setAccessible(true);
289+
defineCommand.invoke(((RemoteWebDriver) this.driver).getCommandExecutor(), name, info);
290+
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
291+
throw new RuntimeException(e);
292+
}
293+
}
294+
295+
private BufferedImage decodeBase64EncodedPng(String base64EncodedPng) {
296+
InputStream in = new ByteArrayInputStream(OutputType.BYTES.convertFromBase64Png(base64EncodedPng));
297+
BufferedImage bImageFromConvert;
298+
try {
299+
bImageFromConvert = ImageIO.read(in);
300+
} catch (IOException e) {
301+
throw new RuntimeException("Error while converting results from bytes to BufferedImage");
302+
}
303+
return bImageFromConvert;
304+
}
261305
}

0 commit comments

Comments
 (0)