66package com .assertthat .selenium_shutterbug .utils .web ;
77
88import com .assertthat .selenium_shutterbug .utils .file .FileUtil ;
9+ import com .github .zafarkhaja .semver .Version ;
910import com .google .common .collect .ImmutableMap ;
1011import org .openqa .selenium .Dimension ;
1112import org .openqa .selenium .JavascriptExecutor ;
1516import org .openqa .selenium .WebDriver ;
1617import org .openqa .selenium .WebElement ;
1718import org .openqa .selenium .chrome .ChromeDriver ;
19+ import org .openqa .selenium .firefox .FirefoxDriver ;
1820import org .openqa .selenium .remote .CommandInfo ;
1921import org .openqa .selenium .remote .HttpCommandExecutor ;
2022import 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