Skip to content

Commit

Permalink
Merge branch 'develop' into support-lighthouse-reports
Browse files Browse the repository at this point in the history
  • Loading branch information
wurzelkuchen authored Dec 3, 2024
2 parents 5f5228e + 45b9820 commit 55167d4
Show file tree
Hide file tree
Showing 45 changed files with 1,819 additions and 129 deletions.
63 changes: 63 additions & 0 deletions config/neodymium.properties
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,49 @@
# Whether or not a screenshot should be taken per step
# neodymium.allureAddons.screenshots.perstep.always = false
#
#############################
#
# Advanced screenshot properties
#
#############################

# Enables advanced screenshot capabilities with additional features
# When set to true, provides more sophisticated screenshot taking mechanisms
neodymium.screenshots.enableAdvancedScreenshots = true

# Controls whether screenshots are taken for successful test scenarios
# When set to false, screenshots will not be captured for passing tests
neodymium.screenshots.enableOnSuccess = false

# Enables full-page screenshot capture, capturing entire page content beyond visible viewport
# Useful for long web pages or scrollable content
neodymium.screenshots.fullpagecapture.enable = true

# Determines if the current viewport should be visually highlighted during full-page capture
# When false, no visual indication of current viewport is added
neodymium.screenshots.fullpagecapture.highlightViewport = false

# Controls the directory structure for storing screenshots
# When false, uses a flat directory structure instead of a nested tree-like structure
neodymium.screenshots.enableTreeDirectoryStructure = false

# Sets the highlight color for the viewport during full-page capture
# Uses hexadecimal color code, in this case bright red
neodymium.screenshots.fullpagecapture.highlightColor= #FF0000

# Enables highlighting of the last interacted or focused element in the screenshot
# Helps identify the final state or location of interaction
neodymium.screenshots.highlightLastElement = true

# Sets the highlight color for the last element
# Uses hexadecimal color code
neodymium.screenshots.element.highlightColor = #FF00FF

# Defines the thickness of highlight lines when elements are marked
# Higher values create thicker, more prominent highlight borders
neodymium.screenshots.highlightLineThickness = 4


#############################
#
# DataUtils properties
Expand Down Expand Up @@ -346,6 +389,7 @@ neodymium.lighthouse.assert.thresholdScore.seo = 0.5
# The password of the root certificate for the MITM proxy
# neodymium.localproxy.certificate.password = MITMCertificatePassword


#############################
#
# Allure Report properties
Expand All @@ -354,3 +398,22 @@ neodymium.lighthouse.assert.thresholdScore.seo = 0.5

# Enable the saving of links of called pages in the report
# neodymium.report.enableStepLinks =

neodymium.report.showSelenideErrorDetails = false


#############################
#
# Popup Blocker settings
#
#############################
# We introduced a simple popup blocker to get rid of test affecting popups on a webpage.
# To use it just configure add a CSS selector which targets the close button of the popup
# in the format neodymium.popup.<popupName> = <css selector>.
# For example neodymium.popup.newsletter = #newsletterbox > button.close
#
#neodymium.popup.popupname =
#
# The delay between two checks for a popup in milliseconds
#neodymium.popupInterval = 1000

16 changes: 16 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@
</reporting>

<dependencies>
<dependency>
<groupId>com.assertthat</groupId>
<artifactId>selenium-shutterbug</artifactId>
<version>1.6</version>
<exclusions>
<exclusion>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>xyz.rogfam</groupId>
<artifactId>littleproxy</artifactId>
Expand Down Expand Up @@ -413,5 +424,10 @@
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>2.0-rc1</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class NeodymiumWebDriverListener implements WebDriverListener
@Override
public void beforeFindElement(WebDriver driver, By by)
{
Neodymium.setLastUsedLocator(by);
try
{
if (Neodymium.configuration().debuggingHighlightSelectedElements())
Expand All @@ -35,6 +36,7 @@ public void beforeFindElement(WebDriver driver, By by)
@Override
public void beforeFindElements(WebDriver driver, By by)
{
Neodymium.setLastUsedLocator(by);
try
{
if (Neodymium.configuration().debuggingHighlightSelectedElements())
Expand All @@ -52,6 +54,7 @@ public void beforeFindElements(WebDriver driver, By by)
@Override
public void beforeFindElement(WebElement element, By locator)
{
Neodymium.setLastUsedLocator(element, locator);
try
{
if (Neodymium.configuration().debuggingHighlightSelectedElements())
Expand All @@ -69,6 +72,7 @@ public void beforeFindElement(WebElement element, By locator)
@Override
public void beforeFindElements(WebElement element, By locator)
{
Neodymium.setLastUsedLocator(element, locator);
try
{
if (Neodymium.configuration().debuggingHighlightSelectedElements())
Expand Down
193 changes: 193 additions & 0 deletions src/main/java/com/xceptance/neodymium/common/ScreenshotWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package com.xceptance.neodymium.common;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
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.Point;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.assertthat.selenium_shutterbug.core.Capture;
import com.assertthat.selenium_shutterbug.core.PageSnapshot;
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.codeborne.selenide.ex.UIAssertionError;
import com.xceptance.neodymium.common.testdata.DataSet;
import com.xceptance.neodymium.util.AllureAddons;
import com.xceptance.neodymium.util.Neodymium;

public class ScreenshotWriter
{
private static final Logger log = LoggerFactory.getLogger(ScreenshotWriter.class);

private static boolean highlightViewPort()
{
return Neodymium.configuration().enableFullPageCapture()?Neodymium.configuration().enableHighlightViewport():false;
}


public static void doScreenshot(String displayName, String testClassName, Optional<Throwable> executionException, Annotation[] annotationList)
throws IOException
{
if (Neodymium.configuration().enableAdvancedScreenShots())
{
String timeStamp = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new java.util.Date());
String dataSetName = "";
for (Annotation a : annotationList)
{
if (a.annotationType().equals(DataSet.class))
{
DataSet set = (DataSet) a;
dataSetName += "_DataSet";
if (set.value() > 0)
{
dataSetName += "_" + set.value();
}
if (!set.id().isEmpty())
{
dataSetName += "_" + set.id();
}
}
}
String imageName = displayName + '_' + Neodymium.getBrowserProfileName() + dataSetName + '_' + timeStamp;
if (Neodymium.configuration().enableTreeDirectoryStructure())
{
testClassName = testClassName.replace('.', File.separatorChar);
}

String pathName = getFormatedReportsPath() + File.separator + testClassName;
if (Neodymium.configuration().enableOnSuccess())
{
doScreenshot(imageName, pathName);
}
else
{
if (executionException.isPresent())
{
Throwable error = executionException.get();
if (error instanceof UIAssertionError)
{
doScreenshot(imageName, pathName);
}
}
}
}
}

private static Capture getCaptureMode()
{
return Neodymium.configuration().enableFullPageCapture() ? Capture.FULL : Capture.VIEWPORT;
}

public static String getFormatedReportsPath()
{
return Path.of(System.getProperty("java.io.tmpdir") + Neodymium.configuration().reportsPath()).normalize().toString();
}

public static boolean doScreenshot(String filename) throws IOException
{
return doScreenshot(filename, getFormatedReportsPath());
}

public static boolean doScreenshot(String filename, String pathname) throws IOException
{
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())
{
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);
image = ImageProcessor.blurExceptArea(image, coords);
image = highlightScreenShot(image, coords, Color.decode(Neodymium.configuration().fullScreenHighlightColor()));
}
if (Neodymium.configuration().enableHighlightLastElement() && Neodymium.hasLastUsedElement())
{
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);

boolean result = ImageIO.write(image, "png", outputfile);
if (result)
{
AllureAddons.removeAttachmentFromStepByName("Screenshot");
boolean screenshotAdded = AllureAddons.addAttachmentToStep("Screenshot", "image/png", ".png", new FileInputStream(imagePath));

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

public static BufferedImage highlightScreenShot(BufferedImage sourceImage, Coordinates coords, Color color)
{
int lineWith = Neodymium.configuration().screenshotHighlightLineThickness();
Graphics2D g = sourceImage.createGraphics();

int maxHeigt = sourceImage.getHeight();
int maxWidth = sourceImage.getWidth();

g.setPaint(color);
g.setStroke(new BasicStroke(lineWith));
g.drawRoundRect(
Math.max(coords.getX() + lineWith / 2, 0),
Math.max(coords.getY() + lineWith / 2, 0),
Math.min(coords.getWidth() - lineWith / 2, maxWidth),
Math.min(coords.getHeight() - lineWith / 2, maxHeigt),
5, 5);
g.dispose();
return sourceImage;
}

}
Loading

0 comments on commit 55167d4

Please sign in to comment.