Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<maven.compiler.target.version>17</maven.compiler.target.version>
<surefire.version>3.5.2</surefire.version>
<allure.version>2.29.1</allure.version>
<selenide.version>7.7.3</selenide.version>
<selenide.version>7.9.4</selenide.version>
<cucumber.version>7.20.1</cucumber.version>
<browserup.version>3.1.2</browserup.version>
<log4j.version>2.24.3</log4j.version>
Expand Down
192 changes: 142 additions & 50 deletions src/main/java/com/xceptance/neodymium/common/ScreenshotWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,24 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.Optional;

import javax.imageio.ImageIO;

import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chromium.HasCdp;
import org.openqa.selenium.devtools.DevTools;
import org.openqa.selenium.devtools.HasDevTools;
import org.openqa.selenium.devtools.v137.page.Page;
import org.openqa.selenium.devtools.v137.page.model.Viewport;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.HasFullPageScreenshot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -30,6 +38,7 @@
import com.assertthat.selenium_shutterbug.core.Shutterbug;
import com.assertthat.selenium_shutterbug.utils.image.ImageProcessor;
import com.assertthat.selenium_shutterbug.utils.web.Coordinates;
import com.google.common.collect.ImmutableMap;
import com.xceptance.neodymium.common.testdata.DataSet;
import com.xceptance.neodymium.util.AllureAddons;
import com.xceptance.neodymium.util.Neodymium;
Expand Down Expand Up @@ -112,73 +121,156 @@ public static boolean doScreenshot(String filename, String pathname) throws IOEx
WebDriver driver = Neodymium.getDriver();

Capture captureMode = getCaptureMode();

PageSnapshot snapshot = Shutterbug.shootPage(driver, captureMode);
BufferedImage image = snapshot.getImage();
Files.createDirectories(Paths.get(pathname));
String imagePath = pathname + File.separator + filename + ".png";
File outputfile = new File(imagePath);

if (highlightViewPort() || blurFullPageScreenshot())
WebDriver webDriver = Neodymium.getDriver();
Optional<BufferedImage> imageOptional = Optional.empty();
if (captureMode.equals(Capture.FULL))
{
double devicePixelRatio = Double.parseDouble(((JavascriptExecutor) driver).executeScript("return window.devicePixelRatio") + "");
int offsetY = (int) (Double.parseDouble(((JavascriptExecutor) driver)
.executeScript("return Math.round(Math.max(document.documentElement.scrollTop, document.body.scrollTop))")
.toString()));
int offsetX = (int) (Double.parseDouble(((JavascriptExecutor) driver)
.executeScript("return Math.round(Math.max(document.documentElement.scrollLeft, document.body.scrollLeft))")
.toString()));

Dimension size = Neodymium.getViewportSize();
if (driver instanceof FirefoxDriver)
Optional<File> imageFile = Optional.empty();
if (webDriver instanceof HasFullPageScreenshot firefoxDriver)
{
size = new Dimension(size.width - (int) (15 * devicePixelRatio), size.height - (int) (15 * devicePixelRatio));
imageFile = Optional.of(firefoxDriver.getFullPageScreenshotAs(OutputType.FILE));
}
Point currentLocation = new Point(offsetX, offsetY);
Coordinates coords = new Coordinates(currentLocation, currentLocation, size, new Dimension(0, 0), devicePixelRatio);

if (highlightViewPort())
else if (webDriver instanceof HasCdp)
{
imageFile = takeScreenshotWithCDP((WebDriver & HasCdp & JavascriptExecutor) webDriver, OutputType.FILE);
}
else if (webDriver instanceof HasDevTools)
{
image = highlightScreenShot(image, coords, Color.decode(Neodymium.configuration().fullScreenHighlightColor()));
imageFile = takeScreenshot((WebDriver & HasDevTools & JavascriptExecutor) webDriver, OutputType.FILE);
}
if (blurFullPageScreenshot())
if (imageFile.isPresent())
{
image = ImageProcessor.blurExceptArea(image, coords);
imageOptional = Optional.of(ImageIO.read(imageFile.get()));
}
}
if (Neodymium.configuration().enableHighlightLastElement() && Neodymium.hasLastUsedElement())
else
{
try
PageSnapshot snapshot = Shutterbug.shootPage(driver, captureMode);
imageOptional = Optional.of(snapshot.getImage());
}
if (imageOptional.isPresent())
{
BufferedImage image = imageOptional.get();
Files.createDirectories(Paths.get(pathname));
String imagePath = pathname + File.separator + filename + ".png";
File outputfile = new File(imagePath);

if (highlightViewPort() || blurFullPageScreenshot())
{
double devicePixelRatio = Double.parseDouble("" + ((JavascriptExecutor) driver).executeScript("return window.devicePixelRatio"));
image = highlightScreenShot(image, new Coordinates(Neodymium.getLastUsedElement(), devicePixelRatio),
Color.decode(Neodymium.configuration().screenshotElementHighlightColor()));
double devicePixelRatio = Double.parseDouble(((JavascriptExecutor) driver).executeScript("return window.devicePixelRatio") + "");
int offsetY = (int) (Double.parseDouble(((JavascriptExecutor) driver)
.executeScript("return Math.round(Math.max(document.documentElement.scrollTop, document.body.scrollTop))")
.toString()));
int offsetX = (int) (Double.parseDouble(((JavascriptExecutor) driver)
.executeScript("return Math.round(Math.max(document.documentElement.scrollLeft, document.body.scrollLeft))")
.toString()));

Dimension size = Neodymium.getViewportSize();
if (driver instanceof FirefoxDriver)
{
size = new Dimension(size.width - (int) (15 * devicePixelRatio), size.height - (int) (15 * devicePixelRatio));
}
Point currentLocation = new Point(offsetX, offsetY);
Coordinates coords = new Coordinates(currentLocation, currentLocation, size, new Dimension(0, 0), devicePixelRatio);

if (highlightViewPort())
{
image = highlightScreenShot(image, coords, Color.decode(Neodymium.configuration().fullScreenHighlightColor()));
}
if (blurFullPageScreenshot())
{
image = ImageProcessor.blurExceptArea(image, coords);
}
}
catch (NoSuchElementException e)
if (Neodymium.configuration().enableHighlightLastElement() && Neodymium.hasLastUsedElement())
{
// If the test is breaking because we can't find an element, we also can't highlight this element... so
// a NoSuchElementException is expected and can be ignored.
try
{
double devicePixelRatio = Double.parseDouble("" + ((JavascriptExecutor) driver).executeScript("return window.devicePixelRatio"));
image = highlightScreenShot(image, new Coordinates(Neodymium.getLastUsedElement(), devicePixelRatio),
Color.decode(Neodymium.configuration().screenshotElementHighlightColor()));
}
catch (NoSuchElementException e)
{
// If the test is breaking because we can't find an element, we also can't highlight this element...
// so
// a NoSuchElementException is expected and can be ignored.
}
}
}
log.info("captured Screenshot to: " + imagePath);
log.info("captured Screenshot to: " + imagePath);

boolean result = ImageIO.write(image, "png", outputfile);
if (result)
{
// The idea is to put the screenshot to the best place in the report,
// but for before methods, this is not possible due to allure limitations
// so we just add it normally when the allure lifecycle does not allow to be altered
boolean screenshotAdded;
Allure.getLifecycle().addAttachment("Screenshot", "image/png", ".png", new FileInputStream(imagePath));
screenshotAdded = true;

// to spare disk space, remove the file if we already used it inside the report
if (screenshotAdded)
boolean result = ImageIO.write(image, "png", outputfile);
if (result)
{
outputfile.delete();
// The idea is to put the screenshot to the best place in the report,
// but for before methods, this is not possible due to allure limitations
// so we just add it normally when the allure lifecycle does not allow to be altered
boolean screenshotAdded;
Allure.getLifecycle().addAttachment("Screenshot", "image/png", ".png", new FileInputStream(imagePath));
screenshotAdded = true;

// to spare disk space, remove the file if we already used it inside the report
if (screenshotAdded)
{
outputfile.delete();
}
}
return result;
}
return result;
return false;
}

public static <WD extends WebDriver & HasDevTools & JavascriptExecutor, ResultType> Optional<ResultType> takeScreenshot(
WD devtoolsDriver,
OutputType<ResultType> outputType)
{
DevTools devTools = devtoolsDriver.getDevTools();
devTools.createSessionIfThereIsNotOne(devtoolsDriver.getWindowHandle());

long fullWidth = (long) devtoolsDriver.executeScript("return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth, document.body.offsetWidth, document.documentElement.offsetWidth, document.body.clientWidth, document.documentElement.clientWidth)");
long fullHeight = (long) devtoolsDriver.executeScript("return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight)");

long viewWidth = (long) devtoolsDriver.executeScript("return window.innerWidth");
long viewHeight = (long) devtoolsDriver.executeScript("return window.innerHeight");
boolean exceedViewport = fullWidth > viewWidth || fullHeight > viewHeight;
Viewport viewport = new Viewport(0, 0, fullWidth, fullHeight, 1);

String base64 = devTools.send(Page.captureScreenshot(
Optional.empty(),
Optional.empty(),
Optional.of(viewport),
Optional.empty(),
Optional.of(exceedViewport),
Optional.of(true)));

ResultType screenshot = outputType.convertFromBase64Png(base64);
return Optional.of(screenshot);
}

public static <WD extends WebDriver & HasCdp & JavascriptExecutor, ResultType> Optional<ResultType> takeScreenshotWithCDP(
WD cdpDriver,
OutputType<ResultType> outputType)
{
long fullWidth = (long) cdpDriver.executeScript("return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth, document.body.offsetWidth, document.documentElement.offsetWidth, document.body.clientWidth, document.documentElement.clientWidth)");
long fullHeight = (long) cdpDriver.executeScript("return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.body.offsetHeight, document.documentElement.offsetHeight, document.body.clientHeight, document.documentElement.clientHeight)");

long viewWidth = (long) cdpDriver.executeScript("return window.innerWidth");
long viewHeight = (long) cdpDriver.executeScript("return window.innerHeight");
boolean exceedViewport = fullWidth > viewWidth || fullHeight > viewHeight;
Map<String, Object> captureScreenshotOptions = ImmutableMap.of(
"clip", ImmutableMap.of(
"x", 0,
"y", 0,
"width", fullWidth,
"height", fullHeight,
"scale", 1),
"captureBeyondViewport", exceedViewport);

Map<String, Object> result = cdpDriver.executeCdpCommand("Page.captureScreenshot", captureScreenshotOptions);

String base64 = (String) result.get("data");
ResultType screenshot = outputType.convertFromBase64Png(base64);
return Optional.of(screenshot);
}

public static BufferedImage highlightScreenShot(BufferedImage sourceImage, Coordinates coords, Color color)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.xceptance.neodymium.common.recording;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;

import javax.imageio.ImageIO;

import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
Expand Down Expand Up @@ -87,7 +92,17 @@ public synchronized void run()
try
{
File file = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
int cropWidth = ((Long) ((JavascriptExecutor) driver).executeScript("return window.innerWidth;")).intValue();
int cropHeight = ((Long) ((JavascriptExecutor) driver).executeScript("return window.innerHeight;")).intValue();
BufferedImage fullImage = ImageIO.read(file);
if (cropWidth > 0 && cropHeight > 0)
{
BufferedImage croppedImage = fullImage.getSubimage(0, 0, cropWidth, cropHeight);
ImageIO.write(croppedImage, "png", file);
}

writer.compressImageIfNeeded(file, recordingConfigurations.imageScaleFactor(), recordingConfigurations.imageQuality());
duration = new Date().getTime() - start;
long delay = recordingConfigurations.oneImagePerMilliseconds() > duration ? recordingConfigurations.oneImagePerMilliseconds()
: duration;
writer.write(file, delay);
Expand Down