Repository: pazone/ashot
Branch: master
Commit: 0fcb25888aa3
Files: 68
Total size: 157.5 KB
Directory structure:
gitextract_ur45qq_0/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ ├── release.yml
│ └── snapshot.yml
├── .gitignore
├── .mvn/
│ └── wrapper/
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── LICENCE
├── README.md
├── config/
│ └── checkstyle/
│ └── checkstyle.xml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── pazone/
│ │ └── ashot/
│ │ ├── AShot.java
│ │ ├── CdpShootingStrategy.java
│ │ ├── CuttingDecorator.java
│ │ ├── HandlingSickyElementsViewportPastingDecorator.java
│ │ ├── ImageReadException.java
│ │ ├── InvalidViewportHeightException.java
│ │ ├── PageDimensions.java
│ │ ├── RotatingDecorator.java
│ │ ├── ScalingDecorator.java
│ │ ├── Screenshot.java
│ │ ├── ShootingDecorator.java
│ │ ├── ShootingStrategies.java
│ │ ├── ShootingStrategy.java
│ │ ├── SimpleShootingStrategy.java
│ │ ├── ViewportPastingDecorator.java
│ │ ├── comparison/
│ │ │ ├── DiffMarkupPolicy.java
│ │ │ ├── ImageDiff.java
│ │ │ ├── ImageDiffer.java
│ │ │ ├── ImageMarkupPolicy.java
│ │ │ └── PointsMarkupPolicy.java
│ │ ├── coordinates/
│ │ │ ├── Coords.java
│ │ │ ├── CoordsPreparationStrategy.java
│ │ │ ├── CoordsProvider.java
│ │ │ ├── JqueryCoordsProvider.java
│ │ │ └── WebDriverCoordsProvider.java
│ │ ├── cropper/
│ │ │ ├── DefaultCropper.java
│ │ │ ├── ImageCropper.java
│ │ │ └── indent/
│ │ │ ├── BlurFilter.java
│ │ │ ├── IndentCropper.java
│ │ │ ├── IndentFilerFactory.java
│ │ │ ├── IndentFilter.java
│ │ │ └── MonochromeFilter.java
│ │ ├── cutter/
│ │ │ ├── CutStrategy.java
│ │ │ ├── FixedCutStrategy.java
│ │ │ └── VariableCutStrategy.java
│ │ └── util/
│ │ ├── ImageBytesDiffer.java
│ │ ├── ImageTool.java
│ │ ├── InnerScript.java
│ │ └── JsCoords.java
│ └── resources/
│ └── js/
│ ├── coords-single.js
│ └── page_dimensions.js
└── test/
└── java/
└── pazone/
└── ashot/
├── CdpShootingStrategyTest.java
├── CroppersTest.java
├── CuttingDecoratorTest.java
├── DiffMarkupPolicyTest.java
├── DifferTest.java
├── ImageBytesDifferTest.java
├── RotatingDecoratorTest.java
├── ScalingDecoratorTest.java
├── SerializeScreenshotTest.java
├── VariableCutStrategyTest.java
├── VerticalPastingShootingStrategyTest.java
├── coordinates/
│ └── WebDriverCoordsProviderTest.java
└── util/
├── ImageToolTest.java
└── TestImageUtils.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: maven
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ 11, 17, 21, 25 ]
steps:
- uses: actions/checkout@v6
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: ${{ matrix.java }}
cache: 'maven'
- name: Tests
run: ./mvnw clean test
================================================
FILE: .github/workflows/release.yml
================================================
name: Release and Deploy
on:
workflow_dispatch
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '11'
server-id: github
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- name: Release
run: |
git config user.email "pazonec@yandex.ru"
git config --global user.name "Pavel Zorin"
./mvnw --batch-mode -DskipTests -X release:clean release:prepare release:perform
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
MAVEN_USERNAME: ${{ secrets.GITHUB_ACTOR }}
MAVEN_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/snapshot.yml
================================================
name: Deploy Snapshot
on:
push:
branches:
- master
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: 11
cache: 'maven'
server-id: github
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- name: deploy snapshot (github packages)
if: github.ref == 'refs/heads/master'
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
MAVEN_USERNAME: ${{ secrets.GITHUB_ACTOR }}
MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
run: ./mvnw --batch-mode -Dgpg.passphrase=$GPG_PASSPHRASE -DskipTests deploy
================================================
FILE: .gitignore
================================================
target
.git
.idea
*.iml
================================================
FILE: .mvn/wrapper/maven-wrapper.properties
================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=bin
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
================================================
FILE: LICENCE
================================================
Copyright 2014 YANDEX
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
aShot
=====
[](https://github.com/pazone/ashot/releases/latest) [](https://maven-badges.herokuapp.com/maven-central/ru.yandex.qatools.ashot/ashot)
WebDriver Screenshot utility
* Takes a screenshot of a WebElement on different platforms (i.e. desktop browsers, iOS Simulator Mobile Safari, Android Emulator Browser)
* Decorates screenshots
* Provides flexible screenshot comparison
##### WebElement view
aShot takes a screenshot in three simple steps:
* Capture a screenshot of the entire page
* Find the element's size and position
* Crop the original screenshot
As a result, aShot provides an image of the WebElement

#### Maven dependency
```xml
<dependency>
<groupId>ru.yandex.qatools.ashot</groupId>
<artifactId>ashot</artifactId>
<version>1.5.4</version>
</dependency>
```
##### Capturing the entire page
Different WebDrivers take screenshots differently. Some WebDrivers provide a screenshot of the entire page while others handle the viewport only. aShot might be configured to handle browsers with the viewport problem. This example configuration gives a screenshot of the entire page even for Chrome, Mobile Safari, etc.
```java
new AShot()
.shootingStrategy(ShootingStrategies.viewportPasting(100))
.takeScreenshot(webDriver);
```
There are built-in strategies in `ShootingStrategies` for different use cases. In case there are no suitable strategies it is possible to build it using existing strategies as decorators or implement your own.
```java
CutStrategy cutting = new VariableCutStrategy(HEADER_IOS_8_MIN, HEADER_IOS_8_MAX, VIEWPORT_MIN_IOS_8_SIM);
ShootingStrategy rotating = new RotatingDecorator(cutting, ShootingStrategies.simple());
ShootingStrategy pasting = new ViewportPastingDecorator(rotating)
.withScrollTimeout(scrollTimeout);
new AShot()
.shootingStrategy(pasting)
.takeScreenshot(webDriver);
```
##### Capturing the WebElement
One can take a screenshot of particular WebElement(s). Just specify the element(s).
```java
WebElement myWebElement = webDriver.findElement(By.cssSelector("#my_element"));
new AShot()
.takeScreenshot(webDriver, myWebElement);
```
As noted earlier, aShot will find an element's size and position and crop the original image. WebDriver API provides a method to find the WebElement's coordinates but different WebDriver implementations behave differently. The most general approach to find coordinates is to use jQuery, so aShot uses jQuery by default. But some drivers have problems with Javascript execution such as OperaDriver. In this case there is another way to find the WebElement's coordinates.
```java
new AShot()
.coordsProvider(new WebDriverCoordsProvider()) //find coordinates with WebDriver API
.takeScreenshot(webDriver, myWebElement);
```
Feel free to implement your own CoordsProvider. Pull requests are welcome.
##### Prettifying the screenshot
So, let's take a simple screenshot of the weather snippet at Yandex.com.
```java
new AShot()
.takeScreenshot(webDriver, yandexWeatherElement);
```
Here is the result.

`DefaultCropper` is used by default. Can we do better? Yes, we can.
```java
new AShot()
.withCropper(new IndentCropper() // set custom cropper with indentation
.addIndentFilter(blur())) // add filter for indented areas
.takeScreenshot(driver, yandexWeatherElement);
```

This screenshot provides more information about position relative other elements and blurs indent in order to focus on the WebElement.
##### Screenshot comparison
```.takeScreenshot()``` returns a ```Screenshot``` object which contains an image and data for comparison. One can ignore some WebElements from comparison.
```java
Screenshot myScreenshot = new AShot()
.addIgnoredElement(By.cssSelector("#weather .blinking_element")) // ignored element(s)
.takeScreenshot(driver, yandexWeatherElement);
```
Use `ImageDiffer` to find a difference between two images.
```java
ImageDiff diff = new ImageDiffer().makeDiff(myScreenshot, anotherScreenshot);
BufferedImage diffImage = diff.getMarkedImage(); // comparison result with marked differences
```
##### Several elements comparison
`(since 1.2)`
Sometimes one needs to take a screenshot of several independent elements. In this case aShot computes complex comparison area.
```java
List<WebElement> elements = webDriver.findElements(By.cssSelector("#my_element, #popup"));
new AShot()
.withCropper(new IndentCropper()
.addIndentFilter(blur()))
.takeScreenshot(webDriver, elements);
```
Here is the result.

One can see only specified elements (the header and the popup) are focused and will be compared if needed.
##### Ignoring of pixels with predefined color
You can set the color of pixels which should be excluded from comparison of screenshots.
```java
ImageDiffer imageDifferWithIgnored = new ImageDiffer().withIgnoredColor(Color.MAGENTA);
ImageDiff diff = imageDifferWithIgnored.makeDiff(templateWithSomeMagentaPixels, actualScreenshot);
assertFalse(diff.hasDiff());
```
Any pixels in template with color `MAGENTA` (255, 0, 255 in RGB) will be ignored during comparison.
================================================
FILE: config/checkstyle/checkstyle.xml
================================================
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the sun coding conventions from:
- the Java Language Specification at
https://docs.oracle.com/javase/specs/jls/se11/html/index.html
- the Sun Code Conventions at https://www.oracle.com/technetwork/java/codeconvtoc-136057.html
- the Javadoc guidelines at
https://www.oracle.com/technetwork/java/javase/documentation/index-137868.html
- the JDK Api documentation https://docs.oracle.com/en/java/javase/11/
- some best practices
Checkstyle is very configurable. Be sure to read the documentation at
https://checkstyle.org (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
https://checkstyle.org/5.x/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<property name="severity" value="error"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Checks whether files end with a new line. -->
<!-- See https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- Checks that property files contain the same keys. -->
<!-- See https://checkstyle.org/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See https://checkstyle.org/config_sizes.html -->
<module name="FileLength"/>
<module name="LineLength">
<property name="fileExtensions" value="java"/>
<property name="max" value="120"/>
</module>
<!-- Checks for whitespace -->
<!-- See https://checkstyle.org/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See https://checkstyle.org/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<!-- Checks for Headers -->
<!-- See https://checkstyle.org/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Checks for Javadoc comments. -->
<!-- See https://checkstyle.org/config_javadoc.html -->
<module name="InvalidJavadocPosition"/>
<module name="JavadocMethod"/>
<module name="JavadocType"/>
<!-- Checks for Naming Conventions. -->
<!-- See https://checkstyle.org/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="MemberName"/>
<module name="MethodName"/>
<module name="PackageName"/>
<module name="ParameterName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See https://checkstyle.org/config_import.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports">
<property name="processJavadoc" value="false"/>
</module>
<!-- Checks for Size Violations. -->
<!-- See https://checkstyle.org/config_sizes.html -->
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See https://checkstyle.org/config_whitespace.html -->
<module name="EmptyForIteratorPad"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See https://checkstyle.org/config_modifiers.html -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See https://checkstyle.org/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See https://checkstyle.org/config_coding.html -->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MissingSwitchDefault"/>
<module name="MultipleVariableDeclarations"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<!-- Checks for class design -->
<!-- See https://checkstyle.org/config_design.html -->
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<!-- Miscellaneous other checks. -->
<!-- See https://checkstyle.org/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="TodoComment"/>
<module name="UpperEll"/>
</module>
</module>
================================================
FILE: mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ]; then
if [ -f /usr/local/etc/mavenrc ]; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ]; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ]; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false
darwin=false
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true ;;
Darwin*)
darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"
export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"
export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ]; then
if [ -r /etc/gentoo-release ]; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin; then
[ -n "$JAVA_HOME" ] \
&& JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] \
&& CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \
&& JAVA_HOME="$(
cd "$JAVA_HOME" || (
echo "cannot cd into $JAVA_HOME." >&2
exit 1
)
pwd
)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin; then
javaHome="$(dirname "$javaExecutable")"
javaExecutable="$(cd "$javaHome" && pwd -P)/javac"
else
javaExecutable="$(readlink -f "$javaExecutable")"
fi
javaHome="$(dirname "$javaExecutable")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ]; then
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(
\unset -f command 2>/dev/null
\command -v java
)"
fi
fi
if [ ! -x "$JAVACMD" ]; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ]; then
echo "Warning: JAVA_HOME environment variable is not set." >&2
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]; then
echo "Path not specified to find_maven_basedir" >&2
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ]; do
if [ -d "$wdir"/.mvn ]; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(
cd "$wdir/.." || exit 1
pwd
)
fi
# end of workaround
done
printf '%s' "$(
cd "$basedir" || exit 1
pwd
)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
# Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' <"$1"
fi
}
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
fi
while IFS="=" read -r key value; do
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in wrapperUrl)
wrapperUrl="$safeValue"
break
;;
esac
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget >/dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl >/dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in wrapperSha256Sum)
wrapperSha256Sum=$value
break
;;
esac
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum >/dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] \
&& JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] \
&& CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] \
&& MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo. >&2
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo. >&2
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo. >&2
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo. >&2
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%
================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.pazone</groupId>
<artifactId>ashot</artifactId>
<version>1.6.1-SNAPSHOT</version>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/pazone/ashot/issues</url>
</issueManagement>
<scm>
<url>https://github.com/pazone/ashot</url>
<connection>scm:git:git@github.com:pazone/ashot.git</connection>
<developerConnection>scm:git:git@github.com:pazone/ashot.git</developerConnection>
<tag>HEAD</tag>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<selenium.version>4.43.0</selenium.version>
</properties>
<name>AShot WebDriver Utility</name>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chromium-driver</artifactId>
<version>${selenium.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.21.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.2</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>3.0</version>
</dependency>
<!--test-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.14.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
<configuration>
<source>11</source>
<target>11</target>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.12.0</version>
<configuration>
<additionalOptions>
<additionalOption>-Xdoclint:none</additionalOption>
</additionalOptions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<scmCommentPrefix>[ci skip]</scmCommentPrefix>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/pazone/ashot</url>
</repository>
</distributionManagement>
<profiles>
<profile>
<id>checkstyle-checks</id>
<activation>
<jdk>[21,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<configLocation>config/checkstyle/checkstyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
</configuration>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>13.4.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performSigning</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.8</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
================================================
FILE: src/main/java/pazone/ashot/AShot.java
================================================
package pazone.ashot;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import pazone.ashot.coordinates.Coords;
import pazone.ashot.coordinates.CoordsPreparationStrategy;
import pazone.ashot.coordinates.CoordsProvider;
import pazone.ashot.coordinates.WebDriverCoordsProvider;
import pazone.ashot.cropper.ImageCropper;
import pazone.ashot.cropper.DefaultCropper;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static java.util.Collections.singletonList;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class AShot implements Serializable {
private CoordsProvider coordsProvider = new WebDriverCoordsProvider();
private ImageCropper cropper = new DefaultCropper();
private Set<By> ignoredLocators = new HashSet<>();
private Set<Coords> ignoredAreas = new HashSet<>();
private ShootingStrategy shootingStrategy = new SimpleShootingStrategy();
public AShot coordsProvider(final CoordsProvider coordsProvider) {
this.coordsProvider = coordsProvider;
return this;
}
@SuppressWarnings("UnusedDeclaration")
public AShot imageCropper(ImageCropper cropper) {
this.cropper = cropper;
return this;
}
/**
* Sets the list of locators to ignore during image comparison.
*
* @param ignoredElements list of By
* @return this
*/
@SuppressWarnings("UnusedDeclaration")
public synchronized AShot ignoredElements(final Set<By> ignoredElements) {
this.ignoredLocators = ignoredElements;
return this;
}
/**
* Adds selector of ignored element.
*
* @param selector By
* @return this
*/
public synchronized AShot addIgnoredElement(final By selector) {
this.ignoredLocators.add(selector);
return this;
}
/**
* Sets a collection of wittingly ignored coords.
* @param ignoredAreas Set of ignored areas
* @return aShot
*/
@SuppressWarnings("UnusedDeclaration")
public synchronized AShot ignoredAreas(final Set<Coords> ignoredAreas) {
this.ignoredAreas = ignoredAreas;
return this;
}
/**
* Adds coordinated to set of wittingly ignored coords.
* @param area coords of wittingly ignored coords
* @return aShot;
*/
@SuppressWarnings("UnusedDeclaration")
public synchronized AShot addIgnoredArea(Coords area) {
this.ignoredAreas.add(area);
return this;
}
/**
* Sets the policy of taking screenshot.
*
* @param strategy shooting strategy
* @return this
* @see ShootingStrategy
*/
public AShot shootingStrategy(ShootingStrategy strategy) {
this.shootingStrategy = strategy;
return this;
}
/**
* Takes the screenshot of given elements
* If elements were not found screenshot of whole page will be returned
*
* @param driver WebDriver instance
* @param elements Web elements to screenshot
* @return Screenshot with cropped image and list of ignored areas on screenshot
* @throws RuntimeException when something goes wrong
* @see Screenshot
*/
public Screenshot takeScreenshot(WebDriver driver, Collection<WebElement> elements) {
Set<Coords> elementCoords = coordsProvider.ofElements(driver, elements);
BufferedImage shot = shootingStrategy.getScreenshot(driver, elementCoords);
Screenshot screenshot = cropper.crop(shot, shootingStrategy.prepareCoords(elementCoords));
Set<Coords> ignoredAreas = compileIgnoredAreas(driver, CoordsPreparationStrategy.intersectingWith(screenshot));
screenshot.setIgnoredAreas(shootingStrategy.prepareCoords(ignoredAreas));
return screenshot;
}
/**
* Takes the screenshot of given element
*
* @param driver WebDriver instance
* @param element Web element to screenshot
* @return Screenshot with cropped image and list of ignored areas on screenshot
* @throws RuntimeException when something goes wrong
* @see Screenshot
*/
public Screenshot takeScreenshot(WebDriver driver, WebElement element) {
return takeScreenshot(driver, singletonList(element));
}
/**
* Takes the screenshot of whole page
*
* @param driver WebDriver instance
* @return Screenshot with whole page image and list of ignored areas on screenshot
* @see Screenshot
*/
public Screenshot takeScreenshot(WebDriver driver) {
Screenshot screenshot = new Screenshot(shootingStrategy.getScreenshot(driver));
screenshot.setIgnoredAreas(compileIgnoredAreas(driver, CoordsPreparationStrategy.simple()));
return screenshot;
}
protected synchronized Set<Coords> compileIgnoredAreas(WebDriver driver,
CoordsPreparationStrategy preparationStrategy) {
Set<Coords> ignoredCoords = new HashSet<>();
for (By ignoredLocator : ignoredLocators) {
List<WebElement> ignoredElements = driver.findElements(ignoredLocator);
if (!ignoredElements.isEmpty()) {
ignoredCoords.addAll(preparationStrategy.prepare(coordsProvider.ofElements(driver, ignoredElements)));
}
}
for (Coords ignoredArea : ignoredAreas) {
ignoredCoords.addAll(preparationStrategy.prepare(singletonList(ignoredArea)));
}
return ignoredCoords;
}
@SuppressWarnings("UnusedDeclaration")
public synchronized Set<By> getIgnoredLocators() {
return ignoredLocators;
}
}
================================================
FILE: src/main/java/pazone/ashot/CdpShootingStrategy.java
================================================
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;
}
}
================================================
FILE: src/main/java/pazone/ashot/CuttingDecorator.java
================================================
package pazone.ashot;
import org.openqa.selenium.WebDriver;
import pazone.ashot.cutter.CutStrategy;
import pazone.ashot.cutter.FixedCutStrategy;
import pazone.ashot.coordinates.Coords;
import java.awt.image.BufferedImage;
import java.util.Set;
/**
* Cuts browser's header/footer off from screenshot.
*
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class CuttingDecorator extends ShootingDecorator {
private CutStrategy cutStrategy;
public CuttingDecorator(ShootingStrategy strategy) {
super(strategy);
}
/**
* Will use {@link FixedCutStrategy} to cut off header and footer.
* @param headerToCut - height of header in pixels
* @param footerToCut - height of footer in pixels
* @return Cutting decorator
*/
public CuttingDecorator withCut(int headerToCut, int footerToCut) {
return withCutStrategy(new FixedCutStrategy(headerToCut, footerToCut));
}
/**
* Will use {@link FixedCutStrategy} to cut off header and footer.
* @param headerToCut - height of header in pixels
* @param footerToCut - height of footer in pixels
* @param leftBarToCut - width of left bar in pixels
* @param rightBarToCut - width of right bar in pixels
* @return Cutting decorator
*/
public CuttingDecorator withCut(int headerToCut, int footerToCut, int leftBarToCut, int rightBarToCut) {
return withCutStrategy(new FixedCutStrategy(headerToCut, footerToCut, leftBarToCut, rightBarToCut));
}
/**
* Will use custom cut strategy, for example {@link pazone.ashot.cutter.VariableCutStrategy}.
* @param cutStrategy - strategy to get height of browser's header
* @return Cutting decorator
*/
public CuttingDecorator withCutStrategy(CutStrategy cutStrategy) {
this.cutStrategy = cutStrategy;
return this;
}
@Override
public BufferedImage getScreenshot(WebDriver wd) {
BufferedImage baseImage = getShootingStrategy().getScreenshot(wd);
int h = baseImage.getHeight();
int w = baseImage.getWidth();
final int headerToCut = getHeaderToCut(wd);
final int footerToCut = getFooterToCut(wd);
final int leftBarToCut = getLeftBarToCut(wd);
final int rightBarToCut = getRightBarToCut(wd);
return baseImage.getSubimage(leftBarToCut, headerToCut, w - leftBarToCut - rightBarToCut,
h - headerToCut - footerToCut);
}
@Override
public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords) {
return getScreenshot(wd);
}
protected int getHeaderToCut(WebDriver wd) {
return cutStrategy.getHeaderHeight(wd);
}
protected int getFooterToCut(WebDriver wd) {
return cutStrategy.getFooterHeight(wd);
}
protected int getLeftBarToCut(WebDriver wd) {
return cutStrategy.getLeftBarWidth(wd);
}
protected int getRightBarToCut(WebDriver wd) {
return cutStrategy.getRightBarWidth(wd);
}
}
================================================
FILE: src/main/java/pazone/ashot/HandlingSickyElementsViewportPastingDecorator.java
================================================
package pazone.ashot;
import java.awt.image.BufferedImage;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
public class HandlingSickyElementsViewportPastingDecorator extends ViewportPastingDecorator {
private final int stickyHeaderHeight;
private final int stickyFooterHeight;
private int currentChunkIndex;
public HandlingSickyElementsViewportPastingDecorator(ShootingStrategy strategy, int stickyHeaderHeight,
int stickyFooterHeight) {
super(strategy);
this.stickyHeaderHeight = stickyHeaderHeight;
this.stickyFooterHeight = stickyFooterHeight;
}
@Override
protected PageDimensions getPageDimensions(WebDriver driver) {
PageDimensions pageDimension = super.getPageDimensions(driver);
return new PageDimensions(pageDimension.getPageHeight(), pageDimension.getViewportWidth(),
pageDimension.getViewportHeight() - stickyHeaderHeight - stickyFooterHeight);
}
@Override
protected BufferedImage getChunk(WebDriver wd, int currentChunkIndex, int totalNumberOfChunks) {
this.currentChunkIndex = currentChunkIndex;
CuttingDecorator cuttingDecorator = new CuttingDecorator(getShootingStrategy());
if (currentChunkIndex == 0) {
cuttingDecorator.withCut(0, stickyFooterHeight);
} else if (currentChunkIndex == totalNumberOfChunks - 1) {
cuttingDecorator.withCut(stickyHeaderHeight, 0);
} else {
cuttingDecorator.withCut(stickyHeaderHeight, stickyFooterHeight);
}
return cuttingDecorator.getScreenshot(wd);
}
@Override
protected int getCurrentScrollY(JavascriptExecutor js) {
int currentScrollY = super.getCurrentScrollY(js);
return currentChunkIndex == 0 ? currentScrollY : currentScrollY + stickyHeaderHeight;
}
}
================================================
FILE: src/main/java/pazone/ashot/ImageReadException.java
================================================
package pazone.ashot;
/**
* @author <a href="frolic@yandex-team.ru">Vyacheslav Frolov</a>
*/
public class ImageReadException extends RuntimeException {
public ImageReadException(String message) {
super(message);
}
public ImageReadException(String message, Exception e) {
super(message, e);
}
}
================================================
FILE: src/main/java/pazone/ashot/InvalidViewportHeightException.java
================================================
package pazone.ashot;
/**
* @author <a href="frolic@yandex-team.ru">Vyacheslav Frolov</a>
*/
public class InvalidViewportHeightException extends RuntimeException {
public InvalidViewportHeightException(String message) {
super(message);
}
public InvalidViewportHeightException(String message, Exception e) {
super(message, e);
}
}
================================================
FILE: src/main/java/pazone/ashot/PageDimensions.java
================================================
package pazone.ashot;
public final class PageDimensions {
private final int pageHeight;
private final int viewportWidth;
private final int viewportHeight;
public PageDimensions(int pageHeight, int viewportWidth, int viewportHeight) {
this.pageHeight = pageHeight;
this.viewportHeight = viewportHeight;
this.viewportWidth = viewportWidth;
}
public int getPageHeight() {
return pageHeight;
}
public int getViewportWidth() {
return viewportWidth;
}
public int getViewportHeight() {
return viewportHeight;
}
}
================================================
FILE: src/main/java/pazone/ashot/RotatingDecorator.java
================================================
package pazone.ashot;
import org.openqa.selenium.WebDriver;
import pazone.ashot.coordinates.Coords;
import pazone.ashot.cutter.CutStrategy;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Set;
import static java.awt.image.BufferedImage.TYPE_4BYTE_ABGR;
/**
* @author <a href="rovner@yandex-team.ru">Rovniakov Viacheslav</a>
*/
public class RotatingDecorator implements ShootingStrategy {
private final CutStrategy cutStrategy;
private final ShootingStrategy shootingStrategy;
public RotatingDecorator(CutStrategy cutStrategy, ShootingStrategy shootingStrategy) {
this.cutStrategy = cutStrategy;
this.shootingStrategy = shootingStrategy;
}
@Override
public BufferedImage getScreenshot(WebDriver wd) {
return rotate(shootingStrategy.getScreenshot(wd), wd);
}
@Override
public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords) {
return getScreenshot(wd);
}
@Override
public Set<Coords> prepareCoords(Set<Coords> coordsSet) {
return coordsSet;
}
private BufferedImage rotate(BufferedImage baseImage, WebDriver wd) {
BufferedImage rotated = new BufferedImage(baseImage.getHeight(), baseImage.getWidth(), TYPE_4BYTE_ABGR);
Graphics2D graphics = rotated.createGraphics();
double theta = 3 * Math.PI / 2;
int origin = baseImage.getWidth() / 2;
graphics.rotate(theta, origin, origin);
graphics.drawImage(baseImage, null, 0, 0);
int rotatedHeight = rotated.getHeight();
int rotatedWidth = rotated.getWidth();
int headerToCut = cutStrategy.getHeaderHeight(wd);
return rotated.getSubimage(0, headerToCut, rotatedWidth, rotatedHeight - headerToCut);
}
}
================================================
FILE: src/main/java/pazone/ashot/ScalingDecorator.java
================================================
package pazone.ashot;
import org.openqa.selenium.WebDriver;
import pazone.ashot.coordinates.Coords;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.Set;
/**
* Will scale down image that was aquired by WebDriver.
* Scaling is performed according to device pixel ratio (DPR)
* Useful for browsers on portable devices with Retina displays.
*/
public class ScalingDecorator extends ShootingDecorator {
private static final Float STANDARD_DRP = 1F;
private Float dprX = STANDARD_DRP;
private Float dprY = STANDARD_DRP;
public ScalingDecorator(ShootingStrategy strategy) {
super(strategy);
}
@Override
public BufferedImage getScreenshot(WebDriver wd) {
return scale(getShootingStrategy().getScreenshot(wd));
}
@Override
public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords) {
return scale(getShootingStrategy().getScreenshot(wd, coords));
}
public ScalingDecorator withDprX(float dprX) {
this.dprX = dprX;
return this;
}
public ScalingDecorator withDprY(float dprY) {
this.dprY = dprY;
return this;
}
public ScalingDecorator withDpr(float dpr) {
return this
.withDprX(dpr)
.withDprY(dpr);
}
private BufferedImage scale(BufferedImage image) {
if (STANDARD_DRP.equals(dprY) && STANDARD_DRP.equals(dprX)) {
return image;
}
int scaledWidth = (int) (image.getWidth() / dprX);
int scaledHeight = (int) (image.getHeight() / dprY);
final BufferedImage bufferedImage = new BufferedImage(scaledWidth, scaledHeight, image.getType());
final Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setComposite(AlphaComposite.Src);
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
graphics2D.dispose();
return bufferedImage;
}
}
================================================
FILE: src/main/java/pazone/ashot/Screenshot.java
================================================
package pazone.ashot;
import pazone.ashot.coordinates.Coords;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
* @author <a href="eoff@yandex-team.ru">Maksim Mukosey</a>
*
* Result of screen capture.
* Contains final processed image and all required information for image comparison.
*/
public class Screenshot implements Serializable {
private static final long serialVersionUID = 1241241256734156872L;
private transient BufferedImage image;
private Set<Coords> ignoredAreas = new HashSet<>();
private Set<Coords> coordsToCompare;
/**
* Coords, containing x and y shift from origin image coordinates system
* Actually it is coordinates of cropped area on origin image.
* Should be set if image is cropped.
*/
private Coords originShift = new Coords(0, 0);
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
public Screenshot(BufferedImage image) {
this.image = image;
this.coordsToCompare = Collections.singleton(Coords.ofImage(image));
}
public Set<Coords> getCoordsToCompare() {
return coordsToCompare;
}
public void setCoordsToCompare(Set<Coords> coordsToCompare) {
this.coordsToCompare = coordsToCompare;
}
public Set<Coords> getIgnoredAreas() {
return ignoredAreas;
}
public void setIgnoredAreas(Set<Coords> ignoredAreas) {
this.ignoredAreas = ignoredAreas;
}
public Coords getOriginShift() {
return originShift;
}
public void setOriginShift(Coords originShift) {
this.originShift = originShift;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
ImageIO.write(image, "png", out);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
image = ImageIO.read(in);
}
}
================================================
FILE: src/main/java/pazone/ashot/ShootingDecorator.java
================================================
package pazone.ashot;
import pazone.ashot.coordinates.Coords;
import java.util.Set;
public abstract class ShootingDecorator implements ShootingStrategy {
private final ShootingStrategy shootingStrategy;
protected ShootingDecorator(ShootingStrategy shootingStrategy) {
this.shootingStrategy = shootingStrategy;
}
public ShootingStrategy getShootingStrategy() {
return shootingStrategy;
}
/**
* Default behavior is not to change coords, because by default coordinates are not changed
*/
@Override
public Set<Coords> prepareCoords(Set<Coords> coordsSet) {
return coordsSet;
}
}
================================================
FILE: src/main/java/pazone/ashot/ShootingStrategies.java
================================================
package pazone.ashot;
import pazone.ashot.cutter.CutStrategy;
import pazone.ashot.cutter.FixedCutStrategy;
import pazone.ashot.cutter.VariableCutStrategy;
/**
* Utility class for different shooting strategies.
*/
public final class ShootingStrategies {
private static final int SCROLL_TIMEOUT_IOS = 500;
private static final int HEADER_IOS_7 = 98;
private static final int HEADER_IOS_8_MIN = 41;
private static final int HEADER_IOS_8_MAX = 65;
private static final int VIEWPORT_MIN_IOS_8 = 960;
private static final int VIEWPORT_MIN_IOS_8_SIM = 1250;
private static final CutStrategy CUT_STRATEGY_IOS_7 = new FixedCutStrategy(HEADER_IOS_7, 0);
private static final CutStrategy CUT_STRATEGY_IOS_8 = iOS8CutStrategy(VIEWPORT_MIN_IOS_8);
private static final CutStrategy CUT_STRATEGY_IOS_8_SIM = iOS8CutStrategy(VIEWPORT_MIN_IOS_8_SIM);
private ShootingStrategies() {
throw new UnsupportedOperationException();
}
/**
* Simple shooting strategy. No image processing is performed.
* @return new instance of SimpleShootingStrategy
*/
public static ShootingStrategy simple() {
return new SimpleShootingStrategy();
}
/**
* Will scale down image according to dpr specified.
*
* @param shootingStrategy Shooting strategy used to take screenshots before scaling
* @param dpr device pixel ratio
* @return {@code ShootingStrategy} that will scale image according to {@code dpr}
*/
public static ShootingStrategy scaling(ShootingStrategy shootingStrategy, float dpr) {
return new ScalingDecorator(shootingStrategy).withDpr(dpr);
}
/**
* Will scale down image according to dpr specified.
*
* @param dpr device pixel ratio
* @return {@code ShootingStrategy} that will scale image according to {@code dpr}
*/
public static ShootingStrategy scaling(float dpr) {
return scaling(simple(), dpr);
}
/**
* Will cut header and footer off from screen shot.
*
* @param shootingStrategy Shooting strategy used to take screenshots before cutting
* @param cutStrategy {@link CutStrategy} to use. See {@link FixedCutStrategy} or {@link VariableCutStrategy}
* @return {@code ShootingStrategy} with custom cutting strategy
*/
public static ShootingStrategy cutting(ShootingStrategy shootingStrategy, CutStrategy cutStrategy) {
return new CuttingDecorator(shootingStrategy).withCutStrategy(cutStrategy);
}
/**
* Will cut header and footer off from screen shot.
*
* @param cutStrategy {@link CutStrategy} to use. See {@link FixedCutStrategy} or {@link VariableCutStrategy}
* @return {@code ShootingStrategy} with custom cutting strategy
*/
public static ShootingStrategy cutting(CutStrategy cutStrategy) {
return cutting(simple(), cutStrategy);
}
/**
* Will cut header and footer off from screen shot.
*
* @param headerToCut header to cut in pixels
* @param footerToCut footer to cut in pixels
* @return {@code ShootingStrategy} with {@link FixedCutStrategy} cutting strategy
*/
public static ShootingStrategy cutting(int headerToCut, int footerToCut) {
return cutting(new FixedCutStrategy(headerToCut, footerToCut));
}
/**
* Will scroll viewport while shooting.
*
* @param shootingStrategy Shooting strategy used to take screenshots between scrolls
* @param scrollTimeout time between viewportPasting scrolls in milliseconds
* @return {@code ShootingStrategy} with custom timeout between scrolls
*/
public static ShootingStrategy viewportPasting(ShootingStrategy shootingStrategy, int scrollTimeout) {
return new ViewportPastingDecorator(shootingStrategy).withScrollTimeout(scrollTimeout);
}
/**
* Will scroll viewport while shooting.
*
* @param scrollTimeout time between viewportPasting scrolls in milliseconds
* @return {@code ShootingStrategy} with custom timeout between scrolls
*/
public static ShootingStrategy viewportPasting(int scrollTimeout) {
return viewportPasting(simple(), scrollTimeout);
}
/**
* Will scroll viewport while shooting and cut off browser's header and footer
*
* @param shootingStrategy Underneath shooting strategy
* @param scrollTimeout time between scrolls in milliseconds
* @param cutStrategy strategy to cut header and footer from image
*
* @return {@code ShootingStrategy} with custom scroll timeout and cutting strategy
*/
public static ShootingStrategy viewportNonRetina(ShootingStrategy shootingStrategy, int scrollTimeout,
CutStrategy cutStrategy) {
return viewportPasting(cutting(shootingStrategy, cutStrategy), scrollTimeout);
}
/**
* Will scroll viewport while shooting and cut off browser's header and footer
*
* @param scrollTimeout time between scrolls in milliseconds
* @param cutStrategy strategy to cut header and footer from image
*
* @return {@code ShootingStrategy} with custom scroll timeout and cutting strategy
*/
public static ShootingStrategy viewportNonRetina(int scrollTimeout, CutStrategy cutStrategy) {
return viewportPasting(cutting(cutStrategy), scrollTimeout);
}
/**
* Will scroll viewportPasting while shooting and cut off browser's header and footer
*
* @param scrollTimeout time between scrolls in milliseconds
* @param headerToCut height of header to cut from image
* @param footerToCut height of footer to cut from image
*
* @return {@code ShootingStrategy} with custom scroll timeout and header/footer to cut
*/
public static ShootingStrategy viewportNonRetina(int scrollTimeout, int headerToCut, int footerToCut) {
return viewportNonRetina(scrollTimeout, new FixedCutStrategy(headerToCut, footerToCut));
}
/**
* Will scale screenshots and scroll viewportPasting while shooting and cut off browser's header/footer
*
* @param shootingStrategy Underneath shooting strategy
* @param scrollTimeout time between scrolls in milliseconds
* @param cutStrategy strategy to cut header and footer from image
* @param dpr device pixel ratio
*
* @return {@code ShootingStrategy} with custom DPR scaling and scroll timeout and cutting strategy
*/
public static ShootingStrategy viewportRetina(ShootingStrategy shootingStrategy, int scrollTimeout,
CutStrategy cutStrategy, float dpr) {
ShootingStrategy scalingDecorator = scaling(shootingStrategy, dpr);
return viewportNonRetina(scalingDecorator, scrollTimeout, cutStrategy);
}
/**
* Will scale screenshots and scroll viewportPasting while shooting and cut off browser's header/footer
*
* @param scrollTimeout time between scrolls in milliseconds
* @param cutStrategy strategy to cut header and footer from image
* @param dpr device pixel ratio
*
* @return {@code ShootingStrategy} with custom DPR scaling and scroll timeout and cutting strategy
*/
public static ShootingStrategy viewportRetina(int scrollTimeout, CutStrategy cutStrategy, float dpr) {
return viewportRetina(simple(), scrollTimeout, cutStrategy, dpr);
}
/**
* Will scale screenshots and scroll viewportPasting while shooting and cut off browser's header/footer
*
* @param scrollTimeout time between scrolls in milliseconds
* @param headerToCut height of header to cut from image
* @param footerToCut height of footer to cut from image
* @param dpr device pixel ratio
*
* @return {@code ShootingStrategy} with custom DPR scaling and scroll timeout and cutting strategy
*/
public static ShootingStrategy viewportRetina(int scrollTimeout, int headerToCut, int footerToCut, float dpr) {
return viewportRetina(scrollTimeout, new FixedCutStrategy(headerToCut, footerToCut), dpr);
}
/**
* Will scale screenshots and scroll viewportPasting while shooting and cut off browser's header/footer
*
* @param shootingStrategy Underneath shooting strategy
* @param scrollTimeout time between scrolls in milliseconds
* @param headerToCut height of header to cut from image
* @param footerToCut height of footer to cut from image
* @param dpr device pixel ratio
*
* @return {@code ShootingStrategy} with custom DPR scaling and scroll timeout and cutting strategy
*/
public static ShootingStrategy viewportRetina(ShootingStrategy shootingStrategy, int scrollTimeout, int headerToCut,
int footerToCut, float dpr) {
return viewportRetina(shootingStrategy, scrollTimeout, new FixedCutStrategy(headerToCut, footerToCut), dpr);
}
public static ShootingStrategy iPad2WithIOS7(ShootingStrategy shootingStrategy) {
return viewportIOSNonRetina(shootingStrategy, CUT_STRATEGY_IOS_7);
}
public static ShootingStrategy iPad2WithIOS7() {
return iPad2WithIOS7(simple());
}
public static ShootingStrategy iPad2WithIOS8(ShootingStrategy shootingStrategy) {
return viewportIOSNonRetina(shootingStrategy, CUT_STRATEGY_IOS_8);
}
public static ShootingStrategy iPad2WithIOS8() {
return iPad2WithIOS8(simple());
}
public static ShootingStrategy iPad2WithIOS8Simulator(ShootingStrategy shootingStrategy) {
return viewportIOSNonRetina(shootingStrategy, CUT_STRATEGY_IOS_8_SIM);
}
public static ShootingStrategy iPad2WithIOS8Simulator() {
return iPad2WithIOS8Simulator(simple());
}
public static ShootingStrategy iPad2WithIOS8Retina(ShootingStrategy shootingStrategy) {
return viewportIOSRetina(shootingStrategy, CUT_STRATEGY_IOS_8);
}
public static ShootingStrategy iPad2WithIOS8Retina() {
return iPad2WithIOS8Retina(simple());
}
public static ShootingStrategy iPad2WithIOS8RetinaSimulator(ShootingStrategy shootingStrategy) {
return viewportIOSRetina(shootingStrategy, CUT_STRATEGY_IOS_8_SIM);
}
public static ShootingStrategy iPad2WithIOS8RetinaSimulator() {
return iPad2WithIOS8RetinaSimulator(simple());
}
/**
* Will create screenshot's of the whole page and rotate landscape images
*
* @param scrollTimeout time between scrolls in milliseconds
* @param cutStrategy strategy to cut header and footer from image
* @return {@code ShootingStrategy} which will shoot whole page and rotate landscape images
*/
public static ShootingStrategy iPadLandscapeOrientation(int scrollTimeout, CutStrategy cutStrategy) {
return viewportPasting(iPadLandscapeOrientationSimple(cutStrategy), scrollTimeout);
}
/**
* Will rotate screenshot's made on iPad in landscape orientation
*
* @param cutStrategy strategy to cut header and footer from image
* @return {@code ShootingStrategy} which will rotate image
*/
public static ShootingStrategy iPadLandscapeOrientationSimple(CutStrategy cutStrategy) {
return new RotatingDecorator(cutStrategy, simple());
}
private static ShootingStrategy viewportIOSNonRetina(ShootingStrategy shootingStrategy, CutStrategy cutStrategy) {
return viewportNonRetina(shootingStrategy, SCROLL_TIMEOUT_IOS, cutStrategy);
}
private static ShootingStrategy viewportIOSRetina(ShootingStrategy shootingStrategy, CutStrategy cutStrategy) {
return viewportRetina(shootingStrategy, SCROLL_TIMEOUT_IOS, cutStrategy, 2F);
}
private static CutStrategy iOS8CutStrategy(int minViewport) {
return new VariableCutStrategy(HEADER_IOS_8_MIN, HEADER_IOS_8_MAX, minViewport);
}
}
================================================
FILE: src/main/java/pazone/ashot/ShootingStrategy.java
================================================
package pazone.ashot;
import org.openqa.selenium.WebDriver;
import pazone.ashot.coordinates.Coords;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.util.Set;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public interface ShootingStrategy extends Serializable {
/**
* Get's screenshot of whole page or viewport (depends on browser)
*
* @param wd WebDrvier
* @return image of the whole page or viewport
*/
BufferedImage getScreenshot(WebDriver wd);
/**
* Get's screenshot of area or areas that are defined by {@link Coords}
*
* @param wd WebDriver
* @param coords Set of coordinates to shoot
* @return minimal image with required coords
*/
BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords);
/**
* Prepares coordinated for cropper and ignored areas
*
* @param coordsSet to prepare
* @return New set of prepared coordinates
*/
Set<Coords> prepareCoords(Set<Coords> coordsSet);
}
================================================
FILE: src/main/java/pazone/ashot/SimpleShootingStrategy.java
================================================
package pazone.ashot;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.Augmenter;
import pazone.ashot.coordinates.Coords;
import pazone.ashot.util.ImageTool;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Set;
/**
* Gets screenshot from webdriver.
*/
public class SimpleShootingStrategy implements ShootingStrategy {
@Override
public BufferedImage getScreenshot(WebDriver wd) {
TakesScreenshot takesScreenshot;
try {
takesScreenshot = (TakesScreenshot) wd;
} catch (ClassCastException ignored) {
takesScreenshot = (TakesScreenshot) new Augmenter().augment(wd);
}
try {
byte[] imageBytes = takesScreenshot.getScreenshotAs(OutputType.BYTES);
return ImageTool.toBufferedImage(imageBytes);
} catch (IOException e) {
throw new ImageReadException("Can not parse screenshot data", e);
}
}
@Override
public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords) {
return getScreenshot(wd);
}
/**
* Default behavior is not to change coords, because by default coordinates are not changed
*/
@Override
public Set<Coords> prepareCoords(Set<Coords> coordsSet) {
return coordsSet;
}
}
================================================
FILE: src/main/java/pazone/ashot/ViewportPastingDecorator.java
================================================
package pazone.ashot;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import pazone.ashot.coordinates.Coords;
import pazone.ashot.util.InnerScript;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Will scroll viewport and shoot to get an image of full page.
* Useful for browsers on portable devices.
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class ViewportPastingDecorator extends ShootingDecorator {
public static final String PAGE_DIMENSIONS_JS = "js/page_dimensions.js";
protected int scrollTimeout = 0;
private Coords shootingArea;
public ViewportPastingDecorator(ShootingStrategy strategy) {
super(strategy);
}
public ViewportPastingDecorator withScrollTimeout(int scrollTimeout) {
this.scrollTimeout = scrollTimeout;
return this;
}
@Override
public BufferedImage getScreenshot(WebDriver wd) {
return getScreenshot(wd, null);
}
@Override
public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coordsSet) {
JavascriptExecutor js = (JavascriptExecutor) wd;
int initialY = getCurrentScrollY(js);
try {
PageDimensions pageDimensions = getPageDimensions(wd);
shootingArea = getShootingCoords(coordsSet, pageDimensions);
BufferedImage finalImage = new BufferedImage(pageDimensions.getViewportWidth(), shootingArea.height,
BufferedImage.TYPE_3BYTE_BGR);
Graphics2D graphics = finalImage.createGraphics();
int viewportHeight = pageDimensions.getViewportHeight();
int scrollTimes = (int) Math.ceil(shootingArea.getHeight() / viewportHeight);
for (int n = 0; n < scrollTimes; n++) {
scrollVertically(js, shootingArea.y + viewportHeight * n);
waitForScrolling();
BufferedImage part = getChunk(wd, n, scrollTimes);
graphics.drawImage(part, 0, getCurrentScrollY(js) - shootingArea.y, null);
}
graphics.dispose();
return finalImage;
} finally {
scrollVertically(js, initialY);
}
}
@Override
public Set<Coords> prepareCoords(Set<Coords> coordsSet) {
return shootingArea == null ? coordsSet : shiftCoords(coordsSet, shootingArea);
}
protected PageDimensions getPageDimensions(WebDriver driver) {
Map<String, Number> pageDimensions = InnerScript.execute(PAGE_DIMENSIONS_JS, driver);
return new PageDimensions(pageDimensions.get("pageHeight").intValue(),
pageDimensions.get("viewportWidth").intValue(), pageDimensions.get("viewportHeight").intValue());
}
protected int getCurrentScrollY(JavascriptExecutor js) {
return ((Number) js.executeScript("var scrY = window.pageYOffset;"
+ "if(scrY){return scrY;} else {return 0;}")).intValue();
}
protected void scrollVertically(JavascriptExecutor js, int scrollY) {
js.executeScript("scrollTo(0, arguments[0]); return [];", scrollY);
}
/**
* Returns a single chunk (viewport screenshot) used to build a full screenshot
*
* @param wd WebDriver instance
* @param currentChunkIndex Zero-based index of the chunk to get
* @param totalNumberOfChunks Total number of chunks
* @return Current screenshot chunk (viewport screenshot)
*/
protected BufferedImage getChunk(WebDriver wd, int currentChunkIndex, int totalNumberOfChunks) {
return getShootingStrategy().getScreenshot(wd);
}
private Coords getShootingCoords(Set<Coords> coords, PageDimensions pageDimensions) {
if (coords == null || coords.isEmpty()) {
return new Coords(0, 0, pageDimensions.getViewportWidth(), pageDimensions.getPageHeight());
}
return extendShootingArea(Coords.unity(coords), pageDimensions);
}
private Set<Coords> shiftCoords(Set<Coords> coordsSet, Coords shootingArea) {
Set<Coords> shiftedCoords = new HashSet<>();
if (coordsSet != null) {
for (Coords coords : coordsSet) {
coords.y -= shootingArea.y;
shiftedCoords.add(coords);
}
}
return shiftedCoords;
}
private Coords extendShootingArea(Coords shootingCoords, PageDimensions pageDimensions) {
int halfViewport = pageDimensions.getViewportHeight() / 2;
shootingCoords.y = Math.max(shootingCoords.y - halfViewport / 2, 0);
shootingCoords.height = Math.min(shootingCoords.height + halfViewport, pageDimensions.getPageHeight());
return shootingCoords;
}
private void waitForScrolling() {
try {
Thread.sleep(scrollTimeout);
} catch (InterruptedException e) {
throw new IllegalStateException("Exception while waiting for scrolling", e);
}
}
}
================================================
FILE: src/main/java/pazone/ashot/comparison/DiffMarkupPolicy.java
================================================
package pazone.ashot.comparison;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import static java.awt.image.BufferedImage.TYPE_BYTE_INDEXED;
/**
* @author Rovniakov Viacheslav rovner@yandex-team.ru
*/
public abstract class DiffMarkupPolicy {
private static final int BITS_PER_PIXEL = 8;
private static final int COLOR_MAP_SIZE = 2;
private static final int TRANSPARENT_COLOR_INDEX = 0;
protected boolean marked = false;
protected int diffSizeTrigger;
protected BufferedImage diffImage;
protected Color diffColor = Color.RED;
public DiffMarkupPolicy withDiffColor(final Color diffColor) {
this.diffColor = diffColor;
return this;
}
public abstract BufferedImage getMarkedImage();
public abstract BufferedImage getTransparentMarkedImage();
public abstract void addDiffPoint(int x, int y);
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
public abstract boolean hasDiff();
public abstract int getDiffSize();
public void setDiffImage(BufferedImage diffImage) {
this.diffImage = diffImage;
}
public void setDiffSizeTrigger(final int diffSizeTrigger) {
this.diffSizeTrigger = diffSizeTrigger;
}
public BufferedImage getDiffImage() {
return diffImage;
}
private IndexColorModel getColorModel() {
return new IndexColorModel(BITS_PER_PIXEL, COLOR_MAP_SIZE, getColorMap(), 0, false, TRANSPARENT_COLOR_INDEX);
}
private byte[] getColorMap() {
Color negativeColor = new Color(0xFFFFFF - diffColor.getRGB()); //negate diff color
return new byte[]{
(byte) negativeColor.getRed(),
(byte) negativeColor.getGreen(),
(byte) negativeColor.getBlue(),
(byte) diffColor.getRed(),
(byte) diffColor.getGreen(),
(byte) diffColor.getBlue()
};
}
protected BufferedImage getTransparentDiffImage(BufferedImage diffImage) {
return new BufferedImage(diffImage.getWidth(), diffImage.getHeight(), TYPE_BYTE_INDEXED, getColorModel());
}
}
================================================
FILE: src/main/java/pazone/ashot/comparison/ImageDiff.java
================================================
package pazone.ashot.comparison;
import java.awt.image.BufferedImage;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class ImageDiff {
@SuppressWarnings("UnusedDeclaration")
public static final ImageDiff EMPTY_DIFF = new ImageDiff();
private final DiffMarkupPolicy diffMarkupPolicy;
public ImageDiff(DiffMarkupPolicy diffMarkupPolicy) {
this.diffMarkupPolicy = diffMarkupPolicy;
}
private ImageDiff() {
diffMarkupPolicy = new PointsMarkupPolicy();
}
/**
* Sets the maximum number of distinguished pixels when images are still considered the same.
*
* @param diffSizeTrigger the number of different pixels
* @return self for fluent style
*/
public ImageDiff withDiffSizeTrigger(final int diffSizeTrigger) {
this.diffMarkupPolicy.setDiffSizeTrigger(diffSizeTrigger);
return this;
}
/**
* @return Diff image with empty spaces in diff areas.
*/
public BufferedImage getDiffImage() {
return diffMarkupPolicy.getDiffImage();
}
/**
* Sets Diff image.
* @param image Image diff
*/
public void setDiffImage(BufferedImage image) {
diffMarkupPolicy.setDiffImage(image);
}
public void addDiffPoint(int x, int y) {
diffMarkupPolicy.addDiffPoint(x, y);
}
/**
* Marks diff on inner image and returns it.
* Idempotent.
*
* @return marked diff image
*/
public BufferedImage getMarkedImage() {
return diffMarkupPolicy.getMarkedImage();
}
/**
* Marks diff points on transparent canvas and returns it.
* Idempotent.
*
* @return marked diff image
*/
public BufferedImage getTransparentMarkedImage() {
return diffMarkupPolicy.getTransparentMarkedImage();
}
/**
* Returns <tt>true</tt> if there are differences between images.
*
* @return <tt>true</tt> if there are differences between images.
*/
public boolean hasDiff() {
return diffMarkupPolicy.hasDiff();
}
/**
* Returns number of points that differ.
*
* @return int - number of points that differ.
*/
public int getDiffSize() {
return diffMarkupPolicy.getDiffSize();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ImageDiff) {
ImageDiff item = (ImageDiff) obj;
return this.diffMarkupPolicy.equals(item.diffMarkupPolicy);
}
return false;
}
@Override
public int hashCode() {
return this.diffMarkupPolicy.hashCode();
}
}
================================================
FILE: src/main/java/pazone/ashot/comparison/ImageDiffer.java
================================================
package pazone.ashot.comparison;
import pazone.ashot.Screenshot;
import pazone.ashot.coordinates.Coords;
import pazone.ashot.util.ImageBytesDiffer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.LinkedHashSet;
import java.util.Set;
import static pazone.ashot.util.ImageTool.rgbCompare;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class ImageDiffer {
private static final int DEFAULT_COLOR_DISTORTION = 15;
private int colorDistortion = DEFAULT_COLOR_DISTORTION;
private DiffMarkupPolicy diffMarkupPolicy = new PointsMarkupPolicy();
private Color ignoredColor = null;
public ImageDiffer withIgnoredColor(final Color ignoreColor) {
this.ignoredColor = ignoreColor;
return this;
}
public ImageDiffer withColorDistortion(int distortion) {
this.colorDistortion = distortion;
return this;
}
/**
* Sets the diff markup policy.
*
* @param diffMarkupPolicy diff markup policy instance
* @return self for fluent style
* @see ImageMarkupPolicy
* @see PointsMarkupPolicy
*/
public ImageDiffer withDiffMarkupPolicy(final DiffMarkupPolicy diffMarkupPolicy) {
this.diffMarkupPolicy = diffMarkupPolicy;
return this;
}
public ImageDiff makeDiff(Screenshot expected, Screenshot actual) {
ImageDiff diff = new ImageDiff(diffMarkupPolicy);
if (ImageBytesDiffer.areImagesEqual(expected, actual)) {
diff.setDiffImage(actual.getImage());
} else {
markDiffPoints(expected, actual, diff);
}
return diff;
}
protected void markDiffPoints(Screenshot expected, Screenshot actual, ImageDiff diff) {
Coords expectedImageCoords = Coords.ofImage(expected.getImage());
Coords actualImageCoords = Coords.ofImage(actual.getImage());
CoordsSet compareCoordsSet = new CoordsSet(
CoordsSet.union(actual.getCoordsToCompare(), expected.getCoordsToCompare()));
CoordsSet ignoreCoordsSet = new CoordsSet(
CoordsSet.intersection(actual.getIgnoredAreas(), expected.getIgnoredAreas()));
int width = Math.max(expected.getImage().getWidth(), actual.getImage().getWidth());
int height = Math.max(expected.getImage().getHeight(), actual.getImage().getHeight());
diff.setDiffImage(createDiffImage(expected.getImage(), actual.getImage(), width, height));
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
if (ignoreCoordsSet.contains(i, j)) {
continue;
}
if (!isInsideBothImages(i, j, expectedImageCoords, actualImageCoords)
|| compareCoordsSet.contains(i, j) && hasDiffInChannel(expected, actual, i, j)) {
diff.addDiffPoint(i, j);
}
}
}
}
private boolean hasDiffInChannel(Screenshot expected, Screenshot actual, int i, int j) {
if (ignoredColor != null && rgbCompare(expected.getImage().getRGB(i, j), ignoredColor.getRGB(), 0)) {
return false;
}
return !rgbCompare(expected.getImage().getRGB(i, j), actual.getImage().getRGB(i, j), colorDistortion);
}
public ImageDiff makeDiff(BufferedImage expected, BufferedImage actual) {
return makeDiff(new Screenshot(expected), new Screenshot(actual));
}
private BufferedImage createDiffImage(BufferedImage expectedImage, BufferedImage actualImage, int width,
int height) {
BufferedImage diffImage = new BufferedImage(width, height, actualImage.getType());
paintImage(actualImage, diffImage);
paintImage(expectedImage, diffImage);
return diffImage;
}
private void paintImage(BufferedImage image, BufferedImage diffImage) {
Graphics graphics = diffImage.getGraphics();
graphics.drawImage(image, 0, 0, null);
graphics.dispose();
}
private boolean isInsideBothImages(int i, int j, Coords expected, Coords actual) {
return expected.contains(i, j) && actual.contains(i, j);
}
private static class CoordsSet {
private final boolean isSingle;
private final Coords minRectangle;
private final Set<Coords> coordsSet;
CoordsSet(Set<Coords> coordsSet) {
isSingle = coordsSet.size() == 1;
this.coordsSet = coordsSet;
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = 0;
int maxY = 0;
for (Coords coords : coordsSet) {
minX = Math.min(minX, (int) coords.getMinX());
minY = Math.min(minY, (int) coords.getMinY());
maxX = Math.max(maxX, (int) coords.getMaxX());
maxY = Math.max(maxY, (int) coords.getMaxY());
}
minRectangle = new Coords(minX, minY, maxX - minX, maxY - minY);
}
private boolean contains(int i, int j) {
return inaccurateContains(i, j) && accurateContains(i, j);
}
private boolean inaccurateContains(int i, int j) {
return minRectangle.contains(i, j);
}
private boolean accurateContains(int i, int j) {
return isSingle || coordsSet.stream().anyMatch(coords -> coords.contains(i, j));
}
private static Set<Coords> intersection(Set<Coords> coordsPool1, Set<Coords> coordsPool2) {
return Coords.intersection(coordsPool1, coordsPool2);
}
private static Set<Coords> union(Set<Coords> coordsPool1, Set<Coords> coordsPool2) {
Set<Coords> coordsPool = new LinkedHashSet<>();
coordsPool.addAll(coordsPool1);
coordsPool.addAll(coordsPool2);
return coordsPool;
}
}
}
================================================
FILE: src/main/java/pazone/ashot/comparison/ImageMarkupPolicy.java
================================================
package pazone.ashot.comparison;
import java.awt.image.BufferedImage;
/**
* @author Rovniakov Viacheslav rovner@yandex-team.ru
*
*/
public class ImageMarkupPolicy extends DiffMarkupPolicy {
private int diffPointCount;
private int xReference = Integer.MAX_VALUE;
private int yReference = Integer.MAX_VALUE;
private int xSum;
private int ySum;
private BufferedImage transparentDiffImage;
@Override
public void setDiffImage(BufferedImage diffImage) {
super.setDiffImage(diffImage);
transparentDiffImage = getTransparentDiffImage(diffImage);
}
@Override
public BufferedImage getMarkedImage() {
if (!marked) {
final int rgb = diffColor.getRGB();
for (int x = 0; x < diffImage.getWidth(); x++) {
for (int y = 0; y < diffImage.getHeight(); y++) {
if (transparentDiffImage.getRGB(x, y) == rgb) {
diffImage.setRGB(x, y, rgb);
}
}
}
marked = true;
}
return diffImage;
}
@Override
public BufferedImage getTransparentMarkedImage() {
return transparentDiffImage;
}
@Override
public void addDiffPoint(int x, int y) {
diffPointCount++;
xReference = Math.min(xReference, x);
yReference = Math.min(yReference, y);
xSum += x;
ySum += y;
transparentDiffImage.setRGB(x, y, diffColor.getRGB());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ImageMarkupPolicy) {
ImageMarkupPolicy item = (ImageMarkupPolicy) obj;
return this.diffPointCount == item.diffPointCount
&& this.xSum - this.diffPointCount * this.xReference
== item.xSum - item.diffPointCount * item.xReference
&& this.ySum - this.diffPointCount * this.yReference
== item.ySum - item.diffPointCount * item.yReference;
}
return false;
}
@Override
public int hashCode() {
int result = diffPointCount;
result = 31 * result + xSum - diffPointCount * xReference;
result = 31 * result + ySum - diffPointCount * yReference;
return result;
}
@Override
public boolean hasDiff() {
return diffPointCount > diffSizeTrigger;
}
@Override
public int getDiffSize() {
return diffPointCount;
}
}
================================================
FILE: src/main/java/pazone/ashot/comparison/PointsMarkupPolicy.java
================================================
package pazone.ashot.comparison;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @author Rovniakov Viacheslav rovner@yandex-team.ru
*
*/
public class PointsMarkupPolicy extends DiffMarkupPolicy {
private final Set<Point> diffPoints = new LinkedHashSet<>();
private Set<Point> deposedPoints = new LinkedHashSet<>();
private BufferedImage transparentMarkedImage = null;
@Override
public BufferedImage getMarkedImage() {
if (!marked) {
markDiffPoints(diffImage);
marked = true;
}
return diffImage;
}
@Override
public BufferedImage getTransparentMarkedImage() {
if (transparentMarkedImage == null) {
transparentMarkedImage = getTransparentDiffImage(diffImage);
markDiffPoints(transparentMarkedImage);
}
return transparentMarkedImage;
}
@Override
public void addDiffPoint(int x, int y) {
diffPoints.add(new Point(x, y));
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PointsMarkupPolicy) {
PointsMarkupPolicy item = (PointsMarkupPolicy) obj;
return diffPoints.size() == item.diffPoints.size() && item.getDeposedPoints().containsAll(
getDeposedPoints());
}
return false;
}
@Override
public int hashCode() {
return getDeposedPoints().hashCode();
}
@Override
public boolean hasDiff() {
return diffPoints.size() > diffSizeTrigger;
}
@Override
public int getDiffSize() {
return diffPoints.size();
}
protected void markDiffPoints(BufferedImage image) {
int rgb = diffColor.getRGB();
for (Point dot : diffPoints) {
image.setRGB(dot.x, dot.y, rgb);
}
}
private Set<Point> getDeposedPoints() {
if (deposedPoints.isEmpty()) {
deposedPoints = deposeReference();
}
return deposedPoints;
}
private Point getReferenceCorner() {
Iterator<Point> iterator = diffPoints.iterator();
Point diffPoint = iterator.next();
double x = diffPoint.getX();
double y = diffPoint.getY();
while (iterator.hasNext()) {
diffPoint = iterator.next();
x = Math.min(x, diffPoint.getX());
y = Math.min(y, diffPoint.getY());
}
return new Point((int) x, (int) y);
}
private Set<Point> deposeReference() {
Point reference = getReferenceCorner();
Set<Point> referenced = new HashSet<>();
for (Point point : diffPoints) {
referenced.add(new Point(point.x - reference.x, point.y - reference.y));
}
return referenced;
}
}
================================================
FILE: src/main/java/pazone/ashot/coordinates/Coords.java
================================================
package pazone.ashot.coordinates;
import com.google.gson.Gson;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class Coords extends Rectangle {
public static Set<Coords> intersection(Collection<Coords> coordsPool1, Collection<Coords> coordsPool2) {
Set<Coords> intersectedCoords = new HashSet<>();
for (Coords coords1 : coordsPool1) {
for (Coords coords2 : coordsPool2) {
Coords intersection = coords1.intersection(coords2);
if (!intersection.isEmpty()) {
intersectedCoords.add(intersection);
}
}
}
return intersectedCoords;
}
public static Set<Coords> setReferenceCoords(Coords reference, Set<Coords> coordsSet) {
Set<Coords> referencedCoords = new HashSet<>();
for (Coords coords : coordsSet) {
referencedCoords.add(new Coords(
coords.x - reference.x,
coords.y - reference.y,
coords.width,
coords.height)
);
}
return referencedCoords;
}
public static Coords unity(Collection<Coords> coordsCollection) {
Coords unity = coordsCollection.iterator().next();
for (Coords coords : coordsCollection) {
unity = unity.union(coords);
}
return unity;
}
public static Coords ofImage(BufferedImage image) {
return new Coords(image.getWidth(), image.getHeight());
}
public Coords(Rectangle rectangle) {
super(rectangle);
}
public Coords(int x, int y, int width, int height) {
super(x, y, width, height);
}
public Coords(int width, int height) {
super(width, height);
}
public void reduceBy(int pixels) {
if (pixels < getWidth() / 2 && pixels < getHeight() / 2) {
this.x += pixels;
this.y += pixels;
this.width -= pixels;
this.height -= pixels;
}
}
@SuppressWarnings("NullableProblems")
@Override
public Coords union(Rectangle r) {
return new Coords(super.union(r));
}
@SuppressWarnings("NullableProblems")
@Override
public Coords intersection(Rectangle r) {
return new Coords(super.intersection(r));
}
@Override
public String toString() {
return new Gson().toJson(this);
}
}
================================================
FILE: src/main/java/pazone/ashot/coordinates/CoordsPreparationStrategy.java
================================================
package pazone.ashot.coordinates;
import pazone.ashot.Screenshot;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import static pazone.ashot.coordinates.Coords.intersection;
import static pazone.ashot.coordinates.Coords.setReferenceCoords;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public abstract class CoordsPreparationStrategy {
public static CoordsPreparationStrategy simple() {
return new CoordsPreparationStrategy() {
@Override
public Set<Coords> prepare(Collection<Coords> coordinates) {
return new HashSet<>(coordinates);
}
};
}
public static CoordsPreparationStrategy intersectingWith(final Screenshot screenshot) {
return new CoordsPreparationStrategy() {
@Override
public Set<Coords> prepare(Collection<Coords> coordinates) {
return intersection(screenshot.getCoordsToCompare(),
setReferenceCoords(screenshot.getOriginShift(), new HashSet<>(coordinates)));
}
};
}
public abstract Set<Coords> prepare(Collection<Coords> coordinates);
}
================================================
FILE: src/main/java/pazone/ashot/coordinates/CoordsProvider.java
================================================
package pazone.ashot.coordinates;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public abstract class CoordsProvider implements Serializable {
public abstract Coords ofElement(WebDriver driver, WebElement element);
public Set<Coords> ofElements(WebDriver driver, Iterable<WebElement> elements) {
Set<Coords> elementsCoords = new HashSet<>();
for (WebElement element : elements) {
Coords elementCoords = ofElement(driver, element);
if (!elementCoords.isEmpty()) {
elementsCoords.add(elementCoords);
}
}
return Collections.unmodifiableSet(elementsCoords);
}
@SuppressWarnings("UnusedDeclaration")
public Set<Coords> ofElements(WebDriver driver, WebElement... elements) {
return ofElements(driver, Arrays.asList(elements));
}
@SuppressWarnings("UnusedDeclaration")
public Set<Coords> locatedBy(WebDriver driver, By locator) {
return ofElements(driver, driver.findElements(locator));
}
}
================================================
FILE: src/main/java/pazone/ashot/coordinates/JqueryCoordsProvider.java
================================================
package pazone.ashot.coordinates;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import pazone.ashot.util.JsCoords;
/**
* @author pazone
*/
public class JqueryCoordsProvider extends CoordsProvider {
@Override
public Coords ofElement(WebDriver driver, WebElement element) {
return JsCoords.findCoordsWithJquery(driver, element);
}
}
================================================
FILE: src/main/java/pazone/ashot/coordinates/WebDriverCoordsProvider.java
================================================
package pazone.ashot.coordinates;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class WebDriverCoordsProvider extends CoordsProvider {
@Override
public Coords ofElement(WebDriver driver, WebElement element) {
// Keep Point/Dimension as opposed to Rectangle because browsers like PhantomJS do not like the latter
Point point = element.getLocation();
Dimension dimension = element.getSize();
return new Coords(
point.getX(),
point.getY(),
dimension.getWidth(),
dimension.getHeight());
}
}
================================================
FILE: src/main/java/pazone/ashot/cropper/DefaultCropper.java
================================================
package pazone.ashot.cropper;
import pazone.ashot.Screenshot;
import pazone.ashot.coordinates.Coords;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Set;
import static pazone.ashot.coordinates.Coords.setReferenceCoords;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class DefaultCropper extends ImageCropper {
@Override
public Screenshot cropScreenshot(BufferedImage image, Set<Coords> coordsToCompare) {
Coords cropArea = Coords.unity(coordsToCompare);
Coords imageIntersection = Coords.ofImage(image).intersection(cropArea);
if (imageIntersection.isEmpty()) {
return new Screenshot(image);
}
BufferedImage cropped = new BufferedImage(imageIntersection.width, imageIntersection.height, image.getType());
Graphics g = cropped.getGraphics();
g.drawImage(
image,
0, 0,
imageIntersection.width, imageIntersection.height,
cropArea.x, cropArea.y,
cropArea.x + imageIntersection.width, cropArea.y + imageIntersection.height,
null
);
g.dispose();
Screenshot screenshot = new Screenshot(cropped);
screenshot.setOriginShift(cropArea);
screenshot.setCoordsToCompare(setReferenceCoords(screenshot.getOriginShift(), coordsToCompare));
return screenshot;
}
protected Coords createCropArea(Set<Coords> coordsToCompare) {
return Coords.unity(coordsToCompare);
}
}
================================================
FILE: src/main/java/pazone/ashot/cropper/ImageCropper.java
================================================
package pazone.ashot.cropper;
import pazone.ashot.Screenshot;
import pazone.ashot.coordinates.Coords;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.util.Set;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public abstract class ImageCropper implements Serializable {
public Screenshot crop(BufferedImage image, Set<Coords> cropArea) {
return cropArea.isEmpty()
? new Screenshot(image)
: cropScreenshot(image, cropArea);
}
protected abstract Screenshot cropScreenshot(BufferedImage image, Set<Coords> coordsToCompare);
}
================================================
FILE: src/main/java/pazone/ashot/cropper/indent/BlurFilter.java
================================================
package pazone.ashot.cropper.indent;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class BlurFilter implements IndentFilter {
@Override
public BufferedImage apply(BufferedImage image) {
Kernel kernel = new Kernel(3, 3,
new float[] {
1f / 9f, 1f / 9f, 1f / 9f,
1f / 9f, 1f / 9f, 1f / 9f,
1f / 9f, 1f / 9f, 1f / 9f
}
);
BufferedImageOp blurOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
return blurOp.filter(image, null);
}
}
================================================
FILE: src/main/java/pazone/ashot/cropper/indent/IndentCropper.java
================================================
package pazone.ashot.cropper.indent;
import pazone.ashot.Screenshot;
import pazone.ashot.cropper.DefaultCropper;
import pazone.ashot.coordinates.Coords;
import pazone.ashot.util.ImageTool;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static pazone.ashot.coordinates.Coords.setReferenceCoords;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class IndentCropper extends DefaultCropper {
public static final int DEFAULT_INDENT = 50;
private int indent;
protected final List<IndentFilter> filters = new LinkedList<>();
public IndentCropper(final int indent) {
this.indent = indent;
}
public IndentCropper() {
this(DEFAULT_INDENT);
}
@Override
public Screenshot cropScreenshot(BufferedImage image, Set<Coords> coordsToCompare) {
Coords cropArea = createCropArea(coordsToCompare);
Coords indentMask = createIndentMask(cropArea, image);
Coords coordsWithIndent = applyIndentMask(cropArea, indentMask);
Screenshot croppedShot = super.cropScreenshot(image, Collections.singleton(coordsWithIndent));
croppedShot.setOriginShift(coordsWithIndent);
croppedShot.setCoordsToCompare(setReferenceCoords(coordsWithIndent, coordsToCompare));
List<NoFilteringArea> noFilteringAreas = createNotFilteringAreas(croppedShot);
croppedShot.setImage(applyFilters(croppedShot.getImage()));
pasteAreasToCompare(croppedShot.getImage(), noFilteringAreas);
return croppedShot;
}
protected Coords applyIndentMask(Coords origin, Coords mask) {
Coords spreadCoords = new Coords(0, 0);
spreadCoords.x = origin.x - mask.x;
spreadCoords.y = origin.y - mask.y;
spreadCoords.height = mask.y + origin.height + mask.height;
spreadCoords.width = mask.x + origin.width + mask.width;
return spreadCoords;
}
protected Coords createIndentMask(Coords originCoords, BufferedImage image) {
Coords indentMask = new Coords(originCoords);
indentMask.x = Math.min(indent, originCoords.x);
indentMask.y = Math.min(indent, originCoords.y);
indentMask.width = Math.min(indent, image.getWidth() - originCoords.x - originCoords.width);
indentMask.height = Math.min(indent, image.getHeight() - originCoords.y - originCoords.height);
return indentMask;
}
protected List<NoFilteringArea> createNotFilteringAreas(Screenshot screenshot) {
List<NoFilteringArea> noFilteringAreas = new ArrayList<>();
for (Coords noFilteringCoords : screenshot.getCoordsToCompare()) {
if (noFilteringCoords.intersects(Coords.ofImage(screenshot.getImage()))) {
noFilteringAreas.add(new NoFilteringArea(screenshot.getImage(), noFilteringCoords));
}
}
return noFilteringAreas;
}
protected void pasteAreasToCompare(BufferedImage filtered, List<NoFilteringArea> noFilteringAreas) {
Graphics graphics = filtered.getGraphics();
for (NoFilteringArea noFilteringArea : noFilteringAreas) {
graphics.drawImage(
noFilteringArea.getSubimage(),
noFilteringArea.getCoords().x,
noFilteringArea.getCoords().y,
null);
}
graphics.dispose();
}
public IndentCropper addIndentFilter(IndentFilter filter) {
this.filters.add(filter);
return this;
}
protected BufferedImage applyFilters(BufferedImage image) {
for (IndentFilter filter : filters) {
image = filter.apply(image);
}
return image;
}
private static final class NoFilteringArea {
private final BufferedImage subimage;
private final Coords coords;
private NoFilteringArea(BufferedImage origin, Coords noFilterCoords) {
this.subimage = ImageTool.subImage(origin, noFilterCoords);
this.coords = noFilterCoords;
}
public BufferedImage getSubimage() {
return subimage;
}
public Coords getCoords() {
return coords;
}
}
}
================================================
FILE: src/main/java/pazone/ashot/cropper/indent/IndentFilerFactory.java
================================================
package pazone.ashot.cropper.indent;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public final class IndentFilerFactory {
private IndentFilerFactory() {
throw new UnsupportedOperationException();
}
public static BlurFilter blur() {
return new BlurFilter();
}
public static MonochromeFilter monochrome() {
return new MonochromeFilter();
}
}
================================================
FILE: src/main/java/pazone/ashot/cropper/indent/IndentFilter.java
================================================
package pazone.ashot.cropper.indent;
import java.awt.image.BufferedImage;
import java.io.Serializable;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public interface IndentFilter extends Serializable {
BufferedImage apply(BufferedImage image);
}
================================================
FILE: src/main/java/pazone/ashot/cropper/indent/MonochromeFilter.java
================================================
package pazone.ashot.cropper.indent;
import pazone.ashot.util.ImageTool;
import java.awt.image.BufferedImage;
import javax.swing.GrayFilter;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public class MonochromeFilter implements IndentFilter {
@Override
public BufferedImage apply(BufferedImage image) {
return darken(image);
}
private BufferedImage darken(BufferedImage image) {
return ImageTool.toBufferedImage(GrayFilter.createDisabledImage(image));
}
}
================================================
FILE: src/main/java/pazone/ashot/cutter/CutStrategy.java
================================================
package pazone.ashot.cutter;
import org.openqa.selenium.WebDriver;
import java.io.Serializable;
/**
* @author <a href="frolic@yandex-team.ru">Vyacheslav Frolov</a>
*/
public interface CutStrategy extends Serializable {
/**
* Obtains height of header that will be cut off from initial screenshot.
* @param driver - webDriver
* @return height of header in pixels
*/
int getHeaderHeight(WebDriver driver);
/**
* Obtains height of footer that will be cut off from initial screenshot.
* @param driver - webDriver
* @return height of header in pixels
*/
int getFooterHeight(WebDriver driver);
/**
* Obtains width of left bar that will be cut off from initial screenshot.
*
* @param driver - webDriver
* @return width of the left bar in pixels
*/
default int getLeftBarWidth(WebDriver driver) {
return 0;
}
/**
* Obtains width of right bar that will be cut off from initial screenshot.
*
* @param driver - webDriver
* @return width of the right bar in pixels
*/
default int getRightBarWidth(WebDriver driver) {
return 0;
}
}
================================================
FILE: src/main/java/pazone/ashot/cutter/FixedCutStrategy.java
================================================
package pazone.ashot.cutter;
import org.openqa.selenium.WebDriver;
/**
* Strategy for cutting header and footer of a constant height.
*
* @author <a href="frolic@yandex-team.ru">Vyacheslav Frolov</a>
*/
public class FixedCutStrategy implements CutStrategy {
private final int headerToCut;
private final int footerToCut;
private final int leftBarToCut;
private final int rightBarToCut;
public FixedCutStrategy(int headerToCut, int footerToCut) {
this(headerToCut, footerToCut, 0, 0);
}
public FixedCutStrategy(int headerToCut, int footerToCut, int leftBarToCut, int rightBarToCut) {
this.headerToCut = headerToCut;
this.footerToCut = footerToCut;
this.leftBarToCut = leftBarToCut;
this.rightBarToCut = rightBarToCut;
}
@Override
public int getHeaderHeight(WebDriver driver) {
return headerToCut;
}
@Override
public int getFooterHeight(WebDriver driver) {
return footerToCut;
}
@Override
public int getLeftBarWidth(WebDriver driver) {
return leftBarToCut;
}
@Override
public int getRightBarWidth(WebDriver driver) {
return rightBarToCut;
}
}
================================================
FILE: src/main/java/pazone/ashot/cutter/VariableCutStrategy.java
================================================
package pazone.ashot.cutter;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import pazone.ashot.InvalidViewportHeightException;
/**
* Strategy for cutting header and footer with variable height.<br>
* For example, Safari browser in iOS 8 introduced a feature (so called 'minimal-ui')<br>
* when browser's header might be 65px or 41px (with address bar hidden).<br>
* This strategy will get current height of browser's header and footer.
* @author <a href="frolic@yandex-team.ru">Vyacheslav Frolov</a>
*/
public class VariableCutStrategy implements CutStrategy {
public static final String SCRIPT = "var h = window.innerHeight || document.documentElement.clientHeight; "
+ "return h;";
private final int headerMin;
private final int headerMax;
private final int windowInnerHeightMin;
private final int footerMax;
private final int footerMin;
/**
* @param headerMin - minimal header height (for Safari iOS 8 it is 41px)
* @param headerMax - maximum header height (for Safari iOS 8 it is 65px)
* @param footerMin - minimal footer height (for Safari iOS 8 it is 0px)
* @param footerMax - maximum footer height (for Safari iOS 8 it is 89px)
* @param windowInnerHeightMin - minimal height of viewportPasting (when header with address bar shown).<br />
* For real device iPad 2 it is 960px (portrait) and 674px (landscape).<br />
* For simulated iPad 2 it is 1225px (portrait) and 687px (landscape).
*/
public VariableCutStrategy(int headerMin, int headerMax, int footerMin, int footerMax, int windowInnerHeightMin) {
this.headerMin = headerMin;
this.headerMax = headerMax;
this.footerMin = footerMin;
this.footerMax = footerMax;
this.windowInnerHeightMin = windowInnerHeightMin;
}
public VariableCutStrategy(int headerMin, int headerMax, int footerMax, int windowInnerHeightMin) {
this(headerMin, headerMax, 0, footerMax, windowInnerHeightMin);
}
public VariableCutStrategy(int headerMin, int headerMax, int windowInnerHeightMin) {
this(headerMin, headerMax, 0, windowInnerHeightMin);
}
@Override
public int getHeaderHeight(WebDriver driver) {
return getCutHeight((JavascriptExecutor) driver, headerMin, headerMax);
}
@Override
public int getFooterHeight(WebDriver driver) {
if (0 == footerMax && 0 == footerMin) {
return 0;
}
return getCutHeight((JavascriptExecutor) driver, footerMin, footerMax);
}
private int getCutHeight(JavascriptExecutor driver, int heightMin, int heightMax) {
final int innerHeight = getWindowInnerHeight(driver);
return innerHeight > windowInnerHeightMin ? heightMin : heightMax;
}
private int getWindowInnerHeight(JavascriptExecutor driver) {
final Number innerHeight;
try {
innerHeight = (Number) driver.executeScript(SCRIPT);
} catch (ClassCastException e) {
throw new InvalidViewportHeightException("Could not acquire window.innerHeight property!", e);
}
if (innerHeight == null) {
throw new InvalidViewportHeightException(
"Could not acquire window.innerHeight property! Returned value is null.");
}
return innerHeight.intValue();
}
}
================================================
FILE: src/main/java/pazone/ashot/util/ImageBytesDiffer.java
================================================
package pazone.ashot.util;
import pazone.ashot.Screenshot;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
/**
* @author <a href="frolic@yandex-team.ru">Viacheslav Frolov</a>
*/
public final class ImageBytesDiffer {
private ImageBytesDiffer() {
throw new UnsupportedOperationException();
}
public static boolean areImagesEqual(Screenshot expected, Screenshot actual) {
return areImagesEqual(expected.getImage(), actual.getImage());
}
public static boolean areImagesEqual(BufferedImage expected, BufferedImage actual) {
return expected.getHeight() == actual.getHeight()
&& expected.getWidth() == actual.getWidth()
&& actual.getColorModel().equals(expected.getColorModel())
&& areImagesBuffersEqual(expected.getRaster().getDataBuffer(), actual.getRaster().getDataBuffer());
}
private static boolean areImagesBuffersEqual(DataBuffer expected, DataBuffer actual) {
return actual.getDataType() == expected.getDataType()
&& actual.getNumBanks() == expected.getNumBanks()
&& actual.getSize() == expected.getSize()
&& areImagesBytesEqual(actual, expected);
}
private static boolean areImagesBytesEqual(DataBuffer expected, DataBuffer actual) {
for (int bank = 0; bank < expected.getNumBanks(); bank++) {
for (int i = 0; i < expected.getSize(); i++) {
if (expected.getElem(bank, i) != actual.getElem(bank, i)) {
return false;
}
}
}
return true;
}
}
================================================
FILE: src/main/java/pazone/ashot/util/ImageTool.java
================================================
package pazone.ashot.util;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import pazone.ashot.Screenshot;
import pazone.ashot.coordinates.Coords;
import javax.imageio.ImageIO;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public final class ImageTool {
private ImageTool() {
throw new UnsupportedOperationException();
}
public static BufferedImage subImage(BufferedImage origin, Coords crop) {
Coords intersection = Coords.ofImage(origin).intersection(crop);
return origin.getSubimage(intersection.x, intersection.y, intersection.width, intersection.height);
}
public static Coords spreadCoordsInsideImage(Coords coordinates, int indent, BufferedImage image) {
return new Coords(Math.max(0, coordinates.x - indent),
Math.max(0, coordinates.y - indent),
Math.min(image.getWidth(), coordinates.width + indent),
Math.min(image.getHeight(), coordinates.height + indent));
}
public static boolean rgbCompare(int rgb1, int rgb2, int inaccuracy) {
if (inaccuracy == 0) {
return rgb1 == rgb2;
}
int red1 = (rgb1 & 0x00FF0000) >> 16;
int green1 = (rgb1 & 0x0000FF00) >> 8;
int blue1 = (rgb1 & 0x000000FF);
int red2 = (rgb2 & 0x00FF0000) >> 16;
int green2 = (rgb2 & 0x0000FF00) >> 8;
int blue2 = (rgb2 & 0x000000FF);
return Math.abs(red1 - red2) <= inaccuracy
&& Math.abs(green1 - green2) <= inaccuracy
&& Math.abs(blue1 - blue2) <= inaccuracy;
}
public static Matcher<BufferedImage> equalImage(final BufferedImage second) {
return new TypeSafeMatcher<BufferedImage>() {
@Override
protected boolean matchesSafely(BufferedImage first) {
if (!Coords.ofImage(first).equals(Coords.ofImage(second))) {
return false;
}
for (int x = 0; x < first.getWidth(); x++) {
for (int y = 0; y < first.getHeight(); y++) {
if (!rgbCompare(first.getRGB(x, y), second.getRGB(x, y), 10)) {
return false;
}
}
}
return true;
}
@Override
public void describeTo(Description description) {
}
};
}
@SuppressWarnings("UnusedDeclaration")
public static byte[] toByteArray(Screenshot screenshot) throws IOException {
return toByteArray(screenshot.getImage());
}
public static byte[] toByteArray(BufferedImage image) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(image, "png", baos);
return baos.toByteArray();
}
}
public static BufferedImage toBufferedImage(Image img) {
if (img instanceof BufferedImage) {
return (BufferedImage) img;
}
BufferedImage bufferedImage = new BufferedImage(
img.getWidth(null),
img.getHeight(null),
BufferedImage.TYPE_INT_ARGB
);
Graphics2D graphics = bufferedImage.createGraphics();
graphics.drawImage(img, 0, 0, null);
graphics.dispose();
return bufferedImage;
}
public static BufferedImage toBufferedImage(byte[] imageBytes) throws IOException {
try (ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes)) {
return ImageIO.read(bais);
}
}
}
================================================
FILE: src/main/java/pazone/ashot/util/InnerScript.java
================================================
package pazone.ashot.util;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import static java.lang.Thread.currentThread;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public final class InnerScript {
private InnerScript() {
throw new UnsupportedOperationException();
}
public static <T> T execute(String path, WebDriver driver, Object... args) {
try {
String script = IOUtils.toString(currentThread().getContextClassLoader().getResourceAsStream(path),
StandardCharsets.UTF_8);
//noinspection unchecked
return (T) ((JavascriptExecutor) driver).executeScript(script, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
================================================
FILE: src/main/java/pazone/ashot/util/JsCoords.java
================================================
package pazone.ashot.util;
import java.util.List;
import com.google.gson.Gson;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import pazone.ashot.coordinates.Coords;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
public final class JsCoords {
public static final String COORDS_JS_PATH = "js/coords-single.js";
private JsCoords() {
throw new UnsupportedOperationException();
}
public static Coords findCoordsWithJquery(WebDriver driver, WebElement element) {
List<?> result = InnerScript.execute(COORDS_JS_PATH, driver, element);
if (result.isEmpty()) {
throw new RuntimeException("Unable to find coordinates with jQuery.");
}
return new Gson().fromJson((String) result.get(0), Coords.class);
}
}
================================================
FILE: src/main/resources/js/coords-single.js
================================================
function Coords(el) {
this.left = parseInt(el.offset().left);
this.top = parseInt(el.offset().top);
this.right = parseInt(this.left + el.outerWidth());
this.bottom = parseInt(this.top + el.outerHeight());
}
Coords.prototype.toString = function () {
var x = Math.max(this.left, 0);
var y = Math.max(this.top, 0);
return JSON.stringify({
x:x,
y:y,
width:this.right - x,
height:this.bottom - y
});
};
return [(new Coords($(arguments[0]))).toString()];
================================================
FILE: src/main/resources/js/page_dimensions.js
================================================
var body = document.body;
var documentElement = document.documentElement;
var pageHeight = Math.max(body.scrollHeight, body.offsetHeight, documentElement.clientHeight,
documentElement.scrollHeight, documentElement.offsetHeight);
var viewportHeight = window.innerHeight || documentElement.clientHeight|| body.clientHeight;
var viewportWidth = window.innerWidth || documentElement.clientWidth || body.clientWidth;
return {"pageHeight": pageHeight, "viewportHeight": viewportHeight, "viewportWidth": viewportWidth}
================================================
FILE: src/test/java/pazone/ashot/CdpShootingStrategyTest.java
================================================
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());
}
}
================================================
FILE: src/test/java/pazone/ashot/CroppersTest.java
================================================
package pazone.ashot;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import pazone.ashot.coordinates.Coords;
import pazone.ashot.cropper.DefaultCropper;
import pazone.ashot.cropper.ImageCropper;
import pazone.ashot.cropper.indent.IndentCropper;
import pazone.ashot.cropper.indent.IndentFilerFactory;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Stream;
import static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;
import static pazone.ashot.util.TestImageUtils.assertImageEquals;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
class CroppersTest {
private static final Set<Coords> OUTSIDE_IMAGE = Collections.singleton(new Coords(20, 20, 200, 90));
private static final Set<Coords> INSIDE_IMAGE = Collections.singleton(new Coords(20, 20, 30, 30));
static Stream<Arguments> outsideCropperData() {
return Stream.of(
Arguments.of(new DefaultCropper(), "img/expected/outside_dc.png"),
Arguments.of(new IndentCropper(10), "img/expected/outside_ic.png")
);
}
@ParameterizedTest
@MethodSource("outsideCropperData")
void testElementOutsideImageCropper(ImageCropper cropper, String expectedImagePath) {
Screenshot screenshot = cropper.crop(IMAGE_A_SMALL, OUTSIDE_IMAGE);
assertImageEquals(screenshot.getImage(), expectedImagePath);
}
@Test
void testElementInsideImageIndentCropperWithFilter() {
Screenshot screenshot = new IndentCropper()
.addIndentFilter(IndentFilerFactory.blur())
.addIndentFilter(IndentFilerFactory.monochrome())
.cropScreenshot(IMAGE_A_SMALL, INSIDE_IMAGE);
assertImageEquals(screenshot.getImage(), "img/expected/inside_icf.png");
}
}
================================================
FILE: src/test/java/pazone/ashot/CuttingDecoratorTest.java
================================================
package pazone.ashot;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static pazone.ashot.util.TestImageUtils.assertImageEquals;
import java.util.function.UnaryOperator;
import org.junit.jupiter.api.Test;
import pazone.ashot.cutter.FixedCutStrategy;
import pazone.ashot.util.TestImageUtils;
class CuttingDecoratorTest {
public static final String CUT_FROM_ALL_THE_SIDES_PNG = "cut-from-all-the-sides.png";
@Test
void shouldCutFromAllTheSides() {
testCuttingFromAllTheSides(cd -> cd.withCut(35, 35, 40, 40), CUT_FROM_ALL_THE_SIDES_PNG);
}
@Test
void shouldCutFromAllTheSidesUsingFixedCutStrategy() {
testCuttingFromAllTheSides(cd -> cd.withCutStrategy(new FixedCutStrategy(35, 35, 40, 40)),
CUT_FROM_ALL_THE_SIDES_PNG);
}
@Test
void shouldCutOnlyFooterAndHeader() {
testCuttingFromAllTheSides(cd -> cd.withCut(35, 35), "cut-footer-header.png");
}
private void testCuttingFromAllTheSides(UnaryOperator<CuttingDecorator> settings, String expectedImageName) {
ShootingStrategy shootingStrategy = mock(ShootingStrategy.class);
CuttingDecorator cuttingDecorator = new CuttingDecorator(shootingStrategy);
when(shootingStrategy.getScreenshot(null)).thenReturn(TestImageUtils.IMAGE_B_SMALL);
assertImageEquals(settings.apply(cuttingDecorator).getScreenshot(null), "img/expected/" + expectedImageName);
}
}
================================================
FILE: src/test/java/pazone/ashot/DiffMarkupPolicyTest.java
================================================
package pazone.ashot;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import pazone.ashot.comparison.DiffMarkupPolicy;
import pazone.ashot.comparison.ImageMarkupPolicy;
import pazone.ashot.comparison.PointsMarkupPolicy;
import java.awt.Point;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;
import static pazone.ashot.util.TestImageUtils.IMAGE_B_SMALL;
/**
* @author Rovniakov Viacheslav rovner@yandex-team.ru
*/
class DiffMarkupPolicyTest {
private static Stream<Arguments> data() {
return Stream.of(
Arguments.of(new PointsMarkupPolicy(), new PointsMarkupPolicy()),
Arguments.of(new ImageMarkupPolicy(), new ImageMarkupPolicy())
);
}
private void initDiffMarkupPolicies(DiffMarkupPolicy diffMarkupPolicyA, DiffMarkupPolicy diffMarkupPolicyB) {
diffMarkupPolicyA.setDiffImage(IMAGE_A_SMALL);
diffMarkupPolicyB.setDiffImage(IMAGE_B_SMALL);
}
@ParameterizedTest
@MethodSource("data")
void testEquality(DiffMarkupPolicy diffMarkupPolicyA, DiffMarkupPolicy diffMarkupPolicyB) {
initDiffMarkupPolicies(diffMarkupPolicyA, diffMarkupPolicyB);
addDiffPoints(getDiffPointsA(), diffMarkupPolicyA, 1, 2);
addDiffPoints(getDiffPointsA(), diffMarkupPolicyB, 0, 3);
assertThat(diffMarkupPolicyA, equalTo(diffMarkupPolicyB));
assertThat(diffMarkupPolicyA.hashCode(), equalTo(diffMarkupPolicyB.hashCode()));
}
@ParameterizedTest
@MethodSource("data")
void testNotEquality(DiffMarkupPolicy diffMarkupPolicyA, DiffMarkupPolicy diffMarkupPolicyB) {
initDiffMarkupPolicies(diffMarkupPolicyA, diffMarkupPolicyB);
addDiffPoints(getDiffPointsA(), diffMarkupPolicyA, 0, 0);
addDiffPoints(getDiffPointsB(), diffMarkupPolicyB, 0, 0);
assertThat(diffMarkupPolicyA, not(equalTo(diffMarkupPolicyB)));
assertThat(diffMarkupPolicyA.hashCode(), not(equalTo(diffMarkupPolicyB.hashCode())));
}
@ParameterizedTest
@MethodSource("data")
void testNotEqualityByNumber(DiffMarkupPolicy diffMarkupPolicyA, DiffMarkupPolicy diffMarkupPolicyB) {
initDiffMarkupPolicies(diffMarkupPolicyA, diffMarkupPolicyB);
addDiffPoints(getDiffPointsA(), diffMarkupPolicyA, 0, 0);
addDiffPoints(getDiffPointsB(), diffMarkupPolicyA, 0, 0);
diffMarkupPolicyB.addDiffPoint(0, 0);
assertThat(diffMarkupPolicyA, not(equalTo(diffMarkupPolicyB)));
assertThat(diffMarkupPolicyA.hashCode(), not(equalTo(diffMarkupPolicyB.hashCode())));
}
private Set<Point> getDiffPointsA() {
return new HashSet<Point>() {{
add(new Point(3, 4));
add(new Point(3, 5));
add(new Point(3, 6));
add(new Point(4, 4));
add(new Point(4, 5));
add(new Point(4, 6));
}};
}
private Set<Point> getDiffPointsB() {
return new HashSet<Point>() {{
add(new Point(3, 4));
add(new Point(3, 5));
add(new Point(3, 6));
add(new Point(4, 4));
add(new Point(4, 5));
add(new Point(4, 7));
}};
}
private void addDiffPoints(Set<Point> points, DiffMarkupPolicy diffMarkupPolicy, int xShift, int yShift) {
for (Point point : points) {
diffMarkupPolicy.addDiffPoint((int) point.getX() - xShift, (int) point.getY() - yShift);
}
}
}
================================================
FILE: src/test/java/pazone/ashot/DifferTest.java
================================================
package pazone.ashot;
import pazone.ashot.comparison.DiffMarkupPolicy;
import pazone.ashot.comparison.ImageDiff;
import pazone.ashot.comparison.ImageDiffer;
import pazone.ashot.comparison.ImageMarkupPolicy;
import pazone.ashot.comparison.PointsMarkupPolicy;
import pazone.ashot.coordinates.Coords;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.Collections;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;
import static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL_PATH;
import static pazone.ashot.util.TestImageUtils.IMAGE_B_SMALL;
import static pazone.ashot.util.TestImageUtils.assertImageEquals;
import static pazone.ashot.util.TestImageUtils.loadImage;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
class DifferTest {
private static final BufferedImage IMAGE_B_BIG = loadImage("img/B_b.png");
private static final String IMAGE_IGNORED_TEMPLATE = "img/ignore_color_template.png";
private static final String IMAGE_IGNORED_PASS = "img/ignore_color_pass.png";
private static final String IMAGE_IGNORED_FAIL = "img/ignore_color_fail.png";
private static Stream<DiffMarkupPolicy> data() {
return Stream.of(new PointsMarkupPolicy(), new ImageMarkupPolicy());
}
private ImageDiffer createImageDiffer(DiffMarkupPolicy diffMarkupPolicy) {
return new ImageDiffer()
.withColorDistortion(10)
.withDiffMarkupPolicy(diffMarkupPolicy.withDiffColor(Color.RED));
}
@ParameterizedTest
@MethodSource("data")
void testSameSizeDiff(DiffMarkupPolicy diffMarkupPolicy) {
ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(IMAGE_A_SMALL, IMAGE_B_SMALL);
assertAll(
() -> assertImageEquals(diff.getMarkedImage(), "img/expected/same_size_diff.png"),
() -> assertImageEquals(diff.getTransparentMarkedImage(), "img/expected/transparent_diff.png"),
() -> assertThat(diff.withDiffSizeTrigger(624).hasDiff(), is(false)),
() -> assertThat(diff.withDiffSizeTrigger(623).hasDiff(), is(true))
);
}
@ParameterizedTest
@MethodSource("data")
void testDifferentSizeDiff(DiffMarkupPolicy diffMarkupPolicy) {
ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(IMAGE_B_SMALL, IMAGE_B_BIG);
assertImageEquals(diff.getMarkedImage(), "img/expected/different_size_diff.png");
}
@ParameterizedTest
@MethodSource("data")
void testSetDiffColor(DiffMarkupPolicy diffMarkupPolicy) {
ImageDiffer greenDiffer = new ImageDiffer()
.withDiffMarkupPolicy(diffMarkupPolicy.withDiffColor(Color.GREEN));
ImageDiff diff = greenDiffer.makeDiff(IMAGE_A_SMALL, IMAGE_B_SMALL);
assertImageEquals(diff.getMarkedImage(), "img/expected/green_diff.png");
}
@ParameterizedTest
@MethodSource("data")
void testEqualImagesDiff(DiffMarkupPolicy diffMarkupPolicy) {
ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(IMAGE_A_SMALL, IMAGE_A_SMALL);
assertFalse(diff.hasDiff());
}
static Stream<Arguments> dataWithIgnoredColorDiff() {
return Stream.of(
Arguments.of(Color.MAGENTA, IMAGE_IGNORED_PASS, false),
Arguments.of(Color.MAGENTA, IMAGE_IGNORED_FAIL, true),
Arguments.of(Color.RED, IMAGE_IGNORED_PASS, true)
);
}
@ParameterizedTest
@MethodSource("dataWithIgnoredColorDiff")
void testDiffImagesWithIgnoredColorDiff(Color ignoredColor, String imageToCompare, boolean hasDiff) {
ImageDiffer imageDifferWithIgnored = new ImageDiffer().withIgnoredColor(ignoredColor);
ImageDiff diff = imageDifferWithIgnored.makeDiff(loadImage(IMAGE_IGNORED_TEMPLATE), loadImage(imageToCompare));
assertThat(diff.hasDiff(), equalTo(hasDiff));
}
@ParameterizedTest
@MethodSource("data")
void testIgnoredCoordsSame(DiffMarkupPolicy diffMarkupPolicy) {
Screenshot a = createScreenshotWithIgnoredAreas(IMAGE_A_SMALL, new Coords(50, 50));
Screenshot b = createScreenshotWithIgnoredAreas(IMAGE_B_SMALL, new Coords(50, 50));
ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(a, b);
assertImageEquals(diff.getMarkedImage(), "img/expected/ignore_coords_same.png");
}
@ParameterizedTest
@MethodSource("data")
void testIgnoredCoordsNotSame(DiffMarkupPolicy diffMarkupPolicy) {
Screenshot a = createScreenshotWithIgnoredAreas(IMAGE_A_SMALL, new Coords(55, 55));
Screenshot b = createScreenshotWithIgnoredAreas(IMAGE_B_SMALL, new Coords(80, 80));
ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(a, b);
assertImageEquals(diff.getMarkedImage(), "img/expected/ignore_coords_not_same.png");
}
@ParameterizedTest
@MethodSource("data")
void testCoordsToCompareAndIgnoredCombine(DiffMarkupPolicy diffMarkupPolicy) {
Screenshot a = createScreenshotWithIgnoredAreas(IMAGE_A_SMALL, new Coords(60, 60));
a.setCoordsToCompare(Collections.singleton(new Coords(50, 50, 100, 100)));
Screenshot b = createScreenshotWithIgnoredAreas(IMAGE_B_SMALL, new Coords(80, 80));
b.setCoordsToCompare(Collections.singleton(new Coords(50, 50, 100, 100)));
ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(a, b);
assertImageEquals(diff.getMarkedImage(), "img/expected/combined_diff.png");
}
@ParameterizedTest
@MethodSource("data")
void testDiffSize(DiffMarkupPolicy diffMarkupPolicy) {
String path = IMAGE_A_SMALL_PATH;
BufferedImage image1 = loadImage(path);
BufferedImage image2 = loadImage(path);
int rgb = Color.GREEN.getRGB();
int diffSize = 10;
for (int i = 0; i < diffSize; i++) {
image2.setRGB(i, 1, rgb);
}
ImageDiff imageDiff = createImageDiffer(diffMarkupPolicy).makeDiff(image1, image2);
assertEquals(diffSize, imageDiff.getDiffSize(), "Should have diff size " + diffSize);
}
private Screenshot createScreenshotWithIgnoredAreas(BufferedImage image, Coords ignoredArea) {
Screenshot screenshot = new Screenshot(image);
screenshot.setIgnoredAreas(Collections.singleton(ignoredArea));
return screenshot;
}
}
================================================
FILE: src/test/java/pazone/ashot/ImageBytesDifferTest.java
================================================
package pazone.ashot;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import static pazone.ashot.util.ImageBytesDiffer.areImagesEqual;
import static pazone.ashot.util.TestImageUtils.loadImage;
import java.util.stream.Stream;
/**
* @author <a href="frolic@yandex-team.ru">Viacheslav Frolov</a>
*/
class ImageBytesDifferTest {
@TestFactory
Stream<DynamicTest> testDifferentImages() {
return Stream.of(
dynamicTest("Different size", () -> testDifferentImages("img/SolidColor_scaled.png")),
dynamicTest("One pixel difference", () -> testDifferentImages("img/SolidColor_1px_red.png")),
dynamicTest("Color mode difference", () -> testDifferentImages("img/SolidColor_indexed.png"))
);
}
void testDifferentImages(String path) {
assertFalse(areImagesEqual(loadImage("img/SolidColor.png"), loadImage(path)), "Images should differ");
}
@Test
void testEqualImages() {
assertTrue(areImagesEqual(loadImage("img/SolidColor.png"), loadImage("img/SolidColor.png")),
"Images should equal");
}
}
================================================
FILE: src/test/java/pazone/ashot/RotatingDecoratorTest.java
================================================
package pazone.ashot;
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.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import pazone.ashot.cutter.FixedCutStrategy;
import pazone.ashot.util.ImageTool;
import java.awt.image.BufferedImage;
import java.io.IOException;
import static org.mockito.Mockito.when;
import static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;
import static pazone.ashot.util.TestImageUtils.assertImageEquals;
/**
* @author <a href="rovner@yandex-team.ru">Rovniakov Viacheslav</a>
*/
@ExtendWith(MockitoExtension.class)
class RotatingDecoratorTest {
@Mock(extraInterfaces = TakesScreenshot.class)
private WebDriver webDriver;
@Test
void testRotating() throws IOException {
when(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES)).thenReturn(
ImageTool.toByteArray(IMAGE_A_SMALL));
ShootingStrategy strategy = new RotatingDecorator(new FixedCutStrategy(0, 0), new SimpleShootingStrategy());
BufferedImage screenshot = strategy.getScreenshot(webDriver);
assertImageEquals(screenshot, "img/expected/rotated.png");
}
}
================================================
FILE: src/test/java/pazone/ashot/ScalingDecoratorTest.java
================================================
package pazone.ashot;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import pazone.ashot.util.ImageTool;
import pazone.ashot.util.TestImageUtils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;
import static pazone.ashot.util.TestImageUtils.assertImageEquals;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* @author <a href="pazone@yandex-team.ru">Pavel Zorin</a>
*/
@ExtendWith(MockitoExtension.class)
class ScalingDecoratorTest {
@Mock(extraInterfaces = TakesScreenshot.class)
private WebDriver webDriver;
@CsvSource({
"2, img/expected/dpr.png",
"1, " + TestImageUtils.IMAGE_A_SMALL_PATH
})
@ParameterizedTest
void testDpr(float dpr, String expectedImagePath) throws IOException {
when(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES)).thenReturn(
ImageTool.toByteArray(IMAGE_A_SMALL));
ShootingStrategy dpr2Strategy = new ScalingDecorator(new SimpleShootingStrategy()).withDpr(dpr);
BufferedImage screenshot = dpr2Strategy.getScreenshot(webDriver);
assertImageEquals(screenshot, expectedImagePath);
assertEquals(IMAGE_A_SMALL.getType(), screenshot.getType());
}
}
================================================
FILE: src/test/java/pazone/ashot/SerializeScreenshotTest.java
================================================
package pazone.ashot;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import pazone.ashot.coordinates.Coords;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;
import static pazone.ashot.util.TestImageUtils.assertImageEquals;
/**
* @author <a href="eoff@yandex-team.ru">Maksim Mukosey</a>
*/
class SerializeScreenshotTest {
static Stream<Set<Coords>> ignoredAreas() {
return Stream.of(
Collections.emptySet(),
Collections.singleton(new Coords(20, 20, 200, 90))
);
}
@ParameterizedTest
@MethodSource("ignoredAreas")
void testSerialization(Set<Coords> ignoredAreas, @TempDir Path tempDir) throws IOException, ClassNotFoundException {
File serializedFile = tempDir.resolve("serialized").toFile();
Screenshot screenshot = new Screenshot(IMAGE_A_SMALL);
screenshot.setIgnoredAreas(ignoredAreas);
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(serializedFile))) {
objectOutputStream.writeObject(screenshot);
}
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(serializedFile))) {
Screenshot deserialized = (Screenshot) objectInputStream.readObject();
assertThat(deserialized.getCoordsToCompare(), equalTo(screenshot.getCoordsToCompare()));
assertThat(deserialized.getIgnoredAreas(), equalTo(screenshot.getIgnoredAreas()));
assertImageEquals(deserialized.getImage(), screenshot.getImage());
}
}
}
================================================
FILE: src/test/java/pazone/ashot/VariableCutStrategyTest.java
================================================
package pazone.ashot;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import pazone.ashot.cutter.CutStrategy;
import pazone.ashot.cutter.VariableCutStrategy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
/**
* @author <a href="frolic@yandex-team.ru">Vyacheslav Frolov</a>
*/
@ExtendWith(MockitoExtension.class)
class VariableCutStrategyTest {
private static final int MAX_HEADER_HEIGHT = 65;
private static final int MIN_HEADER_HEIGHT = 41;
private static final int MIN_INNER_HEIGHT = 960;
private final CutStrategy strategy = new VariableCutStrategy(MIN_HEADER_HEIGHT, MAX_HEADER_HEIGHT,
MIN_INNER_HEIGHT);
@Mock(extraInterfaces = JavascriptExecutor.class)
private WebDriver webDriver;
@ParameterizedTest
@CsvSource({
"960, 65",
"984, 41"
})
void testGetBrowserHeaderHeight(long viewportHeight, int browserHeaderHeight) {
mockViewportInnerHeight(viewportHeight);
int headerHeight = strategy.getHeaderHeight(webDriver);
assertThat("Header height should be detected correctly", browserHeaderHeight, is(headerHeight));
}
@ParameterizedTest
@CsvSource({
"a string",
","
})
void testGetBrowserHeaderHeightWithInvalidViewportHeight(String viewportHeight) {
mockViewportInnerHeight(viewportHeight);
assertThrows(InvalidViewportHeightException.class, () -> strategy.getHeaderHeight(webDriver));
}
private void mockViewportInnerHeight(Object viewportHeight) {
when(((JavascriptExecutor) webDriver).executeScript(VariableCutStrategy.SCRIPT)).thenReturn(viewportHeight);
}
}
================================================
FILE: src/test/java/pazone/ashot/VerticalPastingShootingStrategyTest.java
================================================
package pazone.ashot;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import pazone.ashot.coordinates.Coords;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Set;
import java.util.stream.Stream;
import static java.awt.image.BufferedImage.TYPE_4BYTE_ABGR_PRE;
import static java.util.Collections.singleton;
import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class VerticalPastingShootingStrategyTest {
private static final int VIEWPORT_HEIGHT = 80;
private static final int DEFAULT_PAGE_HEIGHT = VIEWPORT_HEIGHT * 10 + VIEWPORT_HEIGHT / 2;
private static final int PAGE_WIDTH = 100;
private static final int DEFAULT_COORDS_INDENT = VIEWPORT_HEIGHT / 2;
private final BufferedImage viewPortShot = new BufferedImage(PAGE_WIDTH, VIEWPORT_HEIGHT, TYPE_4BYTE_ABGR_PRE);
private Coords shootingCoords;
private BufferedImage screenshot;
private Set<Coords> preparedCoords;
@Mock(extraInterfaces = {JavascriptExecutor.class, TakesScreenshot.class})
private WebDriver webDriver;
@Spy
private ViewportPastingDecorator shootingStrategy = new MockVerticalPastingShootingDecorator(
new SimpleShootingStrategy()).withScrollTimeout(0);
static Stream<Arguments> timesData() {
return Stream.of(
Arguments.of(VIEWPORT_HEIGHT, 2),
Arguments.of(VIEWPORT_HEIGHT / 2, 1),
Arguments.of(VIEWPORT_HEIGHT * 3, 4),
Arguments.of(0, 1)
);
}
@ParameterizedTest
@MethodSource("timesData")
void testTimes(int height, int times) throws IOException {
givenCoordsWithHeight(height);
whenTakingScreenshot(shootingCoords);
thenShootTimes(times);
thenScreenshotIsHeight(height + DEFAULT_COORDS_INDENT);
}
@Test
void testCoordsShiftWithDefaultIndent() throws IOException {
givenCoordsWithHeight(VIEWPORT_HEIGHT / 3);
whenTakingScreenshot(shootingCoords);
whenPreparingCoords(singleton(shootingCoords));
thenCoordsShifted();
}
@Test
void testScreenshotFullPage() throws IOException {
whenTakingScreenshot();
thenShootTimes(11);
thenScreenshotIsHeight(DEFAULT_PAGE_HEIGHT);
}
private byte[] getImageAsBytes() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(viewPortShot, "PNG", baos);
return baos.toByteArray();
}
private void mockScreenshotting() throws IOException {
when(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES)).thenReturn(getImageAsBytes());
}
private void givenCoordsWithHeight(int height) {
shootingCoords = new Coords(0, VIEWPORT_HEIGHT * 4, PAGE_WIDTH, height);
}
private void whenTakingScreenshot() throws IOException {
mockScreenshotting();
screenshot = shootingStrategy.getScreenshot(webDriver);
}
private void whenTakingScreenshot(Coords coords) throws IOException {
mockScreenshotting();
screenshot = shootingStrategy.getScreenshot(webDriver, singleton(coords));
}
private void whenPreparingCoords(Set<Coords> coords) {
preparedCoords = shootingStrategy.prepareCoords(coords);
}
private void thenCoordsShifted() {
assertThat("Coords should be shifted correctly", preparedCoords,
everyItem(hasProperty("y", is((double) DEFAULT_COORDS_INDENT / 2))));
}
private void thenScreenshotIsHeight(int shotHeight) {
assertThat("Screenshot height should be correct", screenshot.getHeight(), is(shotHeight));
}
private void thenShootTimes(int times) {
verify(((TakesScreenshot) webDriver), times(times)).getScreenshotAs(OutputType.BYTES);
verify(shootingStrategy, times(times + 1)).scrollVertically(eq((JavascriptExecutor) webDriver), anyInt());
}
static class MockVerticalPastingShootingDecorator extends ViewportPastingDecorator {
MockVerticalPastingShootingDecorator(ShootingStrategy strategy) {
super(strategy);
}
@Override
protected PageDimensions getPageDimensions(WebDriver driver) {
return new PageDimensions(DEFAULT_PAGE_HEIGHT, PAGE_WIDTH, VIEWPORT_HEIGHT);
}
@Override
public int getCurrentScrollY(JavascriptExecutor js) {
return 0;
}
}
}
================================================
FILE: src/test/java/pazone/ashot/coordinates/WebDriverCoordsProviderTest.java
================================================
package pazone.ashot.coordinates;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebElement;
class WebDriverCoordsProviderTest {
private WebDriverCoordsProvider webDriverCoordsProvider = new WebDriverCoordsProvider();
@Test
void testGetCoordinatesOfElement() {
WebElement element = mock(WebElement.class);
int x = 1;
int y = 2;
int height = 3;
int width = 4;
when(element.getLocation()).thenReturn(new Point(x, y));
when(element.getSize()).thenReturn(new Dimension(width, height));
Coords coords = webDriverCoordsProvider.ofElement(null, element);
assertAll(
() -> assertEquals(x, coords.x),
() -> assertEquals(y, coords.y),
() -> assertEquals(height, coords.height),
() -> assertEquals(width, coords.width)
);
}
}
================================================
FILE: src/test/java/pazone/ashot/util/ImageToolTest.java
================================================
package pazone.ashot.util;
import java.io.IOException;
import org.junit.jupiter.api.Test;
class ImageToolTest {
@Test
void shouldConvertImageToBytesAndViceVersa() throws IOException {
byte[] imageBytes = ImageTool.toByteArray(TestImageUtils.IMAGE_A_SMALL);
TestImageUtils.assertImageEquals(TestImageUtils.IMAGE_A_SMALL, ImageTool.toBufferedImage(imageBytes));
}
}
================================================
FILE: src/test/java/pazone/ashot/util/TestImageUtils.java
================================================
package pazone.ashot.util;
import static org.hamcrest.MatcherAssert.assertThat;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public final class TestImageUtils {
public static final String IMAGE_A_SMALL_PATH = "img/A_s.png";
public static final BufferedImage IMAGE_A_SMALL = loadImage(IMAGE_A_SMALL_PATH);
public static final BufferedImage IMAGE_B_SMALL = loadImage("img/B_s.png");
private TestImageUtils() {
}
public static BufferedImage loadImage(String path) {
try {
return ImageIO.read(ClassLoader.getSystemResourceAsStream(path));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void assertImageEquals(BufferedImage actualImage, String expectedImagePath) {
assertImageEquals(actualImage, loadImage(expectedImagePath));
}
public static void assertImageEquals(BufferedImage actualImage, BufferedImage expectedImage) {
assertThat(actualImage, ImageTool.equalImage(expectedImage));
}
}
gitextract_ur45qq_0/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ ├── release.yml
│ └── snapshot.yml
├── .gitignore
├── .mvn/
│ └── wrapper/
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── LICENCE
├── README.md
├── config/
│ └── checkstyle/
│ └── checkstyle.xml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── pazone/
│ │ └── ashot/
│ │ ├── AShot.java
│ │ ├── CdpShootingStrategy.java
│ │ ├── CuttingDecorator.java
│ │ ├── HandlingSickyElementsViewportPastingDecorator.java
│ │ ├── ImageReadException.java
│ │ ├── InvalidViewportHeightException.java
│ │ ├── PageDimensions.java
│ │ ├── RotatingDecorator.java
│ │ ├── ScalingDecorator.java
│ │ ├── Screenshot.java
│ │ ├── ShootingDecorator.java
│ │ ├── ShootingStrategies.java
│ │ ├── ShootingStrategy.java
│ │ ├── SimpleShootingStrategy.java
│ │ ├── ViewportPastingDecorator.java
│ │ ├── comparison/
│ │ │ ├── DiffMarkupPolicy.java
│ │ │ ├── ImageDiff.java
│ │ │ ├── ImageDiffer.java
│ │ │ ├── ImageMarkupPolicy.java
│ │ │ └── PointsMarkupPolicy.java
│ │ ├── coordinates/
│ │ │ ├── Coords.java
│ │ │ ├── CoordsPreparationStrategy.java
│ │ │ ├── CoordsProvider.java
│ │ │ ├── JqueryCoordsProvider.java
│ │ │ └── WebDriverCoordsProvider.java
│ │ ├── cropper/
│ │ │ ├── DefaultCropper.java
│ │ │ ├── ImageCropper.java
│ │ │ └── indent/
│ │ │ ├── BlurFilter.java
│ │ │ ├── IndentCropper.java
│ │ │ ├── IndentFilerFactory.java
│ │ │ ├── IndentFilter.java
│ │ │ └── MonochromeFilter.java
│ │ ├── cutter/
│ │ │ ├── CutStrategy.java
│ │ │ ├── FixedCutStrategy.java
│ │ │ └── VariableCutStrategy.java
│ │ └── util/
│ │ ├── ImageBytesDiffer.java
│ │ ├── ImageTool.java
│ │ ├── InnerScript.java
│ │ └── JsCoords.java
│ └── resources/
│ └── js/
│ ├── coords-single.js
│ └── page_dimensions.js
└── test/
└── java/
└── pazone/
└── ashot/
├── CdpShootingStrategyTest.java
├── CroppersTest.java
├── CuttingDecoratorTest.java
├── DiffMarkupPolicyTest.java
├── DifferTest.java
├── ImageBytesDifferTest.java
├── RotatingDecoratorTest.java
├── ScalingDecoratorTest.java
├── SerializeScreenshotTest.java
├── VariableCutStrategyTest.java
├── VerticalPastingShootingStrategyTest.java
├── coordinates/
│ └── WebDriverCoordsProviderTest.java
└── util/
├── ImageToolTest.java
└── TestImageUtils.java
SYMBOL INDEX (372 symbols across 54 files)
FILE: src/main/java/pazone/ashot/AShot.java
class AShot (line 26) | public class AShot implements Serializable {
method coordsProvider (line 33) | public AShot coordsProvider(final CoordsProvider coordsProvider) {
method imageCropper (line 38) | @SuppressWarnings("UnusedDeclaration")
method ignoredElements (line 50) | @SuppressWarnings("UnusedDeclaration")
method addIgnoredElement (line 62) | public synchronized AShot addIgnoredElement(final By selector) {
method ignoredAreas (line 72) | @SuppressWarnings("UnusedDeclaration")
method addIgnoredArea (line 83) | @SuppressWarnings("UnusedDeclaration")
method shootingStrategy (line 96) | public AShot shootingStrategy(ShootingStrategy strategy) {
method takeScreenshot (line 111) | public Screenshot takeScreenshot(WebDriver driver, Collection<WebEleme...
method takeScreenshot (line 129) | public Screenshot takeScreenshot(WebDriver driver, WebElement element) {
method takeScreenshot (line 140) | public Screenshot takeScreenshot(WebDriver driver) {
method compileIgnoredAreas (line 146) | protected synchronized Set<Coords> compileIgnoredAreas(WebDriver driver,
method getIgnoredLocators (line 161) | @SuppressWarnings("UnusedDeclaration")
FILE: src/main/java/pazone/ashot/CdpShootingStrategy.java
class CdpShootingStrategy (line 23) | public class CdpShootingStrategy implements ShootingStrategy {
method getScreenshot (line 27) | @Override
method getScreenshot (line 32) | @Override
method prepareCoords (line 63) | @Override
FILE: src/main/java/pazone/ashot/CuttingDecorator.java
class CuttingDecorator (line 16) | public class CuttingDecorator extends ShootingDecorator {
method CuttingDecorator (line 20) | public CuttingDecorator(ShootingStrategy strategy) {
method withCut (line 30) | public CuttingDecorator withCut(int headerToCut, int footerToCut) {
method withCut (line 42) | public CuttingDecorator withCut(int headerToCut, int footerToCut, int ...
method withCutStrategy (line 51) | public CuttingDecorator withCutStrategy(CutStrategy cutStrategy) {
method getScreenshot (line 56) | @Override
method getScreenshot (line 69) | @Override
method getHeaderToCut (line 74) | protected int getHeaderToCut(WebDriver wd) {
method getFooterToCut (line 78) | protected int getFooterToCut(WebDriver wd) {
method getLeftBarToCut (line 82) | protected int getLeftBarToCut(WebDriver wd) {
method getRightBarToCut (line 86) | protected int getRightBarToCut(WebDriver wd) {
FILE: src/main/java/pazone/ashot/HandlingSickyElementsViewportPastingDecorator.java
class HandlingSickyElementsViewportPastingDecorator (line 8) | public class HandlingSickyElementsViewportPastingDecorator extends Viewp...
method HandlingSickyElementsViewportPastingDecorator (line 14) | public HandlingSickyElementsViewportPastingDecorator(ShootingStrategy ...
method getPageDimensions (line 21) | @Override
method getChunk (line 28) | @Override
method getCurrentScrollY (line 42) | @Override
FILE: src/main/java/pazone/ashot/ImageReadException.java
class ImageReadException (line 6) | public class ImageReadException extends RuntimeException {
method ImageReadException (line 8) | public ImageReadException(String message) {
method ImageReadException (line 12) | public ImageReadException(String message, Exception e) {
FILE: src/main/java/pazone/ashot/InvalidViewportHeightException.java
class InvalidViewportHeightException (line 6) | public class InvalidViewportHeightException extends RuntimeException {
method InvalidViewportHeightException (line 8) | public InvalidViewportHeightException(String message) {
method InvalidViewportHeightException (line 12) | public InvalidViewportHeightException(String message, Exception e) {
FILE: src/main/java/pazone/ashot/PageDimensions.java
class PageDimensions (line 3) | public final class PageDimensions {
method PageDimensions (line 8) | public PageDimensions(int pageHeight, int viewportWidth, int viewportH...
method getPageHeight (line 14) | public int getPageHeight() {
method getViewportWidth (line 18) | public int getViewportWidth() {
method getViewportHeight (line 22) | public int getViewportHeight() {
FILE: src/main/java/pazone/ashot/RotatingDecorator.java
class RotatingDecorator (line 17) | public class RotatingDecorator implements ShootingStrategy {
method RotatingDecorator (line 22) | public RotatingDecorator(CutStrategy cutStrategy, ShootingStrategy sho...
method getScreenshot (line 27) | @Override
method getScreenshot (line 32) | @Override
method prepareCoords (line 37) | @Override
method rotate (line 42) | private BufferedImage rotate(BufferedImage baseImage, WebDriver wd) {
FILE: src/main/java/pazone/ashot/ScalingDecorator.java
class ScalingDecorator (line 17) | public class ScalingDecorator extends ShootingDecorator {
method ScalingDecorator (line 23) | public ScalingDecorator(ShootingStrategy strategy) {
method getScreenshot (line 27) | @Override
method getScreenshot (line 32) | @Override
method withDprX (line 37) | public ScalingDecorator withDprX(float dprX) {
method withDprY (line 42) | public ScalingDecorator withDprY(float dprY) {
method withDpr (line 47) | public ScalingDecorator withDpr(float dpr) {
method scale (line 53) | private BufferedImage scale(BufferedImage image) {
FILE: src/main/java/pazone/ashot/Screenshot.java
class Screenshot (line 22) | public class Screenshot implements Serializable {
method getImage (line 36) | public BufferedImage getImage() {
method setImage (line 40) | public void setImage(BufferedImage image) {
method Screenshot (line 44) | public Screenshot(BufferedImage image) {
method getCoordsToCompare (line 49) | public Set<Coords> getCoordsToCompare() {
method setCoordsToCompare (line 53) | public void setCoordsToCompare(Set<Coords> coordsToCompare) {
method getIgnoredAreas (line 57) | public Set<Coords> getIgnoredAreas() {
method setIgnoredAreas (line 61) | public void setIgnoredAreas(Set<Coords> ignoredAreas) {
method getOriginShift (line 65) | public Coords getOriginShift() {
method setOriginShift (line 69) | public void setOriginShift(Coords originShift) {
method writeObject (line 73) | private void writeObject(ObjectOutputStream out) throws IOException {
method readObject (line 78) | private void readObject(ObjectInputStream in) throws IOException, Clas...
FILE: src/main/java/pazone/ashot/ShootingDecorator.java
class ShootingDecorator (line 7) | public abstract class ShootingDecorator implements ShootingStrategy {
method ShootingDecorator (line 11) | protected ShootingDecorator(ShootingStrategy shootingStrategy) {
method getShootingStrategy (line 15) | public ShootingStrategy getShootingStrategy() {
method prepareCoords (line 22) | @Override
FILE: src/main/java/pazone/ashot/ShootingStrategies.java
class ShootingStrategies (line 10) | public final class ShootingStrategies {
method ShootingStrategies (line 25) | private ShootingStrategies() {
method simple (line 33) | public static ShootingStrategy simple() {
method scaling (line 44) | public static ShootingStrategy scaling(ShootingStrategy shootingStrate...
method scaling (line 54) | public static ShootingStrategy scaling(float dpr) {
method cutting (line 65) | public static ShootingStrategy cutting(ShootingStrategy shootingStrate...
method cutting (line 75) | public static ShootingStrategy cutting(CutStrategy cutStrategy) {
method cutting (line 86) | public static ShootingStrategy cutting(int headerToCut, int footerToCu...
method viewportPasting (line 97) | public static ShootingStrategy viewportPasting(ShootingStrategy shooti...
method viewportPasting (line 107) | public static ShootingStrategy viewportPasting(int scrollTimeout) {
method viewportNonRetina (line 120) | public static ShootingStrategy viewportNonRetina(ShootingStrategy shoo...
method viewportNonRetina (line 133) | public static ShootingStrategy viewportNonRetina(int scrollTimeout, Cu...
method viewportNonRetina (line 146) | public static ShootingStrategy viewportNonRetina(int scrollTimeout, in...
method viewportRetina (line 160) | public static ShootingStrategy viewportRetina(ShootingStrategy shootin...
method viewportRetina (line 175) | public static ShootingStrategy viewportRetina(int scrollTimeout, CutSt...
method viewportRetina (line 189) | public static ShootingStrategy viewportRetina(int scrollTimeout, int h...
method viewportRetina (line 204) | public static ShootingStrategy viewportRetina(ShootingStrategy shootin...
method iPad2WithIOS7 (line 209) | public static ShootingStrategy iPad2WithIOS7(ShootingStrategy shooting...
method iPad2WithIOS7 (line 213) | public static ShootingStrategy iPad2WithIOS7() {
method iPad2WithIOS8 (line 217) | public static ShootingStrategy iPad2WithIOS8(ShootingStrategy shooting...
method iPad2WithIOS8 (line 221) | public static ShootingStrategy iPad2WithIOS8() {
method iPad2WithIOS8Simulator (line 225) | public static ShootingStrategy iPad2WithIOS8Simulator(ShootingStrategy...
method iPad2WithIOS8Simulator (line 229) | public static ShootingStrategy iPad2WithIOS8Simulator() {
method iPad2WithIOS8Retina (line 233) | public static ShootingStrategy iPad2WithIOS8Retina(ShootingStrategy sh...
method iPad2WithIOS8Retina (line 237) | public static ShootingStrategy iPad2WithIOS8Retina() {
method iPad2WithIOS8RetinaSimulator (line 241) | public static ShootingStrategy iPad2WithIOS8RetinaSimulator(ShootingSt...
method iPad2WithIOS8RetinaSimulator (line 245) | public static ShootingStrategy iPad2WithIOS8RetinaSimulator() {
method iPadLandscapeOrientation (line 256) | public static ShootingStrategy iPadLandscapeOrientation(int scrollTime...
method iPadLandscapeOrientationSimple (line 266) | public static ShootingStrategy iPadLandscapeOrientationSimple(CutStrat...
method viewportIOSNonRetina (line 270) | private static ShootingStrategy viewportIOSNonRetina(ShootingStrategy ...
method viewportIOSRetina (line 274) | private static ShootingStrategy viewportIOSRetina(ShootingStrategy sho...
method iOS8CutStrategy (line 278) | private static CutStrategy iOS8CutStrategy(int minViewport) {
FILE: src/main/java/pazone/ashot/ShootingStrategy.java
type ShootingStrategy (line 13) | public interface ShootingStrategy extends Serializable {
method getScreenshot (line 21) | BufferedImage getScreenshot(WebDriver wd);
method getScreenshot (line 30) | BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords);
method prepareCoords (line 38) | Set<Coords> prepareCoords(Set<Coords> coordsSet);
FILE: src/main/java/pazone/ashot/SimpleShootingStrategy.java
class SimpleShootingStrategy (line 17) | public class SimpleShootingStrategy implements ShootingStrategy {
method getScreenshot (line 19) | @Override
method getScreenshot (line 35) | @Override
method prepareCoords (line 43) | @Override
FILE: src/main/java/pazone/ashot/ViewportPastingDecorator.java
class ViewportPastingDecorator (line 19) | public class ViewportPastingDecorator extends ShootingDecorator {
method ViewportPastingDecorator (line 26) | public ViewportPastingDecorator(ShootingStrategy strategy) {
method withScrollTimeout (line 30) | public ViewportPastingDecorator withScrollTimeout(int scrollTimeout) {
method getScreenshot (line 35) | @Override
method getScreenshot (line 40) | @Override
method prepareCoords (line 69) | @Override
method getPageDimensions (line 74) | protected PageDimensions getPageDimensions(WebDriver driver) {
method getCurrentScrollY (line 80) | protected int getCurrentScrollY(JavascriptExecutor js) {
method scrollVertically (line 85) | protected void scrollVertically(JavascriptExecutor js, int scrollY) {
method getChunk (line 97) | protected BufferedImage getChunk(WebDriver wd, int currentChunkIndex, ...
method getShootingCoords (line 101) | private Coords getShootingCoords(Set<Coords> coords, PageDimensions pa...
method shiftCoords (line 108) | private Set<Coords> shiftCoords(Set<Coords> coordsSet, Coords shooting...
method extendShootingArea (line 119) | private Coords extendShootingArea(Coords shootingCoords, PageDimension...
method waitForScrolling (line 126) | private void waitForScrolling() {
FILE: src/main/java/pazone/ashot/comparison/DiffMarkupPolicy.java
class DiffMarkupPolicy (line 13) | public abstract class DiffMarkupPolicy {
method withDiffColor (line 23) | public DiffMarkupPolicy withDiffColor(final Color diffColor) {
method getMarkedImage (line 28) | public abstract BufferedImage getMarkedImage();
method getTransparentMarkedImage (line 30) | public abstract BufferedImage getTransparentMarkedImage();
method addDiffPoint (line 32) | public abstract void addDiffPoint(int x, int y);
method equals (line 34) | @Override
method hashCode (line 37) | @Override
method hasDiff (line 40) | public abstract boolean hasDiff();
method getDiffSize (line 42) | public abstract int getDiffSize();
method setDiffImage (line 44) | public void setDiffImage(BufferedImage diffImage) {
method setDiffSizeTrigger (line 48) | public void setDiffSizeTrigger(final int diffSizeTrigger) {
method getDiffImage (line 52) | public BufferedImage getDiffImage() {
method getColorModel (line 56) | private IndexColorModel getColorModel() {
method getColorMap (line 60) | private byte[] getColorMap() {
method getTransparentDiffImage (line 72) | protected BufferedImage getTransparentDiffImage(BufferedImage diffImag...
FILE: src/main/java/pazone/ashot/comparison/ImageDiff.java
class ImageDiff (line 9) | public class ImageDiff {
method ImageDiff (line 16) | public ImageDiff(DiffMarkupPolicy diffMarkupPolicy) {
method ImageDiff (line 20) | private ImageDiff() {
method withDiffSizeTrigger (line 30) | public ImageDiff withDiffSizeTrigger(final int diffSizeTrigger) {
method getDiffImage (line 38) | public BufferedImage getDiffImage() {
method setDiffImage (line 46) | public void setDiffImage(BufferedImage image) {
method addDiffPoint (line 50) | public void addDiffPoint(int x, int y) {
method getMarkedImage (line 60) | public BufferedImage getMarkedImage() {
method getTransparentMarkedImage (line 70) | public BufferedImage getTransparentMarkedImage() {
method hasDiff (line 79) | public boolean hasDiff() {
method getDiffSize (line 88) | public int getDiffSize() {
method equals (line 92) | @Override
method hashCode (line 101) | @Override
FILE: src/main/java/pazone/ashot/comparison/ImageDiffer.java
class ImageDiffer (line 19) | public class ImageDiffer {
method withIgnoredColor (line 28) | public ImageDiffer withIgnoredColor(final Color ignoreColor) {
method withColorDistortion (line 34) | public ImageDiffer withColorDistortion(int distortion) {
method withDiffMarkupPolicy (line 47) | public ImageDiffer withDiffMarkupPolicy(final DiffMarkupPolicy diffMar...
method makeDiff (line 52) | public ImageDiff makeDiff(Screenshot expected, Screenshot actual) {
method markDiffPoints (line 64) | protected void markDiffPoints(Screenshot expected, Screenshot actual, ...
method hasDiffInChannel (line 90) | private boolean hasDiffInChannel(Screenshot expected, Screenshot actua...
method makeDiff (line 98) | public ImageDiff makeDiff(BufferedImage expected, BufferedImage actual) {
method createDiffImage (line 102) | private BufferedImage createDiffImage(BufferedImage expectedImage, Buf...
method paintImage (line 110) | private void paintImage(BufferedImage image, BufferedImage diffImage) {
method isInsideBothImages (line 116) | private boolean isInsideBothImages(int i, int j, Coords expected, Coor...
class CoordsSet (line 120) | private static class CoordsSet {
method CoordsSet (line 126) | CoordsSet(Set<Coords> coordsSet) {
method contains (line 142) | private boolean contains(int i, int j) {
method inaccurateContains (line 146) | private boolean inaccurateContains(int i, int j) {
method accurateContains (line 150) | private boolean accurateContains(int i, int j) {
method intersection (line 154) | private static Set<Coords> intersection(Set<Coords> coordsPool1, Set...
method union (line 158) | private static Set<Coords> union(Set<Coords> coordsPool1, Set<Coords...
FILE: src/main/java/pazone/ashot/comparison/ImageMarkupPolicy.java
class ImageMarkupPolicy (line 10) | public class ImageMarkupPolicy extends DiffMarkupPolicy {
method setDiffImage (line 19) | @Override
method getMarkedImage (line 25) | @Override
method getTransparentMarkedImage (line 41) | @Override
method addDiffPoint (line 46) | @Override
method equals (line 56) | @Override
method hashCode (line 69) | @Override
method hasDiff (line 77) | @Override
method getDiffSize (line 82) | @Override
FILE: src/main/java/pazone/ashot/comparison/PointsMarkupPolicy.java
class PointsMarkupPolicy (line 15) | public class PointsMarkupPolicy extends DiffMarkupPolicy {
method getMarkedImage (line 21) | @Override
method getTransparentMarkedImage (line 30) | @Override
method addDiffPoint (line 39) | @Override
method equals (line 44) | @Override
method hashCode (line 54) | @Override
method hasDiff (line 59) | @Override
method getDiffSize (line 64) | @Override
method markDiffPoints (line 69) | protected void markDiffPoints(BufferedImage image) {
method getDeposedPoints (line 76) | private Set<Point> getDeposedPoints() {
method getReferenceCorner (line 83) | private Point getReferenceCorner() {
method deposeReference (line 96) | private Set<Point> deposeReference() {
FILE: src/main/java/pazone/ashot/coordinates/Coords.java
class Coords (line 15) | public class Coords extends Rectangle {
method intersection (line 17) | public static Set<Coords> intersection(Collection<Coords> coordsPool1,...
method setReferenceCoords (line 30) | public static Set<Coords> setReferenceCoords(Coords reference, Set<Coo...
method unity (line 43) | public static Coords unity(Collection<Coords> coordsCollection) {
method ofImage (line 51) | public static Coords ofImage(BufferedImage image) {
method Coords (line 55) | public Coords(Rectangle rectangle) {
method Coords (line 59) | public Coords(int x, int y, int width, int height) {
method Coords (line 63) | public Coords(int width, int height) {
method reduceBy (line 67) | public void reduceBy(int pixels) {
method union (line 77) | @SuppressWarnings("NullableProblems")
method intersection (line 83) | @SuppressWarnings("NullableProblems")
method toString (line 89) | @Override
FILE: src/main/java/pazone/ashot/coordinates/CoordsPreparationStrategy.java
class CoordsPreparationStrategy (line 16) | public abstract class CoordsPreparationStrategy {
method simple (line 18) | public static CoordsPreparationStrategy simple() {
method intersectingWith (line 27) | public static CoordsPreparationStrategy intersectingWith(final Screens...
method prepare (line 37) | public abstract Set<Coords> prepare(Collection<Coords> coordinates);
FILE: src/main/java/pazone/ashot/coordinates/CoordsProvider.java
class CoordsProvider (line 17) | public abstract class CoordsProvider implements Serializable {
method ofElement (line 19) | public abstract Coords ofElement(WebDriver driver, WebElement element);
method ofElements (line 21) | public Set<Coords> ofElements(WebDriver driver, Iterable<WebElement> e...
method ofElements (line 32) | @SuppressWarnings("UnusedDeclaration")
method locatedBy (line 37) | @SuppressWarnings("UnusedDeclaration")
FILE: src/main/java/pazone/ashot/coordinates/JqueryCoordsProvider.java
class JqueryCoordsProvider (line 10) | public class JqueryCoordsProvider extends CoordsProvider {
method ofElement (line 12) | @Override
FILE: src/main/java/pazone/ashot/coordinates/WebDriverCoordsProvider.java
class WebDriverCoordsProvider (line 11) | public class WebDriverCoordsProvider extends CoordsProvider {
method ofElement (line 12) | @Override
FILE: src/main/java/pazone/ashot/cropper/DefaultCropper.java
class DefaultCropper (line 16) | public class DefaultCropper extends ImageCropper {
method cropScreenshot (line 18) | @Override
method createCropArea (line 45) | protected Coords createCropArea(Set<Coords> coordsToCompare) {
FILE: src/main/java/pazone/ashot/cropper/ImageCropper.java
class ImageCropper (line 14) | public abstract class ImageCropper implements Serializable {
method crop (line 16) | public Screenshot crop(BufferedImage image, Set<Coords> cropArea) {
method cropScreenshot (line 22) | protected abstract Screenshot cropScreenshot(BufferedImage image, Set<...
FILE: src/main/java/pazone/ashot/cropper/indent/BlurFilter.java
class BlurFilter (line 12) | public class BlurFilter implements IndentFilter {
method apply (line 13) | @Override
FILE: src/main/java/pazone/ashot/cropper/indent/IndentCropper.java
class IndentCropper (line 22) | public class IndentCropper extends DefaultCropper {
method IndentCropper (line 30) | public IndentCropper(final int indent) {
method IndentCropper (line 34) | public IndentCropper() {
method cropScreenshot (line 38) | @Override
method applyIndentMask (line 52) | protected Coords applyIndentMask(Coords origin, Coords mask) {
method createIndentMask (line 61) | protected Coords createIndentMask(Coords originCoords, BufferedImage i...
method createNotFilteringAreas (line 70) | protected List<NoFilteringArea> createNotFilteringAreas(Screenshot scr...
method pasteAreasToCompare (line 80) | protected void pasteAreasToCompare(BufferedImage filtered, List<NoFilt...
method addIndentFilter (line 92) | public IndentCropper addIndentFilter(IndentFilter filter) {
method applyFilters (line 97) | protected BufferedImage applyFilters(BufferedImage image) {
class NoFilteringArea (line 104) | private static final class NoFilteringArea {
method NoFilteringArea (line 108) | private NoFilteringArea(BufferedImage origin, Coords noFilterCoords) {
method getSubimage (line 113) | public BufferedImage getSubimage() {
method getCoords (line 117) | public Coords getCoords() {
FILE: src/main/java/pazone/ashot/cropper/indent/IndentFilerFactory.java
class IndentFilerFactory (line 7) | public final class IndentFilerFactory {
method IndentFilerFactory (line 9) | private IndentFilerFactory() {
method blur (line 14) | public static BlurFilter blur() {
method monochrome (line 18) | public static MonochromeFilter monochrome() {
FILE: src/main/java/pazone/ashot/cropper/indent/IndentFilter.java
type IndentFilter (line 10) | public interface IndentFilter extends Serializable {
method apply (line 12) | BufferedImage apply(BufferedImage image);
FILE: src/main/java/pazone/ashot/cropper/indent/MonochromeFilter.java
class MonochromeFilter (line 13) | public class MonochromeFilter implements IndentFilter {
method apply (line 14) | @Override
method darken (line 19) | private BufferedImage darken(BufferedImage image) {
FILE: src/main/java/pazone/ashot/cutter/CutStrategy.java
type CutStrategy (line 10) | public interface CutStrategy extends Serializable {
method getHeaderHeight (line 17) | int getHeaderHeight(WebDriver driver);
method getFooterHeight (line 24) | int getFooterHeight(WebDriver driver);
method getLeftBarWidth (line 32) | default int getLeftBarWidth(WebDriver driver) {
method getRightBarWidth (line 42) | default int getRightBarWidth(WebDriver driver) {
FILE: src/main/java/pazone/ashot/cutter/FixedCutStrategy.java
class FixedCutStrategy (line 10) | public class FixedCutStrategy implements CutStrategy {
method FixedCutStrategy (line 16) | public FixedCutStrategy(int headerToCut, int footerToCut) {
method FixedCutStrategy (line 20) | public FixedCutStrategy(int headerToCut, int footerToCut, int leftBarT...
method getHeaderHeight (line 27) | @Override
method getFooterHeight (line 32) | @Override
method getLeftBarWidth (line 37) | @Override
method getRightBarWidth (line 42) | @Override
FILE: src/main/java/pazone/ashot/cutter/VariableCutStrategy.java
class VariableCutStrategy (line 14) | public class VariableCutStrategy implements CutStrategy {
method VariableCutStrategy (line 33) | public VariableCutStrategy(int headerMin, int headerMax, int footerMin...
method VariableCutStrategy (line 41) | public VariableCutStrategy(int headerMin, int headerMax, int footerMax...
method VariableCutStrategy (line 45) | public VariableCutStrategy(int headerMin, int headerMax, int windowInn...
method getHeaderHeight (line 49) | @Override
method getFooterHeight (line 55) | @Override
method getCutHeight (line 63) | private int getCutHeight(JavascriptExecutor driver, int heightMin, int...
method getWindowInnerHeight (line 68) | private int getWindowInnerHeight(JavascriptExecutor driver) {
FILE: src/main/java/pazone/ashot/util/ImageBytesDiffer.java
class ImageBytesDiffer (line 11) | public final class ImageBytesDiffer {
method ImageBytesDiffer (line 13) | private ImageBytesDiffer() {
method areImagesEqual (line 17) | public static boolean areImagesEqual(Screenshot expected, Screenshot a...
method areImagesEqual (line 21) | public static boolean areImagesEqual(BufferedImage expected, BufferedI...
method areImagesBuffersEqual (line 28) | private static boolean areImagesBuffersEqual(DataBuffer expected, Data...
method areImagesBytesEqual (line 35) | private static boolean areImagesBytesEqual(DataBuffer expected, DataBu...
FILE: src/main/java/pazone/ashot/util/ImageTool.java
class ImageTool (line 21) | public final class ImageTool {
method ImageTool (line 23) | private ImageTool() {
method subImage (line 27) | public static BufferedImage subImage(BufferedImage origin, Coords crop) {
method spreadCoordsInsideImage (line 32) | public static Coords spreadCoordsInsideImage(Coords coordinates, int i...
method rgbCompare (line 40) | public static boolean rgbCompare(int rgb1, int rgb2, int inaccuracy) {
method equalImage (line 55) | public static Matcher<BufferedImage> equalImage(final BufferedImage se...
method toByteArray (line 80) | @SuppressWarnings("UnusedDeclaration")
method toByteArray (line 85) | public static byte[] toByteArray(BufferedImage image) throws IOExcepti...
method toBufferedImage (line 92) | public static BufferedImage toBufferedImage(Image img) {
method toBufferedImage (line 108) | public static BufferedImage toBufferedImage(byte[] imageBytes) throws ...
FILE: src/main/java/pazone/ashot/util/InnerScript.java
class InnerScript (line 15) | public final class InnerScript {
method InnerScript (line 17) | private InnerScript() {
method execute (line 21) | public static <T> T execute(String path, WebDriver driver, Object... a...
FILE: src/main/java/pazone/ashot/util/JsCoords.java
class JsCoords (line 15) | public final class JsCoords {
method JsCoords (line 19) | private JsCoords() {
method findCoordsWithJquery (line 23) | public static Coords findCoordsWithJquery(WebDriver driver, WebElement...
FILE: src/main/resources/js/coords-single.js
function Coords (line 1) | function Coords(el) {
FILE: src/test/java/pazone/ashot/CdpShootingStrategyTest.java
class CdpShootingStrategyTest (line 25) | @ExtendWith(MockitoExtension.class)
method testPageScreenshot (line 32) | @Test
method testElementScreenshot (line 44) | @Test
method testUnsupportedCdp (line 59) | @Test
FILE: src/test/java/pazone/ashot/CroppersTest.java
class CroppersTest (line 24) | class CroppersTest {
method outsideCropperData (line 29) | static Stream<Arguments> outsideCropperData() {
method testElementOutsideImageCropper (line 36) | @ParameterizedTest
method testElementInsideImageIndentCropperWithFilter (line 43) | @Test
FILE: src/test/java/pazone/ashot/CuttingDecoratorTest.java
class CuttingDecoratorTest (line 14) | class CuttingDecoratorTest {
method shouldCutFromAllTheSides (line 17) | @Test
method shouldCutFromAllTheSidesUsingFixedCutStrategy (line 22) | @Test
method shouldCutOnlyFooterAndHeader (line 28) | @Test
method testCuttingFromAllTheSides (line 33) | private void testCuttingFromAllTheSides(UnaryOperator<CuttingDecorator...
FILE: src/test/java/pazone/ashot/DiffMarkupPolicyTest.java
class DiffMarkupPolicyTest (line 25) | class DiffMarkupPolicyTest {
method data (line 27) | private static Stream<Arguments> data() {
method initDiffMarkupPolicies (line 34) | private void initDiffMarkupPolicies(DiffMarkupPolicy diffMarkupPolicyA...
method testEquality (line 39) | @ParameterizedTest
method testNotEquality (line 49) | @ParameterizedTest
method testNotEqualityByNumber (line 59) | @ParameterizedTest
method getDiffPointsA (line 70) | private Set<Point> getDiffPointsA() {
method getDiffPointsB (line 81) | private Set<Point> getDiffPointsB() {
method addDiffPoints (line 92) | private void addDiffPoints(Set<Point> points, DiffMarkupPolicy diffMar...
FILE: src/test/java/pazone/ashot/DifferTest.java
class DifferTest (line 34) | class DifferTest {
method data (line 41) | private static Stream<DiffMarkupPolicy> data() {
method createImageDiffer (line 45) | private ImageDiffer createImageDiffer(DiffMarkupPolicy diffMarkupPolic...
method testSameSizeDiff (line 51) | @ParameterizedTest
method testDifferentSizeDiff (line 64) | @ParameterizedTest
method testSetDiffColor (line 71) | @ParameterizedTest
method testEqualImagesDiff (line 80) | @ParameterizedTest
method dataWithIgnoredColorDiff (line 87) | static Stream<Arguments> dataWithIgnoredColorDiff() {
method testDiffImagesWithIgnoredColorDiff (line 95) | @ParameterizedTest
method testIgnoredCoordsSame (line 103) | @ParameterizedTest
method testIgnoredCoordsNotSame (line 112) | @ParameterizedTest
method testCoordsToCompareAndIgnoredCombine (line 121) | @ParameterizedTest
method testDiffSize (line 132) | @ParameterizedTest
method createScreenshotWithIgnoredAreas (line 149) | private Screenshot createScreenshotWithIgnoredAreas(BufferedImage imag...
FILE: src/test/java/pazone/ashot/ImageBytesDifferTest.java
class ImageBytesDifferTest (line 18) | class ImageBytesDifferTest {
method testDifferentImages (line 20) | @TestFactory
method testDifferentImages (line 29) | void testDifferentImages(String path) {
method testEqualImages (line 33) | @Test
FILE: src/test/java/pazone/ashot/RotatingDecoratorTest.java
class RotatingDecoratorTest (line 23) | @ExtendWith(MockitoExtension.class)
method testRotating (line 29) | @Test
FILE: src/test/java/pazone/ashot/ScalingDecoratorTest.java
class ScalingDecoratorTest (line 25) | @ExtendWith(MockitoExtension.class)
method testDpr (line 31) | @CsvSource({
FILE: src/test/java/pazone/ashot/SerializeScreenshotTest.java
class SerializeScreenshotTest (line 28) | class SerializeScreenshotTest {
method ignoredAreas (line 30) | static Stream<Set<Coords>> ignoredAreas() {
method testSerialization (line 37) | @ParameterizedTest
FILE: src/test/java/pazone/ashot/VariableCutStrategyTest.java
class VariableCutStrategyTest (line 21) | @ExtendWith(MockitoExtension.class)
method testGetBrowserHeaderHeight (line 34) | @ParameterizedTest
method testGetBrowserHeaderHeightWithInvalidViewportHeight (line 45) | @ParameterizedTest
method mockViewportInnerHeight (line 55) | private void mockViewportInnerHeight(Object viewportHeight) {
FILE: src/test/java/pazone/ashot/VerticalPastingShootingStrategyTest.java
class VerticalPastingShootingStrategyTest (line 36) | @ExtendWith(MockitoExtension.class)
method timesData (line 56) | static Stream<Arguments> timesData() {
method testTimes (line 65) | @ParameterizedTest
method testCoordsShiftWithDefaultIndent (line 74) | @Test
method testScreenshotFullPage (line 82) | @Test
method getImageAsBytes (line 89) | private byte[] getImageAsBytes() throws IOException {
method mockScreenshotting (line 95) | private void mockScreenshotting() throws IOException {
method givenCoordsWithHeight (line 99) | private void givenCoordsWithHeight(int height) {
method whenTakingScreenshot (line 103) | private void whenTakingScreenshot() throws IOException {
method whenTakingScreenshot (line 108) | private void whenTakingScreenshot(Coords coords) throws IOException {
method whenPreparingCoords (line 113) | private void whenPreparingCoords(Set<Coords> coords) {
method thenCoordsShifted (line 117) | private void thenCoordsShifted() {
method thenScreenshotIsHeight (line 122) | private void thenScreenshotIsHeight(int shotHeight) {
method thenShootTimes (line 126) | private void thenShootTimes(int times) {
class MockVerticalPastingShootingDecorator (line 131) | static class MockVerticalPastingShootingDecorator extends ViewportPast...
method MockVerticalPastingShootingDecorator (line 133) | MockVerticalPastingShootingDecorator(ShootingStrategy strategy) {
method getPageDimensions (line 137) | @Override
method getCurrentScrollY (line 142) | @Override
FILE: src/test/java/pazone/ashot/coordinates/WebDriverCoordsProviderTest.java
class WebDriverCoordsProviderTest (line 13) | class WebDriverCoordsProviderTest {
method testGetCoordinatesOfElement (line 16) | @Test
FILE: src/test/java/pazone/ashot/util/ImageToolTest.java
class ImageToolTest (line 7) | class ImageToolTest {
method shouldConvertImageToBytesAndViceVersa (line 8) | @Test
FILE: src/test/java/pazone/ashot/util/TestImageUtils.java
class TestImageUtils (line 10) | public final class TestImageUtils {
method TestImageUtils (line 16) | private TestImageUtils() {
method loadImage (line 19) | public static BufferedImage loadImage(String path) {
method assertImageEquals (line 27) | public static void assertImageEquals(BufferedImage actualImage, String...
method assertImageEquals (line 31) | public static void assertImageEquals(BufferedImage actualImage, Buffer...
Condensed preview — 68 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (172K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 245,
"preview": "version: 2\nupdates:\n- package-ecosystem: maven\n directory: \"/\"\n schedule:\n interval: weekly\n open-pull-requests-li"
},
{
"path": ".github/workflows/ci.yml",
"chars": 454,
"preview": "name: CI\n\non:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\njobs:\n build:\n runs-"
},
{
"path": ".github/workflows/release.yml",
"chars": 890,
"preview": "name: Release and Deploy\n\non:\n workflow_dispatch\n\njobs:\n release:\n runs-on: ubuntu-latest\n\n steps:\n - name:"
},
{
"path": ".github/workflows/snapshot.yml",
"chars": 747,
"preview": "name: Deploy Snapshot\n\non:\n push:\n branches:\n - master\n\njobs:\n deploy:\n runs-on: ubuntu-latest\n\n steps:\n"
},
{
"path": ".gitignore",
"chars": 26,
"preview": "target\n.git \n.idea\n*.iml\n\n"
},
{
"path": ".mvn/wrapper/maven-wrapper.properties",
"chars": 1060,
"preview": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements. See the NOTICE f"
},
{
"path": "LICENCE",
"chars": 542,
"preview": "Copyright 2014 YANDEX\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except "
},
{
"path": "README.md",
"chars": 5523,
"preview": "aShot\n=====\n\n\n[](https://github.com/pazone/ashot/rel"
},
{
"path": "config/checkstyle/checkstyle.xml",
"chars": 6692,
"preview": "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC\n \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n "
},
{
"path": "mvnw",
"chars": 11172,
"preview": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Softwa"
},
{
"path": "mvnw.cmd",
"chars": 7903,
"preview": "@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software "
},
{
"path": "pom.xml",
"chars": 7571,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
},
{
"path": "src/main/java/pazone/ashot/AShot.java",
"chars": 5717,
"preview": "package pazone.ashot;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.W"
},
{
"path": "src/main/java/pazone/ashot/CdpShootingStrategy.java",
"chars": 2255,
"preview": "package pazone.ashot;\n\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.io.UncheckedIOExcept"
},
{
"path": "src/main/java/pazone/ashot/CuttingDecorator.java",
"chars": 3008,
"preview": "package pazone.ashot;\n\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.cutter.CutStrategy;\nimport pazone.ashot"
},
{
"path": "src/main/java/pazone/ashot/HandlingSickyElementsViewportPastingDecorator.java",
"chars": 1882,
"preview": "package pazone.ashot;\n\nimport java.awt.image.BufferedImage;\n\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.o"
},
{
"path": "src/main/java/pazone/ashot/ImageReadException.java",
"chars": 331,
"preview": "package pazone.ashot;\n\n/**\n * @author <a href=\"frolic@yandex-team.ru\">Vyacheslav Frolov</a>\n */\npublic class ImageReadEx"
},
{
"path": "src/main/java/pazone/ashot/InvalidViewportHeightException.java",
"chars": 367,
"preview": "package pazone.ashot;\n\n/**\n * @author <a href=\"frolic@yandex-team.ru\">Vyacheslav Frolov</a>\n */\npublic class InvalidView"
},
{
"path": "src/main/java/pazone/ashot/PageDimensions.java",
"chars": 603,
"preview": "package pazone.ashot;\n\npublic final class PageDimensions {\n private final int pageHeight;\n private final int viewp"
},
{
"path": "src/main/java/pazone/ashot/RotatingDecorator.java",
"chars": 1780,
"preview": "package pazone.ashot;\n\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot"
},
{
"path": "src/main/java/pazone/ashot/ScalingDecorator.java",
"chars": 2358,
"preview": "package pazone.ashot;\n\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt.Al"
},
{
"path": "src/main/java/pazone/ashot/Screenshot.java",
"chars": 2296,
"preview": "package pazone.ashot;\n\nimport pazone.ashot.coordinates.Coords;\n\nimport javax.imageio.ImageIO;\nimport java.awt.image.Buff"
},
{
"path": "src/main/java/pazone/ashot/ShootingDecorator.java",
"chars": 652,
"preview": "package pazone.ashot;\n\nimport pazone.ashot.coordinates.Coords;\n\nimport java.util.Set;\n\npublic abstract class ShootingDec"
},
{
"path": "src/main/java/pazone/ashot/ShootingStrategies.java",
"chars": 11811,
"preview": "package pazone.ashot;\n\nimport pazone.ashot.cutter.CutStrategy;\nimport pazone.ashot.cutter.FixedCutStrategy;\nimport pazon"
},
{
"path": "src/main/java/pazone/ashot/ShootingStrategy.java",
"chars": 1049,
"preview": "package pazone.ashot;\n\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt.im"
},
{
"path": "src/main/java/pazone/ashot/SimpleShootingStrategy.java",
"chars": 1410,
"preview": "package pazone.ashot;\n\nimport org.openqa.selenium.OutputType;\nimport org.openqa.selenium.TakesScreenshot;\nimport org.ope"
},
{
"path": "src/main/java/pazone/ashot/ViewportPastingDecorator.java",
"chars": 5039,
"preview": "package pazone.ashot;\n\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.WebDriver;\nimport pazon"
},
{
"path": "src/main/java/pazone/ashot/comparison/DiffMarkupPolicy.java",
"chars": 2223,
"preview": "package pazone.ashot.comparison;\n\nimport java.awt.Color;\nimport java.awt.image.BufferedImage;\nimport java.awt.image.Inde"
},
{
"path": "src/main/java/pazone/ashot/comparison/ImageDiff.java",
"chars": 2642,
"preview": "package pazone.ashot.comparison;\n\nimport java.awt.image.BufferedImage;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">P"
},
{
"path": "src/main/java/pazone/ashot/comparison/ImageDiffer.java",
"chars": 5921,
"preview": "package pazone.ashot.comparison;\n\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.coordinates.Coords;\nimport pazone."
},
{
"path": "src/main/java/pazone/ashot/comparison/ImageMarkupPolicy.java",
"chars": 2497,
"preview": "package pazone.ashot.comparison;\n\nimport java.awt.image.BufferedImage;\n\n/**\n * @author Rovniakov Viacheslav rovner@yande"
},
{
"path": "src/main/java/pazone/ashot/comparison/PointsMarkupPolicy.java",
"chars": 2874,
"preview": "package pazone.ashot.comparison;\n\nimport java.awt.Point;\nimport java.awt.image.BufferedImage;\nimport java.util.HashSet;\n"
},
{
"path": "src/main/java/pazone/ashot/coordinates/Coords.java",
"chars": 2610,
"preview": "package pazone.ashot.coordinates;\n\nimport com.google.gson.Gson;\n\nimport java.awt.Rectangle;\nimport java.awt.image.Buffer"
},
{
"path": "src/main/java/pazone/ashot/coordinates/CoordsPreparationStrategy.java",
"chars": 1188,
"preview": "package pazone.ashot.coordinates;\n\nimport pazone.ashot.Screenshot;\n\nimport java.util.Collection;\nimport java.util.HashSe"
},
{
"path": "src/main/java/pazone/ashot/coordinates/CoordsProvider.java",
"chars": 1290,
"preview": "package pazone.ashot.coordinates;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebDriver;\nimport org.openq"
},
{
"path": "src/main/java/pazone/ashot/coordinates/JqueryCoordsProvider.java",
"chars": 387,
"preview": "package pazone.ashot.coordinates;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport p"
},
{
"path": "src/main/java/pazone/ashot/coordinates/WebDriverCoordsProvider.java",
"chars": 774,
"preview": "package pazone.ashot.coordinates;\n\nimport org.openqa.selenium.Dimension;\nimport org.openqa.selenium.Point;\nimport org.op"
},
{
"path": "src/main/java/pazone/ashot/cropper/DefaultCropper.java",
"chars": 1560,
"preview": "package pazone.ashot.cropper;\n\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt."
},
{
"path": "src/main/java/pazone/ashot/cropper/ImageCropper.java",
"chars": 630,
"preview": "package pazone.ashot.cropper;\n\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt."
},
{
"path": "src/main/java/pazone/ashot/cropper/indent/BlurFilter.java",
"chars": 723,
"preview": "package pazone.ashot.cropper.indent;\n\nimport java.awt.image.BufferedImage;\nimport java.awt.image.BufferedImageOp;\nimport"
},
{
"path": "src/main/java/pazone/ashot/cropper/indent/IndentCropper.java",
"chars": 4320,
"preview": "package pazone.ashot.cropper.indent;\n\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.cropper.DefaultCropper;\nimport"
},
{
"path": "src/main/java/pazone/ashot/cropper/indent/IndentFilerFactory.java",
"chars": 417,
"preview": "package pazone.ashot.cropper.indent;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic final c"
},
{
"path": "src/main/java/pazone/ashot/cropper/indent/IndentFilter.java",
"chars": 276,
"preview": "package pazone.ashot.cropper.indent;\n\nimport java.awt.image.BufferedImage;\nimport java.io.Serializable;\n\n/**\n * @author "
},
{
"path": "src/main/java/pazone/ashot/cropper/indent/MonochromeFilter.java",
"chars": 520,
"preview": "package pazone.ashot.cropper.indent;\n\nimport pazone.ashot.util.ImageTool;\n\nimport java.awt.image.BufferedImage;\n\nimport "
},
{
"path": "src/main/java/pazone/ashot/cutter/CutStrategy.java",
"chars": 1173,
"preview": "package pazone.ashot.cutter;\n\nimport org.openqa.selenium.WebDriver;\n\nimport java.io.Serializable;\n\n/**\n * @author <a hre"
},
{
"path": "src/main/java/pazone/ashot/cutter/FixedCutStrategy.java",
"chars": 1204,
"preview": "package pazone.ashot.cutter;\n\nimport org.openqa.selenium.WebDriver;\n\n/**\n * Strategy for cutting header and footer of a "
},
{
"path": "src/main/java/pazone/ashot/cutter/VariableCutStrategy.java",
"chars": 3446,
"preview": "package pazone.ashot.cutter;\n\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.WebDriver;\nimpor"
},
{
"path": "src/main/java/pazone/ashot/util/ImageBytesDiffer.java",
"chars": 1642,
"preview": "package pazone.ashot.util;\n\nimport pazone.ashot.Screenshot;\n\nimport java.awt.image.BufferedImage;\nimport java.awt.image."
},
{
"path": "src/main/java/pazone/ashot/util/ImageTool.java",
"chars": 3843,
"preview": "package pazone.ashot.util;\n\nimport org.hamcrest.Description;\nimport org.hamcrest.Matcher;\nimport org.hamcrest.TypeSafeMa"
},
{
"path": "src/main/java/pazone/ashot/util/InnerScript.java",
"chars": 894,
"preview": "package pazone.ashot.util;\n\nimport java.nio.charset.StandardCharsets;\n\nimport org.apache.commons.io.IOUtils;\nimport org."
},
{
"path": "src/main/java/pazone/ashot/util/JsCoords.java",
"chars": 827,
"preview": "package pazone.ashot.util;\n\nimport java.util.List;\n\nimport com.google.gson.Gson;\n\nimport org.openqa.selenium.WebDriver;\n"
},
{
"path": "src/main/resources/js/coords-single.js",
"chars": 515,
"preview": "function Coords(el) {\n this.left = parseInt(el.offset().left);\n this.top = parseInt(el.offset().top);\n this.rig"
},
{
"path": "src/main/resources/js/page_dimensions.js",
"chars": 515,
"preview": "var body = document.body;\nvar documentElement = document.documentElement;\nvar pageHeight = Math.max(body.scrollHeight, b"
},
{
"path": "src/test/java/pazone/ashot/CdpShootingStrategyTest.java",
"chars": 2538,
"preview": "package pazone.ashot;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api."
},
{
"path": "src/test/java/pazone/ashot/CroppersTest.java",
"chars": 1914,
"preview": "package pazone.ashot;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org."
},
{
"path": "src/test/java/pazone/ashot/CuttingDecoratorTest.java",
"chars": 1462,
"preview": "package pazone.ashot;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static paz"
},
{
"path": "src/test/java/pazone/ashot/DiffMarkupPolicyTest.java",
"chars": 3760,
"preview": "package pazone.ashot;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Argum"
},
{
"path": "src/test/java/pazone/ashot/DifferTest.java",
"chars": 6866,
"preview": "package pazone.ashot;\n\nimport pazone.ashot.comparison.DiffMarkupPolicy;\nimport pazone.ashot.comparison.ImageDiff;\nimport"
},
{
"path": "src/test/java/pazone/ashot/ImageBytesDifferTest.java",
"chars": 1365,
"preview": "package pazone.ashot;\n\nimport org.junit.jupiter.api.DynamicTest;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jup"
},
{
"path": "src/test/java/pazone/ashot/RotatingDecoratorTest.java",
"chars": 1313,
"preview": "package pazone.ashot;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org."
},
{
"path": "src/test/java/pazone/ashot/ScalingDecoratorTest.java",
"chars": 1633,
"preview": "package pazone.ashot;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedT"
},
{
"path": "src/test/java/pazone/ashot/SerializeScreenshotTest.java",
"chars": 2069,
"preview": "package pazone.ashot;\n\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimpor"
},
{
"path": "src/test/java/pazone/ashot/VariableCutStrategyTest.java",
"chars": 2071,
"preview": "package pazone.ashot;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedT"
},
{
"path": "src/test/java/pazone/ashot/VerticalPastingShootingStrategyTest.java",
"chars": 5374,
"preview": "package pazone.ashot;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org."
},
{
"path": "src/test/java/pazone/ashot/coordinates/WebDriverCoordsProviderTest.java",
"chars": 1165,
"preview": "package pazone.ashot.coordinates;\n\nimport static org.junit.jupiter.api.Assertions.assertAll;\nimport static org.junit.jup"
},
{
"path": "src/test/java/pazone/ashot/util/ImageToolTest.java",
"chars": 395,
"preview": "package pazone.ashot.util;\n\nimport java.io.IOException;\n\nimport org.junit.jupiter.api.Test;\n\nclass ImageToolTest {\n @"
},
{
"path": "src/test/java/pazone/ashot/util/TestImageUtils.java",
"chars": 1080,
"preview": "package pazone.ashot.util;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.awt.image.BufferedImage;\ni"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the pazone/ashot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 68 files (157.5 KB), approximately 39.0k tokens, and a symbol index with 372 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.