[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: maven\n  directory: \"/\"\n  schedule:\n    interval: weekly\n  open-pull-requests-limit: 10\n- package-ecosystem: github-actions\n  directory: \"/\"\n  schedule:\n    interval: weekly\n  open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        java: [ 11, 17, 21, 25 ]\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Set up JDK\n      uses: actions/setup-java@v5\n      with:\n        distribution: 'zulu'\n        java-version: ${{ matrix.java }}\n        cache: 'maven'\n\n    - name: Tests\n      run: ./mvnw clean test\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release and Deploy\n\non:\n  workflow_dispatch\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}\n\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: '11'\n          server-id: github\n          server-username: MAVEN_USERNAME\n          server-password: MAVEN_PASSWORD\n\n      - name: Release\n        run: |\n          git config user.email \"pazonec@yandex.ru\"\n          git config --global user.name \"Pavel Zorin\"\n          ./mvnw --batch-mode -DskipTests -X release:clean release:prepare release:perform\n        env:\n          GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}\n          MAVEN_USERNAME: ${{ secrets.GITHUB_ACTOR }}\n          MAVEN_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/snapshot.yml",
    "content": "name: Deploy Snapshot\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v6\n\n    - name: Set up JDK\n      uses: actions/setup-java@v5\n      with:\n        distribution: 'zulu'\n        java-version: 11\n        cache: 'maven'\n        server-id: github\n        server-username: MAVEN_USERNAME\n        server-password: MAVEN_PASSWORD\n\n    - name: deploy snapshot (github packages)\n      if: github.ref == 'refs/heads/master'\n      env:\n        GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}\n        MAVEN_USERNAME: ${{ secrets.GITHUB_ACTOR }}\n        MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }}\n      run: ./mvnw --batch-mode -Dgpg.passphrase=$GPG_PASSPHRASE -DskipTests deploy"
  },
  {
    "path": ".gitignore",
    "content": "target\n.git \n.idea\n*.iml\n\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\nwrapperVersion=3.3.2\ndistributionType=bin\ndistributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip\nwrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar\n"
  },
  {
    "path": "LICENCE",
    "content": "Copyright 2014 YANDEX\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License."
  },
  {
    "path": "README.md",
    "content": "aShot\n=====\n\n\n[![release](https://img.shields.io/github/v/release/pazone/ashot.svg)](https://github.com/pazone/ashot/releases/latest) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/ru.yandex.qatools.ashot/ashot/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/ru.yandex.qatools.ashot/ashot)\n\n\nWebDriver Screenshot utility\n\n* Takes a screenshot of a WebElement on different platforms (i.e. desktop browsers, iOS Simulator Mobile Safari, Android Emulator Browser)\n* Decorates screenshots\n* Provides flexible screenshot comparison\n\n##### WebElement view\n\naShot takes a screenshot in three simple steps:\n* Capture a screenshot of the entire page\n* Find the element's size and position   \n* Crop the original screenshot\n\nAs a result, aShot provides an image of the WebElement\n![images snippet](/doc/img/images_intent_blur.png)\n\n#### Maven dependency\n```xml\n<dependency>\n    <groupId>ru.yandex.qatools.ashot</groupId>\n    <artifactId>ashot</artifactId>\n    <version>1.5.4</version>\n</dependency>\n``` \n\n##### Capturing the entire page\n\nDifferent 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. \n```java\nnew AShot()\n  .shootingStrategy(ShootingStrategies.viewportPasting(100))\n  .takeScreenshot(webDriver);\n```\n\nThere 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.\n```java\nCutStrategy cutting = new VariableCutStrategy(HEADER_IOS_8_MIN, HEADER_IOS_8_MAX, VIEWPORT_MIN_IOS_8_SIM);\nShootingStrategy rotating = new RotatingDecorator(cutting, ShootingStrategies.simple());\nShootingStrategy pasting = new ViewportPastingDecorator(rotating)\n    .withScrollTimeout(scrollTimeout);   \nnew AShot()\n  .shootingStrategy(pasting)\n  .takeScreenshot(webDriver);\n```\n\n##### Capturing the WebElement\n\nOne can take a screenshot of particular WebElement(s). Just specify the element(s).\n```java\nWebElement myWebElement = webDriver.findElement(By.cssSelector(\"#my_element\"));\nnew AShot()\n  .takeScreenshot(webDriver, myWebElement);\n```\n \nAs 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.\n```java\nnew AShot()\n  .coordsProvider(new WebDriverCoordsProvider()) //find coordinates with WebDriver API\n  .takeScreenshot(webDriver, myWebElement);\n```\nFeel free to implement your own CoordsProvider. Pull requests are welcome.\n\n##### Prettifying the screenshot\n\nSo, let's take a simple screenshot of the weather snippet at Yandex.com.\n\n```java\nnew AShot()\n  .takeScreenshot(webDriver, yandexWeatherElement);\n```\nHere is the result.\n![simple weather snippet](/doc/img/def_crop.png)\n \n`DefaultCropper` is used by default. Can we do better? Yes, we can.\n \n```java\nnew AShot()\n  .withCropper(new IndentCropper()           // set custom cropper with indentation\n                 .addIndentFilter(blur()))   // add filter for indented areas\n  .takeScreenshot(driver, yandexWeatherElement);\n```\n  \n![indent blur weather snippet](/doc/img/weather_indent_blur.png)\nThis screenshot provides more information about position relative other elements and blurs indent in order to focus on the WebElement.  \n  \n##### Screenshot comparison\n```.takeScreenshot()``` returns a ```Screenshot``` object which contains an image and data for comparison. One can ignore some WebElements from comparison.\n\n```java\nScreenshot myScreenshot = new AShot()\n  .addIgnoredElement(By.cssSelector(\"#weather .blinking_element\")) // ignored element(s)\n  .takeScreenshot(driver, yandexWeatherElement);\n```\n\nUse `ImageDiffer` to find a difference between two images.\n\n```java\nImageDiff diff = new ImageDiffer().makeDiff(myScreenshot, anotherScreenshot);\nBufferedImage diffImage = diff.getMarkedImage(); // comparison result with marked differences\n```\n\n##### Several elements comparison\n`(since 1.2)`  \nSometimes one needs to take a screenshot of several independent elements. In this case aShot computes complex comparison area.\n```java\nList<WebElement> elements = webDriver.findElements(By.cssSelector(\"#my_element, #popup\"));\nnew AShot()\n  .withCropper(new IndentCropper() \n                 .addIndentFilter(blur()))\n  .takeScreenshot(webDriver, elements);\n```\nHere is the result.\n![complex comparison area](/doc/img/complex_elements.png)\n\nOne can see only specified elements (the header and the popup) are focused and will be compared if needed.\n\n##### Ignoring of pixels with predefined color\nYou can set the color of pixels which should be excluded from comparison of screenshots.\n```java\nImageDiffer imageDifferWithIgnored = new ImageDiffer().withIgnoredColor(Color.MAGENTA);\nImageDiff diff = imageDifferWithIgnored.makeDiff(templateWithSomeMagentaPixels, actualScreenshot);\nassertFalse(diff.hasDiff());\n```\nAny pixels in template with color `MAGENTA` (255, 0, 255 in RGB) will be ignored during comparison.\n"
  },
  {
    "path": "config/checkstyle/checkstyle.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC\n          \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n          \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n\n<!--\n\n  Checkstyle configuration that checks the sun coding conventions from:\n\n    - the Java Language Specification at\n      https://docs.oracle.com/javase/specs/jls/se11/html/index.html\n\n    - the Sun Code Conventions at https://www.oracle.com/technetwork/java/codeconvtoc-136057.html\n\n    - the Javadoc guidelines at\n      https://www.oracle.com/technetwork/java/javase/documentation/index-137868.html\n\n    - the JDK Api documentation https://docs.oracle.com/en/java/javase/11/\n\n    - some best practices\n\n  Checkstyle is very configurable. Be sure to read the documentation at\n  https://checkstyle.org (or in your downloaded distribution).\n\n  Most Checks are configurable, be sure to consult the documentation.\n\n  To completely disable a check, just comment it out or delete it from the file.\n\n  Finally, it is worth reading the documentation.\n\n-->\n\n<module name=\"Checker\">\n    <!--\n        If you set the basedir property below, then all reported file\n        names will be relative to the specified directory. See\n        https://checkstyle.org/5.x/config.html#Checker\n\n        <property name=\"basedir\" value=\"${basedir}\"/>\n    -->\n    <property name=\"severity\" value=\"error\"/>\n\n    <property name=\"fileExtensions\" value=\"java, properties, xml\"/>\n\n    <!-- Excludes all 'module-info.java' files              -->\n    <!-- See https://checkstyle.org/config_filefilters.html -->\n    <module name=\"BeforeExecutionExclusionFileFilter\">\n        <property name=\"fileNamePattern\" value=\"module\\-info\\.java$\"/>\n    </module>\n\n    <!-- Checks whether files end with a new line.                        -->\n    <!-- See https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->\n    <module name=\"NewlineAtEndOfFile\"/>\n\n    <!-- Checks that property files contain the same keys.         -->\n    <!-- See https://checkstyle.org/config_misc.html#Translation -->\n    <module name=\"Translation\"/>\n\n    <!-- Checks for Size Violations.                    -->\n    <!-- See https://checkstyle.org/config_sizes.html -->\n    <module name=\"FileLength\"/>\n    <module name=\"LineLength\">\n      <property name=\"fileExtensions\" value=\"java\"/>\n      <property name=\"max\" value=\"120\"/>\n    </module>\n\n    <!-- Checks for whitespace                               -->\n    <!-- See https://checkstyle.org/config_whitespace.html -->\n    <module name=\"FileTabCharacter\"/>\n\n    <!-- Miscellaneous other checks.                   -->\n    <!-- See https://checkstyle.org/config_misc.html -->\n    <module name=\"RegexpSingleline\">\n       <property name=\"format\" value=\"\\s+$\"/>\n       <property name=\"minimum\" value=\"0\"/>\n       <property name=\"maximum\" value=\"0\"/>\n       <property name=\"message\" value=\"Line has trailing spaces.\"/>\n    </module>\n\n    <!-- Checks for Headers                                -->\n    <!-- See https://checkstyle.org/config_header.html   -->\n    <!-- <module name=\"Header\"> -->\n    <!--   <property name=\"headerFile\" value=\"${checkstyle.header.file}\"/> -->\n    <!--   <property name=\"fileExtensions\" value=\"java\"/> -->\n    <!-- </module> -->\n\n    <module name=\"TreeWalker\">\n\n        <!-- Checks for Javadoc comments.                     -->\n        <!-- See https://checkstyle.org/config_javadoc.html -->\n        <module name=\"InvalidJavadocPosition\"/>\n        <module name=\"JavadocMethod\"/>\n        <module name=\"JavadocType\"/>\n\n        <!-- Checks for Naming Conventions.                  -->\n        <!-- See https://checkstyle.org/config_naming.html -->\n        <module name=\"ConstantName\"/>\n        <module name=\"LocalFinalVariableName\"/>\n        <module name=\"LocalVariableName\"/>\n        <module name=\"MemberName\"/>\n        <module name=\"MethodName\"/>\n        <module name=\"PackageName\"/>\n        <module name=\"ParameterName\"/>\n        <module name=\"StaticVariableName\"/>\n        <module name=\"TypeName\"/>\n\n        <!-- Checks for imports                              -->\n        <!-- See https://checkstyle.org/config_import.html -->\n        <module name=\"AvoidStarImport\"/>\n        <module name=\"IllegalImport\"/> <!-- defaults to sun.* packages -->\n        <module name=\"RedundantImport\"/>\n        <module name=\"UnusedImports\">\n            <property name=\"processJavadoc\" value=\"false\"/>\n        </module>\n\n        <!-- Checks for Size Violations.                    -->\n        <!-- See https://checkstyle.org/config_sizes.html -->\n        <module name=\"MethodLength\"/>\n        <module name=\"ParameterNumber\"/>\n\n        <!-- Checks for whitespace                               -->\n        <!-- See https://checkstyle.org/config_whitespace.html -->\n        <module name=\"EmptyForIteratorPad\"/>\n        <module name=\"GenericWhitespace\"/>\n        <module name=\"MethodParamPad\"/>\n        <module name=\"NoWhitespaceAfter\"/>\n        <module name=\"NoWhitespaceBefore\"/>\n        <module name=\"OperatorWrap\"/>\n        <module name=\"ParenPad\"/>\n        <module name=\"TypecastParenPad\"/>\n        <module name=\"WhitespaceAfter\"/>\n        <module name=\"WhitespaceAround\"/>\n\n        <!-- Modifier Checks                                    -->\n        <!-- See https://checkstyle.org/config_modifiers.html -->\n        <module name=\"ModifierOrder\"/>\n        <module name=\"RedundantModifier\"/>\n\n        <!-- Checks for blocks. You know, those {}'s         -->\n        <!-- See https://checkstyle.org/config_blocks.html -->\n        <module name=\"AvoidNestedBlocks\"/>\n        <module name=\"EmptyBlock\"/>\n        <module name=\"LeftCurly\"/>\n        <module name=\"NeedBraces\"/>\n        <module name=\"RightCurly\"/>\n\n        <!-- Checks for common coding problems               -->\n        <!-- See https://checkstyle.org/config_coding.html -->\n        <module name=\"EmptyStatement\"/>\n        <module name=\"EqualsHashCode\"/>\n        <module name=\"IllegalInstantiation\"/>\n        <module name=\"InnerAssignment\"/>\n        <module name=\"MissingSwitchDefault\"/>\n        <module name=\"MultipleVariableDeclarations\"/>\n        <module name=\"SimplifyBooleanExpression\"/>\n        <module name=\"SimplifyBooleanReturn\"/>\n\n        <!-- Checks for class design                         -->\n        <!-- See https://checkstyle.org/config_design.html -->\n        <module name=\"FinalClass\"/>\n        <module name=\"HideUtilityClassConstructor\"/>\n        <module name=\"InterfaceIsType\"/>\n\n        <!-- Miscellaneous other checks.                   -->\n        <!-- See https://checkstyle.org/config_misc.html -->\n        <module name=\"ArrayTypeStyle\"/>\n        <module name=\"TodoComment\"/>\n        <module name=\"UpperEll\"/>\n\n    </module>\n\n</module>\n"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Apache Maven Wrapper startup batch script, version 3.3.2\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ]; then\n\n  if [ -f /usr/local/etc/mavenrc ]; then\n    . /usr/local/etc/mavenrc\n  fi\n\n  if [ -f /etc/mavenrc ]; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ]; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false\ndarwin=false\nmingw=false\ncase \"$(uname)\" in\nCYGWIN*) cygwin=true ;;\nMINGW*) mingw=true ;;\nDarwin*)\n  darwin=true\n  # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n  # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n  if [ -z \"$JAVA_HOME\" ]; then\n    if [ -x \"/usr/libexec/java_home\" ]; then\n      JAVA_HOME=\"$(/usr/libexec/java_home)\"\n      export JAVA_HOME\n    else\n      JAVA_HOME=\"/Library/Java/Home\"\n      export JAVA_HOME\n    fi\n  fi\n  ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ]; then\n  if [ -r /etc/gentoo-release ]; then\n    JAVA_HOME=$(java-config --jre-home)\n  fi\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin; then\n  [ -n \"$JAVA_HOME\" ] \\\n    && JAVA_HOME=$(cygpath --unix \"$JAVA_HOME\")\n  [ -n \"$CLASSPATH\" ] \\\n    && CLASSPATH=$(cygpath --path --unix \"$CLASSPATH\")\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw; then\n  [ -n \"$JAVA_HOME\" ] && [ -d \"$JAVA_HOME\" ] \\\n    && JAVA_HOME=\"$(\n      cd \"$JAVA_HOME\" || (\n        echo \"cannot cd into $JAVA_HOME.\" >&2\n        exit 1\n      )\n      pwd\n    )\"\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"$(which javac)\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"$(expr \"$javaExecutable\" : '\\([^ ]*\\)')\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=$(which readlink)\n    if [ ! \"$(expr \"$readLink\" : '\\([^ ]*\\)')\" = \"no\" ]; then\n      if $darwin; then\n        javaHome=\"$(dirname \"$javaExecutable\")\"\n        javaExecutable=\"$(cd \"$javaHome\" && pwd -P)/javac\"\n      else\n        javaExecutable=\"$(readlink -f \"$javaExecutable\")\"\n      fi\n      javaHome=\"$(dirname \"$javaExecutable\")\"\n      javaHome=$(expr \"$javaHome\" : '\\(.*\\)/bin')\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ]; then\n  if [ -n \"$JAVA_HOME\" ]; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ]; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"$(\n      \\unset -f command 2>/dev/null\n      \\command -v java\n    )\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ]; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\" >&2\nfi\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n  if [ -z \"$1\" ]; then\n    echo \"Path not specified to find_maven_basedir\" >&2\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ]; do\n    if [ -d \"$wdir\"/.mvn ]; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=$(\n        cd \"$wdir/..\" || exit 1\n        pwd\n      )\n    fi\n    # end of workaround\n  done\n  printf '%s' \"$(\n    cd \"$basedir\" || exit 1\n    pwd\n  )\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    # Remove \\r in case we run on Windows within Git Bash\n    # and check out the repository with auto CRLF management\n    # enabled. Otherwise, we may read lines that are delimited with\n    # \\r\\n and produce $'-Xarg\\r' rather than -Xarg due to word\n    # splitting rules.\n    tr -s '\\r\\n' ' ' <\"$1\"\n  fi\n}\n\nlog() {\n  if [ \"$MVNW_VERBOSE\" = true ]; then\n    printf '%s\\n' \"$1\"\n  fi\n}\n\nBASE_DIR=$(find_maven_basedir \"$(dirname \"$0\")\")\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1\nfi\n\nMAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\nexport MAVEN_PROJECTBASEDIR\nlog \"$MAVEN_PROJECTBASEDIR\"\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nwrapperJarPath=\"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\"\nif [ -r \"$wrapperJarPath\" ]; then\n  log \"Found $wrapperJarPath\"\nelse\n  log \"Couldn't find $wrapperJarPath, downloading it ...\"\n\n  if [ -n \"$MVNW_REPOURL\" ]; then\n    wrapperUrl=\"$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar\"\n  else\n    wrapperUrl=\"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar\"\n  fi\n  while IFS=\"=\" read -r key value; do\n    # 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 '=' )\n    safeValue=$(echo \"$value\" | tr -d '\\r')\n    case \"$key\" in wrapperUrl)\n      wrapperUrl=\"$safeValue\"\n      break\n      ;;\n    esac\n  done <\"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties\"\n  log \"Downloading from: $wrapperUrl\"\n\n  if $cygwin; then\n    wrapperJarPath=$(cygpath --path --windows \"$wrapperJarPath\")\n  fi\n\n  if command -v wget >/dev/null; then\n    log \"Found wget ... using wget\"\n    [ \"$MVNW_VERBOSE\" = true ] && QUIET=\"\" || QUIET=\"--quiet\"\n    if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n      wget $QUIET \"$wrapperUrl\" -O \"$wrapperJarPath\" || rm -f \"$wrapperJarPath\"\n    else\n      wget $QUIET --http-user=\"$MVNW_USERNAME\" --http-password=\"$MVNW_PASSWORD\" \"$wrapperUrl\" -O \"$wrapperJarPath\" || rm -f \"$wrapperJarPath\"\n    fi\n  elif command -v curl >/dev/null; then\n    log \"Found curl ... using curl\"\n    [ \"$MVNW_VERBOSE\" = true ] && QUIET=\"\" || QUIET=\"--silent\"\n    if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n      curl $QUIET -o \"$wrapperJarPath\" \"$wrapperUrl\" -f -L || rm -f \"$wrapperJarPath\"\n    else\n      curl $QUIET --user \"$MVNW_USERNAME:$MVNW_PASSWORD\" -o \"$wrapperJarPath\" \"$wrapperUrl\" -f -L || rm -f \"$wrapperJarPath\"\n    fi\n  else\n    log \"Falling back to using Java to download\"\n    javaSource=\"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n    javaClass=\"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class\"\n    # For Cygwin, switch paths to Windows format before running javac\n    if $cygwin; then\n      javaSource=$(cygpath --path --windows \"$javaSource\")\n      javaClass=$(cygpath --path --windows \"$javaClass\")\n    fi\n    if [ -e \"$javaSource\" ]; then\n      if [ ! -e \"$javaClass\" ]; then\n        log \" - Compiling MavenWrapperDownloader.java ...\"\n        (\"$JAVA_HOME/bin/javac\" \"$javaSource\")\n      fi\n      if [ -e \"$javaClass\" ]; then\n        log \" - Running MavenWrapperDownloader.java ...\"\n        (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$wrapperUrl\" \"$wrapperJarPath\") || rm -f \"$wrapperJarPath\"\n      fi\n    fi\n  fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\n# If specified, validate the SHA-256 sum of the Maven wrapper jar file\nwrapperSha256Sum=\"\"\nwhile IFS=\"=\" read -r key value; do\n  case \"$key\" in wrapperSha256Sum)\n    wrapperSha256Sum=$value\n    break\n    ;;\n  esac\ndone <\"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties\"\nif [ -n \"$wrapperSha256Sum\" ]; then\n  wrapperSha256Result=false\n  if command -v sha256sum >/dev/null; then\n    if echo \"$wrapperSha256Sum  $wrapperJarPath\" | sha256sum -c >/dev/null 2>&1; then\n      wrapperSha256Result=true\n    fi\n  elif command -v shasum >/dev/null; then\n    if echo \"$wrapperSha256Sum  $wrapperJarPath\" | shasum -a 256 -c >/dev/null 2>&1; then\n      wrapperSha256Result=true\n    fi\n  else\n    echo \"Checksum validation was requested but neither 'sha256sum' or 'shasum' are available.\" >&2\n    echo \"Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties.\" >&2\n    exit 1\n  fi\n  if [ $wrapperSha256Result = false ]; then\n    echo \"Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.\" >&2\n    echo \"Investigate or delete $wrapperJarPath to attempt a clean download.\" >&2\n    echo \"If you updated your Maven version, you need to update the specified wrapperSha256Sum property.\" >&2\n    exit 1\n  fi\nfi\n\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$JAVA_HOME\" ] \\\n    && JAVA_HOME=$(cygpath --path --windows \"$JAVA_HOME\")\n  [ -n \"$CLASSPATH\" ] \\\n    && CLASSPATH=$(cygpath --path --windows \"$CLASSPATH\")\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] \\\n    && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\")\nfi\n\n# Provide a \"standardized\" way to retrieve the CLI args that will\n# work with both Windows and non-Windows executions.\nMAVEN_CMD_LINE_ARGS=\"$MAVEN_CONFIG $*\"\nexport MAVEN_CMD_LINE_ARGS\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\n# shellcheck disable=SC2086 # safe args\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  $MAVEN_DEBUG_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software Foundation (ASF) under one\r\n@REM or more contributor license agreements.  See the NOTICE file\r\n@REM distributed with this work for additional information\r\n@REM regarding copyright ownership.  The ASF licenses this file\r\n@REM to you under the Apache License, Version 2.0 (the\r\n@REM \"License\"); you may not use this file except in compliance\r\n@REM with the License.  You may obtain a copy of the License at\r\n@REM\r\n@REM    http://www.apache.org/licenses/LICENSE-2.0\r\n@REM\r\n@REM Unless required by applicable law or agreed to in writing,\r\n@REM software distributed under the License is distributed on an\r\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n@REM KIND, either express or implied.  See the License for the\r\n@REM specific language governing permissions and limitations\r\n@REM under the License.\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM ----------------------------------------------------------------------------\r\n@REM Apache Maven Wrapper startup batch script, version 3.3.2\r\n@REM\r\n@REM Required ENV vars:\r\n@REM JAVA_HOME - location of a JDK home dir\r\n@REM\r\n@REM Optional ENV vars\r\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\r\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending\r\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\r\n@REM     e.g. to debug Maven itself, use\r\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\r\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\r\n@echo off\r\n@REM set title of command window\r\ntitle %0\r\n@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'\r\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\r\n\r\n@REM set %HOME% to equivalent of $HOME\r\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\r\n\r\n@REM Execute a user defined script before this one\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\r\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%USERPROFILE%\\mavenrc_pre.bat\" call \"%USERPROFILE%\\mavenrc_pre.bat\" %*\r\nif exist \"%USERPROFILE%\\mavenrc_pre.cmd\" call \"%USERPROFILE%\\mavenrc_pre.cmd\" %*\r\n:skipRcPre\r\n\r\n@setlocal\r\n\r\nset ERROR_CODE=0\r\n\r\n@REM To isolate internal variables from possible post scripts, we use another setlocal\r\n@setlocal\r\n\r\n@REM ==== START VALIDATION ====\r\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\r\n\r\necho. >&2\r\necho Error: JAVA_HOME not found in your environment. >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho. >&2\r\ngoto error\r\n\r\n:OkJHome\r\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\r\n\r\necho. >&2\r\necho Error: JAVA_HOME is set to an invalid directory. >&2\r\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho. >&2\r\ngoto error\r\n\r\n@REM ==== END VALIDATION ====\r\n\r\n:init\r\n\r\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\r\n@REM Fallback to current working directory if not found.\r\n\r\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\r\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\r\n\r\nset EXEC_DIR=%CD%\r\nset WDIR=%EXEC_DIR%\r\n:findBaseDir\r\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\r\ncd ..\r\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\r\nset WDIR=%CD%\r\ngoto findBaseDir\r\n\r\n:baseDirFound\r\nset MAVEN_PROJECTBASEDIR=%WDIR%\r\ncd \"%EXEC_DIR%\"\r\ngoto endDetectBaseDir\r\n\r\n:baseDirNotFound\r\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\r\ncd \"%EXEC_DIR%\"\r\n\r\n:endDetectBaseDir\r\n\r\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\r\n\r\n@setlocal EnableExtensions EnableDelayedExpansion\r\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\r\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\r\n\r\n:endReadAdditionalConfig\r\n\r\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\r\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\r\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\r\n\r\nset WRAPPER_URL=\"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar\"\r\n\r\nFOR /F \"usebackq tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\r\n    IF \"%%A\"==\"wrapperUrl\" SET WRAPPER_URL=%%B\r\n)\r\n\r\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\r\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\r\nif exist %WRAPPER_JAR% (\r\n    if \"%MVNW_VERBOSE%\" == \"true\" (\r\n        echo Found %WRAPPER_JAR%\r\n    )\r\n) else (\r\n    if not \"%MVNW_REPOURL%\" == \"\" (\r\n        SET WRAPPER_URL=\"%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar\"\r\n    )\r\n    if \"%MVNW_VERBOSE%\" == \"true\" (\r\n        echo Couldn't find %WRAPPER_JAR%, downloading it ...\r\n        echo Downloading from: %WRAPPER_URL%\r\n    )\r\n\r\n    powershell -Command \"&{\"^\r\n\t\t\"$webclient = new-object System.Net.WebClient;\"^\r\n\t\t\"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {\"^\r\n\t\t\"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');\"^\r\n\t\t\"}\"^\r\n\t\t\"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')\"^\r\n\t\t\"}\"\r\n    if \"%MVNW_VERBOSE%\" == \"true\" (\r\n        echo Finished downloading %WRAPPER_JAR%\r\n    )\r\n)\r\n@REM End of extension\r\n\r\n@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file\r\nSET WRAPPER_SHA_256_SUM=\"\"\r\nFOR /F \"usebackq tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\r\n    IF \"%%A\"==\"wrapperSha256Sum\" SET WRAPPER_SHA_256_SUM=%%B\r\n)\r\nIF NOT %WRAPPER_SHA_256_SUM%==\"\" (\r\n    powershell -Command \"&{\"^\r\n       \"Import-Module $PSHOME\\Modules\\Microsoft.PowerShell.Utility -Function Get-FileHash;\"^\r\n       \"$hash = (Get-FileHash \\\"%WRAPPER_JAR%\\\" -Algorithm SHA256).Hash.ToLower();\"^\r\n       \"If('%WRAPPER_SHA_256_SUM%' -ne $hash){\"^\r\n       \"  Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';\"^\r\n       \"  Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';\"^\r\n       \"  Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';\"^\r\n       \"  exit 1;\"^\r\n       \"}\"^\r\n       \"}\"\r\n    if ERRORLEVEL 1 goto error\r\n)\r\n\r\n@REM Provide a \"standardized\" way to retrieve the CLI args that will\r\n@REM work with both Windows and non-Windows executions.\r\nset MAVEN_CMD_LINE_ARGS=%*\r\n\r\n%MAVEN_JAVA_EXE% ^\r\n  %JVM_CONFIG_MAVEN_PROPS% ^\r\n  %MAVEN_OPTS% ^\r\n  %MAVEN_DEBUG_OPTS% ^\r\n  -classpath %WRAPPER_JAR% ^\r\n  \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" ^\r\n  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\r\nif ERRORLEVEL 1 goto error\r\ngoto end\r\n\r\n:error\r\nset ERROR_CODE=1\r\n\r\n:end\r\n@endlocal & set ERROR_CODE=%ERROR_CODE%\r\n\r\nif not \"%MAVEN_SKIP_RC%\"==\"\" goto skipRcPost\r\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%USERPROFILE%\\mavenrc_post.bat\" call \"%USERPROFILE%\\mavenrc_post.bat\"\r\nif exist \"%USERPROFILE%\\mavenrc_post.cmd\" call \"%USERPROFILE%\\mavenrc_post.cmd\"\r\n:skipRcPost\r\n\r\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\r\nif \"%MAVEN_BATCH_PAUSE%\"==\"on\" pause\r\n\r\nif \"%MAVEN_TERMINATE_CMD%\"==\"on\" exit %ERROR_CODE%\r\n\r\ncmd /C exit /B %ERROR_CODE%\r\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<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\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>io.github.pazone</groupId>\n    <artifactId>ashot</artifactId>\n    <version>1.6.1-SNAPSHOT</version>\n\n    <licenses>\n        <license>\n            <name>The Apache Software License, Version 2.0</name>\n            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n            <distribution>repo</distribution>\n        </license>\n    </licenses>\n    <issueManagement>\n        <system>GitHub Issues</system>\n        <url>https://github.com/pazone/ashot/issues</url>\n    </issueManagement>\n\n    <scm>\n        <url>https://github.com/pazone/ashot</url>\n        <connection>scm:git:git@github.com:pazone/ashot.git</connection>\n        <developerConnection>scm:git:git@github.com:pazone/ashot.git</developerConnection>\n        <tag>HEAD</tag>\n    </scm>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <selenium.version>4.43.0</selenium.version>\n    </properties>\n\n    <name>AShot WebDriver Utility</name>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.seleniumhq.selenium</groupId>\n            <artifactId>selenium-remote-driver</artifactId>\n            <version>${selenium.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.seleniumhq.selenium</groupId>\n            <artifactId>selenium-chromium-driver</artifactId>\n            <version>${selenium.version}</version>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>commons-io</groupId>\n            <artifactId>commons-io</artifactId>\n            <version>2.21.0</version>\n        </dependency>\n        <dependency>\n            <groupId>com.google.code.gson</groupId>\n            <artifactId>gson</artifactId>\n            <version>2.13.2</version>\n        </dependency>\n        <dependency>\n            <groupId>org.hamcrest</groupId>\n            <artifactId>hamcrest</artifactId>\n            <version>3.0</version>\n        </dependency>\n\n        <!--test-->\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>5.14.3</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-junit-jupiter</artifactId>\n            <version>5.23.0</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <version>3.4.0</version>\n                <executions>\n                    <execution>\n                        <id>attach-sources</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.15.0</version>\n                <configuration>\n                    <source>11</source>\n                    <target>11</target>\n                    <forceJavacCompilerUse>true</forceJavacCompilerUse>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.5.5</version>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <version>3.12.0</version>\n                <configuration>\n                    <additionalOptions>\n                        <additionalOption>-Xdoclint:none</additionalOption>\n                    </additionalOptions>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-release-plugin</artifactId>\n                <version>3.3.1</version>\n                <configuration>\n                    <scmCommentPrefix>[ci skip]</scmCommentPrefix>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n    <distributionManagement>\n        <repository>\n            <id>github</id>\n            <url>https://maven.pkg.github.com/pazone/ashot</url>\n        </repository>\n    </distributionManagement>\n\n    <profiles>\n        <profile>\n            <id>checkstyle-checks</id>\n            <activation>\n                <jdk>[21,)</jdk>\n            </activation>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-checkstyle-plugin</artifactId>\n                        <version>3.6.0</version>\n                        <configuration>\n                            <configLocation>config/checkstyle/checkstyle.xml</configLocation>\n                            <consoleOutput>true</consoleOutput>\n                            <failsOnError>true</failsOnError>\n                            <includeTestSourceDirectory>true</includeTestSourceDirectory>\n                        </configuration>\n                        <executions>\n                            <execution>\n                                <id>validate</id>\n                                <phase>validate</phase>\n                                <goals>\n                                    <goal>check</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                        <dependencies>\n                            <dependency>\n                                <groupId>com.puppycrawl.tools</groupId>\n                                <artifactId>checkstyle</artifactId>\n                                <version>13.4.0</version>\n                            </dependency>\n                        </dependencies>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n        <profile>\n            <id>release-sign-artifacts</id>\n            <activation>\n                <property>\n                    <name>performSigning</name>\n                    <value>true</value>\n                </property>\n            </activation>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-gpg-plugin</artifactId>\n                        <version>3.2.8</version>\n                        <executions>\n                            <execution>\n                                <id>sign-artifacts</id>\n                                <phase>verify</phase>\n                                <goals>\n                                    <goal>sign</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n\n</project>\n"
  },
  {
    "path": "src/main/java/pazone/ashot/AShot.java",
    "content": "package pazone.ashot;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.coordinates.CoordsPreparationStrategy;\nimport pazone.ashot.coordinates.CoordsProvider;\nimport pazone.ashot.coordinates.WebDriverCoordsProvider;\nimport pazone.ashot.cropper.ImageCropper;\nimport pazone.ashot.cropper.DefaultCropper;\n\nimport java.awt.image.BufferedImage;\nimport java.io.Serializable;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport static java.util.Collections.singletonList;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic class AShot implements Serializable {\n    private CoordsProvider coordsProvider = new WebDriverCoordsProvider();\n    private ImageCropper cropper = new DefaultCropper();\n    private Set<By> ignoredLocators = new HashSet<>();\n    private Set<Coords> ignoredAreas = new HashSet<>();\n    private ShootingStrategy shootingStrategy = new SimpleShootingStrategy();\n\n    public AShot coordsProvider(final CoordsProvider coordsProvider) {\n        this.coordsProvider = coordsProvider;\n        return this;\n    }\n\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public AShot imageCropper(ImageCropper cropper) {\n        this.cropper = cropper;\n        return this;\n    }\n\n    /**\n     * Sets the list of locators to ignore during image comparison.\n     *\n     * @param ignoredElements list of By\n     * @return this\n     */\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public synchronized AShot ignoredElements(final Set<By> ignoredElements) {\n        this.ignoredLocators = ignoredElements;\n        return this;\n    }\n\n    /**\n     * Adds selector of ignored element.\n     *\n     * @param selector By\n     * @return this\n     */\n    public synchronized AShot addIgnoredElement(final By selector) {\n        this.ignoredLocators.add(selector);\n        return this;\n    }\n\n    /**\n     * Sets a collection of wittingly ignored coords.\n     * @param ignoredAreas Set of ignored areas\n     * @return aShot\n     */\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public synchronized AShot ignoredAreas(final Set<Coords> ignoredAreas) {\n        this.ignoredAreas = ignoredAreas;\n        return this;\n    }\n\n    /**\n     * Adds coordinated to set of wittingly ignored coords.\n     * @param area coords of wittingly ignored coords\n     * @return aShot;\n     */\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public synchronized AShot addIgnoredArea(Coords area) {\n        this.ignoredAreas.add(area);\n        return this;\n    }\n\n    /**\n     * Sets the policy of taking screenshot.\n     *\n     * @param strategy shooting strategy\n     * @return this\n     * @see ShootingStrategy\n     */\n    public AShot shootingStrategy(ShootingStrategy strategy) {\n        this.shootingStrategy = strategy;\n        return this;\n    }\n\n    /**\n     * Takes the screenshot of given elements\n     * If elements were not found screenshot of whole page will be returned\n     *\n     * @param driver WebDriver instance\n     * @param elements Web elements to screenshot\n     * @return Screenshot with cropped image and list of ignored areas on screenshot\n     * @throws RuntimeException when something goes wrong\n     * @see Screenshot\n     */\n    public Screenshot takeScreenshot(WebDriver driver, Collection<WebElement> elements) {\n        Set<Coords> elementCoords = coordsProvider.ofElements(driver, elements);\n        BufferedImage shot = shootingStrategy.getScreenshot(driver, elementCoords);\n        Screenshot screenshot = cropper.crop(shot, shootingStrategy.prepareCoords(elementCoords));\n        Set<Coords> ignoredAreas = compileIgnoredAreas(driver, CoordsPreparationStrategy.intersectingWith(screenshot));\n        screenshot.setIgnoredAreas(shootingStrategy.prepareCoords(ignoredAreas));\n        return screenshot;\n    }\n\n    /**\n     * Takes the screenshot of given element\n     *\n     * @param driver WebDriver instance\n     * @param element Web element to screenshot\n     * @return Screenshot with cropped image and list of ignored areas on screenshot\n     * @throws RuntimeException when something goes wrong\n     * @see Screenshot\n     */\n    public Screenshot takeScreenshot(WebDriver driver, WebElement element) {\n        return takeScreenshot(driver, singletonList(element));\n    }\n\n    /**\n     * Takes the screenshot of whole page\n     *\n     * @param driver WebDriver instance\n     * @return Screenshot with whole page image and list of ignored areas on screenshot\n     * @see Screenshot\n     */\n    public Screenshot takeScreenshot(WebDriver driver) {\n        Screenshot screenshot = new Screenshot(shootingStrategy.getScreenshot(driver));\n        screenshot.setIgnoredAreas(compileIgnoredAreas(driver, CoordsPreparationStrategy.simple()));\n        return screenshot;\n    }\n\n    protected synchronized Set<Coords> compileIgnoredAreas(WebDriver driver,\n            CoordsPreparationStrategy preparationStrategy) {\n        Set<Coords> ignoredCoords = new HashSet<>();\n        for (By ignoredLocator : ignoredLocators) {\n            List<WebElement> ignoredElements = driver.findElements(ignoredLocator);\n            if (!ignoredElements.isEmpty()) {\n                ignoredCoords.addAll(preparationStrategy.prepare(coordsProvider.ofElements(driver, ignoredElements)));\n            }\n        }\n        for (Coords ignoredArea : ignoredAreas) {\n            ignoredCoords.addAll(preparationStrategy.prepare(singletonList(ignoredArea)));\n        }\n        return ignoredCoords;\n    }\n\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public synchronized Set<By> getIgnoredLocators() {\n        return ignoredLocators;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/CdpShootingStrategy.java",
    "content": "package pazone.ashot;\n\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.openqa.selenium.OutputType;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.chromium.HasCdp;\n\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.util.ImageTool;\n\n/**\n * Gets a screenshot using\n * <a href=\"https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot\">\n * capture screenshot</a> function provided by Chrome DevTools protocol. {@link WebDriver} instance provided\n * to the class methods must be an instance of {@link HasCdp} and support Chrome DevTools protocol.\n */\npublic class CdpShootingStrategy implements ShootingStrategy {\n\n    private static final long serialVersionUID = -4371668803381640029L;\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver driver) {\n        return getScreenshot(driver, Set.of());\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver driver, Set<Coords> coords) {\n        if (!HasCdp.class.isAssignableFrom(driver.getClass())) {\n            throw new IllegalArgumentException(\"WebDriver instance must support Chrome DevTools protocol\");\n        }\n\n        Map<String, Object> args = new HashMap<>();\n        args.put(\"captureBeyondViewport\", true);\n\n        if (!coords.isEmpty()) {\n            Coords elementCoords = coords.iterator().next();\n            args.put(\"clip\", Map.of(\n                \"x\", elementCoords.x,\n                \"y\", elementCoords.y,\n                \"width\", elementCoords.width,\n                \"height\", elementCoords.height,\n                \"scale\", 1)\n            );\n        }\n\n        Map<String, Object> results = ((HasCdp) driver).executeCdpCommand(\"Page.captureScreenshot\", args);\n        String base64 = (String) results.get(\"data\");\n        byte[] bytes = OutputType.BYTES.convertFromBase64Png(base64);\n\n        try {\n            return ImageTool.toBufferedImage(bytes);\n        } catch (IOException thrown) {\n            throw new UncheckedIOException(thrown);\n        }\n    }\n\n    @Override\n    public Set<Coords> prepareCoords(Set<Coords> coordsSet) {\n        return coordsSet;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/CuttingDecorator.java",
    "content": "package pazone.ashot;\n\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.cutter.CutStrategy;\nimport pazone.ashot.cutter.FixedCutStrategy;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt.image.BufferedImage;\nimport java.util.Set;\n\n/**\n * Cuts browser's header/footer off from screenshot.\n *\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\npublic class CuttingDecorator extends ShootingDecorator {\n\n    private CutStrategy cutStrategy;\n\n    public CuttingDecorator(ShootingStrategy strategy) {\n        super(strategy);\n    }\n\n    /**\n     * Will use {@link FixedCutStrategy} to cut off header and footer.\n     * @param headerToCut - height of header in pixels\n     * @param footerToCut - height of footer in pixels\n     * @return Cutting decorator\n     */\n    public CuttingDecorator withCut(int headerToCut, int footerToCut) {\n        return withCutStrategy(new FixedCutStrategy(headerToCut, footerToCut));\n    }\n\n    /**\n     * Will use {@link FixedCutStrategy} to cut off header and footer.\n     * @param headerToCut - height of header in pixels\n     * @param footerToCut - height of footer in pixels\n     * @param leftBarToCut - width of left bar in pixels\n     * @param rightBarToCut - width of right bar in pixels\n     * @return Cutting decorator\n     */\n    public CuttingDecorator withCut(int headerToCut, int footerToCut, int leftBarToCut, int rightBarToCut) {\n        return withCutStrategy(new FixedCutStrategy(headerToCut, footerToCut, leftBarToCut, rightBarToCut));\n    }\n\n    /**\n     * Will use custom cut strategy, for example {@link pazone.ashot.cutter.VariableCutStrategy}.\n     * @param cutStrategy - strategy to get height of browser's header\n     * @return Cutting decorator\n     */\n    public CuttingDecorator withCutStrategy(CutStrategy cutStrategy) {\n        this.cutStrategy = cutStrategy;\n        return this;\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd) {\n        BufferedImage baseImage = getShootingStrategy().getScreenshot(wd);\n        int h = baseImage.getHeight();\n        int w = baseImage.getWidth();\n        final int headerToCut = getHeaderToCut(wd);\n        final int footerToCut = getFooterToCut(wd);\n        final int leftBarToCut = getLeftBarToCut(wd);\n        final int rightBarToCut = getRightBarToCut(wd);\n        return baseImage.getSubimage(leftBarToCut, headerToCut, w - leftBarToCut - rightBarToCut,\n            h - headerToCut - footerToCut);\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords) {\n        return getScreenshot(wd);\n    }\n\n    protected int getHeaderToCut(WebDriver wd) {\n        return cutStrategy.getHeaderHeight(wd);\n    }\n\n    protected int getFooterToCut(WebDriver wd) {\n        return cutStrategy.getFooterHeight(wd);\n    }\n\n    protected int getLeftBarToCut(WebDriver wd) {\n        return cutStrategy.getLeftBarWidth(wd);\n    }\n\n    protected int getRightBarToCut(WebDriver wd) {\n        return cutStrategy.getRightBarWidth(wd);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/HandlingSickyElementsViewportPastingDecorator.java",
    "content": "package pazone.ashot;\n\nimport java.awt.image.BufferedImage;\n\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.WebDriver;\n\npublic class HandlingSickyElementsViewportPastingDecorator extends ViewportPastingDecorator {\n\n    private final int stickyHeaderHeight;\n    private final int stickyFooterHeight;\n    private int currentChunkIndex;\n\n    public HandlingSickyElementsViewportPastingDecorator(ShootingStrategy strategy, int stickyHeaderHeight,\n            int stickyFooterHeight) {\n        super(strategy);\n        this.stickyHeaderHeight = stickyHeaderHeight;\n        this.stickyFooterHeight = stickyFooterHeight;\n    }\n\n    @Override\n    protected PageDimensions getPageDimensions(WebDriver driver) {\n        PageDimensions pageDimension = super.getPageDimensions(driver);\n        return new PageDimensions(pageDimension.getPageHeight(), pageDimension.getViewportWidth(),\n                pageDimension.getViewportHeight() - stickyHeaderHeight - stickyFooterHeight);\n    }\n\n    @Override\n    protected BufferedImage getChunk(WebDriver wd, int currentChunkIndex, int totalNumberOfChunks) {\n        this.currentChunkIndex = currentChunkIndex;\n        CuttingDecorator cuttingDecorator = new CuttingDecorator(getShootingStrategy());\n        if (currentChunkIndex == 0) {\n            cuttingDecorator.withCut(0, stickyFooterHeight);\n        } else if (currentChunkIndex == totalNumberOfChunks - 1) {\n            cuttingDecorator.withCut(stickyHeaderHeight, 0);\n        } else {\n            cuttingDecorator.withCut(stickyHeaderHeight, stickyFooterHeight);\n        }\n        return cuttingDecorator.getScreenshot(wd);\n    }\n\n    @Override\n    protected int getCurrentScrollY(JavascriptExecutor js) {\n        int currentScrollY = super.getCurrentScrollY(js);\n        return currentChunkIndex == 0 ? currentScrollY : currentScrollY + stickyHeaderHeight;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/ImageReadException.java",
    "content": "package pazone.ashot;\n\n/**\n * @author <a href=\"frolic@yandex-team.ru\">Vyacheslav Frolov</a>\n */\npublic class ImageReadException extends RuntimeException {\n\n    public ImageReadException(String message) {\n        super(message);\n    }\n\n    public ImageReadException(String message, Exception e) {\n        super(message, e);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/InvalidViewportHeightException.java",
    "content": "package pazone.ashot;\n\n/**\n * @author <a href=\"frolic@yandex-team.ru\">Vyacheslav Frolov</a>\n */\npublic class InvalidViewportHeightException extends RuntimeException {\n\n    public InvalidViewportHeightException(String message) {\n        super(message);\n    }\n\n    public InvalidViewportHeightException(String message, Exception e) {\n        super(message, e);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/PageDimensions.java",
    "content": "package pazone.ashot;\n\npublic final class PageDimensions {\n    private final int pageHeight;\n    private final int viewportWidth;\n    private final int viewportHeight;\n\n    public PageDimensions(int pageHeight, int viewportWidth, int viewportHeight) {\n        this.pageHeight = pageHeight;\n        this.viewportHeight = viewportHeight;\n        this.viewportWidth = viewportWidth;\n    }\n\n    public int getPageHeight() {\n        return pageHeight;\n    }\n\n    public int getViewportWidth() {\n        return viewportWidth;\n    }\n\n    public int getViewportHeight() {\n        return viewportHeight;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/RotatingDecorator.java",
    "content": "package pazone.ashot;\n\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.cutter.CutStrategy;\n\nimport java.awt.Graphics2D;\nimport java.awt.image.BufferedImage;\nimport java.util.Set;\n\nimport static java.awt.image.BufferedImage.TYPE_4BYTE_ABGR;\n\n/**\n * @author <a href=\"rovner@yandex-team.ru\">Rovniakov Viacheslav</a>\n */\n\npublic class RotatingDecorator implements ShootingStrategy {\n\n    private final CutStrategy cutStrategy;\n    private final ShootingStrategy shootingStrategy;\n\n    public RotatingDecorator(CutStrategy cutStrategy, ShootingStrategy shootingStrategy) {\n        this.cutStrategy = cutStrategy;\n        this.shootingStrategy = shootingStrategy;\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd) {\n        return rotate(shootingStrategy.getScreenshot(wd), wd);\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords) {\n        return getScreenshot(wd);\n    }\n\n    @Override\n    public Set<Coords> prepareCoords(Set<Coords> coordsSet) {\n        return coordsSet;\n    }\n\n    private BufferedImage rotate(BufferedImage baseImage, WebDriver wd) {\n        BufferedImage rotated = new BufferedImage(baseImage.getHeight(), baseImage.getWidth(), TYPE_4BYTE_ABGR);\n        Graphics2D graphics = rotated.createGraphics();\n        double theta = 3 * Math.PI / 2;\n        int origin = baseImage.getWidth() / 2;\n        graphics.rotate(theta, origin, origin);\n        graphics.drawImage(baseImage, null, 0, 0);\n        int rotatedHeight = rotated.getHeight();\n        int rotatedWidth = rotated.getWidth();\n        int headerToCut = cutStrategy.getHeaderHeight(wd);\n        return rotated.getSubimage(0, headerToCut, rotatedWidth, rotatedHeight - headerToCut);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/ScalingDecorator.java",
    "content": "package pazone.ashot;\n\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt.AlphaComposite;\nimport java.awt.Graphics2D;\nimport java.awt.RenderingHints;\nimport java.awt.image.BufferedImage;\nimport java.util.Set;\n\n/**\n * Will scale down image that was aquired by WebDriver.\n * Scaling is performed according to device pixel ratio (DPR)\n * Useful for browsers on portable devices with Retina displays.\n */\npublic class ScalingDecorator extends ShootingDecorator {\n\n    private static final Float STANDARD_DRP = 1F;\n    private Float dprX = STANDARD_DRP;\n    private Float dprY = STANDARD_DRP;\n\n    public ScalingDecorator(ShootingStrategy strategy) {\n        super(strategy);\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd) {\n        return scale(getShootingStrategy().getScreenshot(wd));\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords) {\n        return scale(getShootingStrategy().getScreenshot(wd, coords));\n    }\n\n    public ScalingDecorator withDprX(float dprX) {\n        this.dprX = dprX;\n        return this;\n    }\n\n    public ScalingDecorator withDprY(float dprY) {\n        this.dprY = dprY;\n        return this;\n    }\n\n    public ScalingDecorator withDpr(float dpr) {\n        return this\n                .withDprX(dpr)\n                .withDprY(dpr);\n    }\n\n    private BufferedImage scale(BufferedImage image) {\n        if (STANDARD_DRP.equals(dprY) && STANDARD_DRP.equals(dprX)) {\n            return image;\n        }\n        int scaledWidth = (int) (image.getWidth() / dprX);\n        int scaledHeight = (int) (image.getHeight() / dprY);\n\n        final BufferedImage bufferedImage = new BufferedImage(scaledWidth, scaledHeight, image.getType());\n        final Graphics2D graphics2D = bufferedImage.createGraphics();\n        graphics2D.setComposite(AlphaComposite.Src);\n        graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);\n        graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);\n        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\n        graphics2D.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);\n        graphics2D.dispose();\n        return bufferedImage;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/Screenshot.java",
    "content": "package pazone.ashot;\n\nimport pazone.ashot.coordinates.Coords;\n\nimport javax.imageio.ImageIO;\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.Serializable;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n * @author <a href=\"eoff@yandex-team.ru\">Maksim Mukosey</a>\n *\n * Result of screen capture.\n * Contains final processed image and all required information for image comparison.\n */\npublic class Screenshot implements Serializable {\n    private static final long serialVersionUID = 1241241256734156872L;\n\n    private transient BufferedImage image;\n    private Set<Coords> ignoredAreas = new HashSet<>();\n    private Set<Coords> coordsToCompare;\n\n    /**\n     * Coords, containing x and y shift from origin image coordinates system\n     * Actually it is coordinates of cropped area on origin image.\n     * Should be set if image is cropped.\n     */\n    private Coords originShift = new Coords(0, 0);\n\n    public BufferedImage getImage() {\n        return image;\n    }\n\n    public void setImage(BufferedImage image) {\n        this.image = image;\n    }\n\n    public Screenshot(BufferedImage image) {\n        this.image = image;\n        this.coordsToCompare = Collections.singleton(Coords.ofImage(image));\n    }\n\n    public Set<Coords> getCoordsToCompare() {\n        return coordsToCompare;\n    }\n\n    public void setCoordsToCompare(Set<Coords> coordsToCompare) {\n        this.coordsToCompare = coordsToCompare;\n    }\n\n    public Set<Coords> getIgnoredAreas() {\n        return ignoredAreas;\n    }\n\n    public void setIgnoredAreas(Set<Coords> ignoredAreas) {\n        this.ignoredAreas = ignoredAreas;\n    }\n\n    public Coords getOriginShift() {\n        return originShift;\n    }\n\n    public void setOriginShift(Coords originShift) {\n        this.originShift = originShift;\n    }\n\n    private void writeObject(ObjectOutputStream out) throws IOException {\n        out.defaultWriteObject();\n        ImageIO.write(image, \"png\", out);\n    }\n\n    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {\n        in.defaultReadObject();\n        image = ImageIO.read(in);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/ShootingDecorator.java",
    "content": "package pazone.ashot;\n\nimport pazone.ashot.coordinates.Coords;\n\nimport java.util.Set;\n\npublic abstract class ShootingDecorator implements ShootingStrategy {\n\n    private final ShootingStrategy shootingStrategy;\n\n    protected ShootingDecorator(ShootingStrategy shootingStrategy) {\n        this.shootingStrategy = shootingStrategy;\n    }\n\n    public ShootingStrategy getShootingStrategy() {\n        return shootingStrategy;\n    }\n\n    /**\n     * Default behavior is not to change coords, because by default coordinates are not changed\n     */\n    @Override\n    public Set<Coords> prepareCoords(Set<Coords> coordsSet) {\n        return coordsSet;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/ShootingStrategies.java",
    "content": "package pazone.ashot;\n\nimport pazone.ashot.cutter.CutStrategy;\nimport pazone.ashot.cutter.FixedCutStrategy;\nimport pazone.ashot.cutter.VariableCutStrategy;\n\n/**\n * Utility class for different shooting strategies.\n */\npublic final class ShootingStrategies {\n\n    private static final int SCROLL_TIMEOUT_IOS = 500;\n\n    private static final int HEADER_IOS_7 = 98;\n\n    private static final int HEADER_IOS_8_MIN = 41;\n    private static final int HEADER_IOS_8_MAX = 65;\n    private static final int VIEWPORT_MIN_IOS_8 = 960;\n    private static final int VIEWPORT_MIN_IOS_8_SIM = 1250;\n\n    private static final CutStrategy CUT_STRATEGY_IOS_7 = new FixedCutStrategy(HEADER_IOS_7, 0);\n    private static final CutStrategy CUT_STRATEGY_IOS_8 = iOS8CutStrategy(VIEWPORT_MIN_IOS_8);\n    private static final CutStrategy CUT_STRATEGY_IOS_8_SIM = iOS8CutStrategy(VIEWPORT_MIN_IOS_8_SIM);\n\n    private ShootingStrategies() {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Simple shooting strategy. No image processing is performed.\n     * @return new instance of SimpleShootingStrategy\n     */\n    public static ShootingStrategy simple() {\n        return new SimpleShootingStrategy();\n    }\n\n    /**\n     * Will scale down image according to dpr specified.\n     *\n     * @param shootingStrategy Shooting strategy used to take screenshots before scaling\n     * @param dpr device pixel ratio\n     * @return {@code ShootingStrategy} that will scale image according to {@code dpr}\n     */\n    public static ShootingStrategy scaling(ShootingStrategy shootingStrategy, float dpr) {\n        return new ScalingDecorator(shootingStrategy).withDpr(dpr);\n    }\n\n    /**\n     * Will scale down image according to dpr specified.\n     *\n     * @param dpr device pixel ratio\n     * @return {@code ShootingStrategy} that will scale image according to {@code dpr}\n     */\n    public static ShootingStrategy scaling(float dpr) {\n        return scaling(simple(), dpr);\n    }\n\n    /**\n     * Will cut header and footer off from screen shot.\n     *\n     * @param shootingStrategy Shooting strategy used to take screenshots before cutting\n     * @param cutStrategy {@link CutStrategy} to use. See {@link FixedCutStrategy} or {@link VariableCutStrategy}\n     * @return {@code ShootingStrategy} with custom cutting strategy\n     */\n    public static ShootingStrategy cutting(ShootingStrategy shootingStrategy, CutStrategy cutStrategy) {\n        return new CuttingDecorator(shootingStrategy).withCutStrategy(cutStrategy);\n    }\n\n    /**\n     * Will cut header and footer off from screen shot.\n     *\n     * @param cutStrategy {@link CutStrategy} to use. See {@link FixedCutStrategy} or {@link VariableCutStrategy}\n     * @return {@code ShootingStrategy} with custom cutting strategy\n     */\n    public static ShootingStrategy cutting(CutStrategy cutStrategy) {\n        return cutting(simple(), cutStrategy);\n    }\n\n    /**\n     * Will cut header and footer off from screen shot.\n     *\n     * @param headerToCut header to cut in pixels\n     * @param footerToCut footer to cut in pixels\n     * @return {@code ShootingStrategy} with {@link FixedCutStrategy} cutting strategy\n     */\n    public static ShootingStrategy cutting(int headerToCut, int footerToCut) {\n        return cutting(new FixedCutStrategy(headerToCut, footerToCut));\n    }\n\n    /**\n     * Will scroll viewport while shooting.\n     *\n     * @param shootingStrategy Shooting strategy used to take screenshots between scrolls\n     * @param scrollTimeout time between viewportPasting scrolls in milliseconds\n     * @return {@code ShootingStrategy} with custom timeout between scrolls\n     */\n    public static ShootingStrategy viewportPasting(ShootingStrategy shootingStrategy, int scrollTimeout) {\n        return new ViewportPastingDecorator(shootingStrategy).withScrollTimeout(scrollTimeout);\n    }\n\n    /**\n     * Will scroll viewport while shooting.\n     *\n     * @param scrollTimeout time between viewportPasting scrolls in milliseconds\n     * @return {@code ShootingStrategy} with custom timeout between scrolls\n     */\n    public static ShootingStrategy viewportPasting(int scrollTimeout) {\n        return viewportPasting(simple(), scrollTimeout);\n    }\n\n    /**\n     * Will scroll viewport while shooting and cut off browser's header and footer\n     *\n     * @param shootingStrategy Underneath shooting strategy\n     * @param scrollTimeout time between scrolls in milliseconds\n     * @param cutStrategy strategy to cut header and footer from image\n     *\n     * @return {@code ShootingStrategy} with custom scroll timeout and cutting strategy\n     */\n    public static ShootingStrategy viewportNonRetina(ShootingStrategy shootingStrategy, int scrollTimeout,\n            CutStrategy cutStrategy) {\n        return viewportPasting(cutting(shootingStrategy, cutStrategy), scrollTimeout);\n    }\n\n    /**\n     * Will scroll viewport while shooting and cut off browser's header and footer\n     *\n     * @param scrollTimeout time between scrolls in milliseconds\n     * @param cutStrategy strategy to cut header and footer from image\n     *\n     * @return {@code ShootingStrategy} with custom scroll timeout and cutting strategy\n     */\n    public static ShootingStrategy viewportNonRetina(int scrollTimeout, CutStrategy cutStrategy) {\n        return viewportPasting(cutting(cutStrategy), scrollTimeout);\n    }\n\n    /**\n     * Will scroll viewportPasting while shooting and cut off browser's header and footer\n     *\n     * @param scrollTimeout time between scrolls in milliseconds\n     * @param headerToCut height of header to cut from image\n     * @param footerToCut height of footer to cut from image\n     *\n     * @return {@code ShootingStrategy} with custom scroll timeout and header/footer to cut\n     */\n    public static ShootingStrategy viewportNonRetina(int scrollTimeout, int headerToCut, int footerToCut) {\n        return viewportNonRetina(scrollTimeout, new FixedCutStrategy(headerToCut, footerToCut));\n    }\n\n    /**\n     * Will scale screenshots and scroll viewportPasting while shooting and cut off browser's header/footer\n     *\n     * @param shootingStrategy Underneath shooting strategy\n     * @param scrollTimeout time between scrolls in milliseconds\n     * @param cutStrategy strategy to cut header and footer from image\n     * @param dpr device pixel ratio\n     *\n     * @return {@code ShootingStrategy} with custom DPR scaling and scroll timeout and cutting strategy\n     */\n    public static ShootingStrategy viewportRetina(ShootingStrategy shootingStrategy, int scrollTimeout,\n            CutStrategy cutStrategy, float dpr) {\n        ShootingStrategy scalingDecorator = scaling(shootingStrategy, dpr);\n        return viewportNonRetina(scalingDecorator, scrollTimeout, cutStrategy);\n    }\n\n    /**\n     * Will scale screenshots and scroll viewportPasting while shooting and cut off browser's header/footer\n     *\n     * @param scrollTimeout time between scrolls in milliseconds\n     * @param cutStrategy strategy to cut header and footer from image\n     * @param dpr device pixel ratio\n     *\n     * @return {@code ShootingStrategy} with custom DPR scaling and scroll timeout and cutting strategy\n     */\n    public static ShootingStrategy viewportRetina(int scrollTimeout, CutStrategy cutStrategy, float dpr) {\n        return viewportRetina(simple(), scrollTimeout, cutStrategy, dpr);\n    }\n\n    /**\n     * Will scale screenshots and scroll viewportPasting while shooting and cut off browser's header/footer\n     *\n     * @param scrollTimeout time between scrolls in milliseconds\n     * @param headerToCut height of header to cut from image\n     * @param footerToCut height of footer to cut from image\n     * @param dpr device pixel ratio\n     *\n     * @return {@code ShootingStrategy} with custom DPR scaling and scroll timeout and cutting strategy\n     */\n    public static ShootingStrategy viewportRetina(int scrollTimeout, int headerToCut, int footerToCut, float dpr) {\n        return viewportRetina(scrollTimeout, new FixedCutStrategy(headerToCut, footerToCut), dpr);\n    }\n\n    /**\n     * Will scale screenshots and scroll viewportPasting while shooting and cut off browser's header/footer\n     *\n     * @param shootingStrategy Underneath shooting strategy\n     * @param scrollTimeout time between scrolls in milliseconds\n     * @param headerToCut height of header to cut from image\n     * @param footerToCut height of footer to cut from image\n     * @param dpr device pixel ratio\n     *\n     * @return {@code ShootingStrategy} with custom DPR scaling and scroll timeout and cutting strategy\n     */\n    public static ShootingStrategy viewportRetina(ShootingStrategy shootingStrategy, int scrollTimeout, int headerToCut,\n            int footerToCut, float dpr) {\n        return viewportRetina(shootingStrategy, scrollTimeout, new FixedCutStrategy(headerToCut, footerToCut), dpr);\n    }\n\n    public static ShootingStrategy iPad2WithIOS7(ShootingStrategy shootingStrategy) {\n        return viewportIOSNonRetina(shootingStrategy, CUT_STRATEGY_IOS_7);\n    }\n\n    public static ShootingStrategy iPad2WithIOS7() {\n        return iPad2WithIOS7(simple());\n    }\n\n    public static ShootingStrategy iPad2WithIOS8(ShootingStrategy shootingStrategy) {\n        return viewportIOSNonRetina(shootingStrategy, CUT_STRATEGY_IOS_8);\n    }\n\n    public static ShootingStrategy iPad2WithIOS8() {\n        return iPad2WithIOS8(simple());\n    }\n\n    public static ShootingStrategy iPad2WithIOS8Simulator(ShootingStrategy shootingStrategy) {\n        return viewportIOSNonRetina(shootingStrategy, CUT_STRATEGY_IOS_8_SIM);\n    }\n\n    public static ShootingStrategy iPad2WithIOS8Simulator() {\n        return iPad2WithIOS8Simulator(simple());\n    }\n\n    public static ShootingStrategy iPad2WithIOS8Retina(ShootingStrategy shootingStrategy) {\n        return viewportIOSRetina(shootingStrategy, CUT_STRATEGY_IOS_8);\n    }\n\n    public static ShootingStrategy iPad2WithIOS8Retina() {\n        return iPad2WithIOS8Retina(simple());\n    }\n\n    public static ShootingStrategy iPad2WithIOS8RetinaSimulator(ShootingStrategy shootingStrategy) {\n        return viewportIOSRetina(shootingStrategy, CUT_STRATEGY_IOS_8_SIM);\n    }\n\n    public static ShootingStrategy iPad2WithIOS8RetinaSimulator() {\n        return iPad2WithIOS8RetinaSimulator(simple());\n    }\n\n    /**\n     * Will create screenshot's of the whole page and rotate landscape images\n     *\n     * @param scrollTimeout time between scrolls in milliseconds\n     * @param cutStrategy strategy to cut header and footer from image\n     * @return {@code ShootingStrategy} which will shoot whole page and rotate landscape images\n     */\n    public static ShootingStrategy iPadLandscapeOrientation(int scrollTimeout, CutStrategy cutStrategy) {\n        return viewportPasting(iPadLandscapeOrientationSimple(cutStrategy), scrollTimeout);\n    }\n\n    /**\n     * Will rotate screenshot's made on iPad in landscape orientation\n     *\n     * @param cutStrategy strategy to cut header and footer from image\n     * @return {@code ShootingStrategy} which will rotate image\n     */\n    public static ShootingStrategy iPadLandscapeOrientationSimple(CutStrategy cutStrategy) {\n        return new RotatingDecorator(cutStrategy, simple());\n    }\n\n    private static ShootingStrategy viewportIOSNonRetina(ShootingStrategy shootingStrategy, CutStrategy cutStrategy) {\n        return viewportNonRetina(shootingStrategy, SCROLL_TIMEOUT_IOS, cutStrategy);\n    }\n\n    private static ShootingStrategy viewportIOSRetina(ShootingStrategy shootingStrategy, CutStrategy cutStrategy) {\n        return viewportRetina(shootingStrategy, SCROLL_TIMEOUT_IOS, cutStrategy, 2F);\n    }\n\n    private static CutStrategy iOS8CutStrategy(int minViewport) {\n        return new VariableCutStrategy(HEADER_IOS_8_MIN, HEADER_IOS_8_MAX, minViewport);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/ShootingStrategy.java",
    "content": "package pazone.ashot;\n\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt.image.BufferedImage;\nimport java.io.Serializable;\nimport java.util.Set;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\npublic interface ShootingStrategy extends Serializable {\n\n    /**\n     * Get's screenshot of whole page or viewport (depends on browser)\n     *\n     * @param wd WebDrvier\n     * @return image of the whole page or viewport\n     */\n    BufferedImage getScreenshot(WebDriver wd);\n\n    /**\n     * Get's screenshot of area or areas that are defined by {@link Coords}\n     *\n     * @param wd WebDriver\n     * @param coords Set of coordinates to shoot\n     * @return minimal image with required coords\n     */\n    BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords);\n\n    /**\n     * Prepares coordinated for cropper and ignored areas\n     *\n     * @param coordsSet to prepare\n     * @return New set of prepared coordinates\n     */\n    Set<Coords> prepareCoords(Set<Coords> coordsSet);\n\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/SimpleShootingStrategy.java",
    "content": "package pazone.ashot;\n\nimport org.openqa.selenium.OutputType;\nimport org.openqa.selenium.TakesScreenshot;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.remote.Augmenter;\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.util.ImageTool;\n\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.util.Set;\n\n/**\n * Gets screenshot from webdriver.\n */\npublic class SimpleShootingStrategy implements ShootingStrategy {\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd) {\n        TakesScreenshot takesScreenshot;\n        try {\n            takesScreenshot = (TakesScreenshot) wd;\n        } catch (ClassCastException ignored) {\n            takesScreenshot = (TakesScreenshot) new Augmenter().augment(wd);\n        }\n        try {\n            byte[] imageBytes = takesScreenshot.getScreenshotAs(OutputType.BYTES);\n            return ImageTool.toBufferedImage(imageBytes);\n        } catch (IOException e) {\n            throw new ImageReadException(\"Can not parse screenshot data\", e);\n        }\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coords) {\n        return getScreenshot(wd);\n    }\n\n    /**\n     * Default behavior is not to change coords, because by default coordinates are not changed\n     */\n    @Override\n    public Set<Coords> prepareCoords(Set<Coords> coordsSet) {\n        return coordsSet;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/ViewportPastingDecorator.java",
    "content": "package pazone.ashot;\n\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.util.InnerScript;\n\nimport java.awt.Graphics2D;\nimport java.awt.image.BufferedImage;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Will scroll viewport and shoot to get an image of full page.\n * Useful for browsers on portable devices.\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\npublic class ViewportPastingDecorator extends ShootingDecorator {\n\n    public static final String PAGE_DIMENSIONS_JS = \"js/page_dimensions.js\";\n\n    protected int scrollTimeout = 0;\n    private Coords shootingArea;\n\n    public ViewportPastingDecorator(ShootingStrategy strategy) {\n        super(strategy);\n    }\n\n    public ViewportPastingDecorator withScrollTimeout(int scrollTimeout) {\n        this.scrollTimeout = scrollTimeout;\n        return this;\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd) {\n        return getScreenshot(wd, null);\n    }\n\n    @Override\n    public BufferedImage getScreenshot(WebDriver wd, Set<Coords> coordsSet) {\n        JavascriptExecutor js = (JavascriptExecutor) wd;\n        int initialY = getCurrentScrollY(js);\n\n        try {\n            PageDimensions pageDimensions = getPageDimensions(wd);\n            shootingArea = getShootingCoords(coordsSet, pageDimensions);\n\n            BufferedImage finalImage = new BufferedImage(pageDimensions.getViewportWidth(), shootingArea.height,\n                    BufferedImage.TYPE_3BYTE_BGR);\n            Graphics2D graphics = finalImage.createGraphics();\n\n            int viewportHeight = pageDimensions.getViewportHeight();\n            int scrollTimes = (int) Math.ceil(shootingArea.getHeight() / viewportHeight);\n            for (int n = 0; n < scrollTimes; n++) {\n                scrollVertically(js, shootingArea.y + viewportHeight * n);\n                waitForScrolling();\n                BufferedImage part = getChunk(wd, n, scrollTimes);\n                graphics.drawImage(part, 0, getCurrentScrollY(js) - shootingArea.y, null);\n            }\n\n            graphics.dispose();\n            return finalImage;\n        } finally {\n            scrollVertically(js, initialY);\n        }\n    }\n\n    @Override\n    public Set<Coords> prepareCoords(Set<Coords> coordsSet) {\n        return shootingArea == null ? coordsSet : shiftCoords(coordsSet, shootingArea);\n    }\n\n    protected PageDimensions getPageDimensions(WebDriver driver) {\n        Map<String, Number> pageDimensions = InnerScript.execute(PAGE_DIMENSIONS_JS, driver);\n        return new PageDimensions(pageDimensions.get(\"pageHeight\").intValue(),\n                pageDimensions.get(\"viewportWidth\").intValue(), pageDimensions.get(\"viewportHeight\").intValue());\n    }\n\n    protected int getCurrentScrollY(JavascriptExecutor js) {\n        return ((Number) js.executeScript(\"var scrY = window.pageYOffset;\"\n                + \"if(scrY){return scrY;} else {return 0;}\")).intValue();\n    }\n\n    protected void scrollVertically(JavascriptExecutor js, int scrollY) {\n        js.executeScript(\"scrollTo(0, arguments[0]); return [];\", scrollY);\n    }\n\n    /**\n     * Returns a single chunk (viewport screenshot) used to build a full screenshot\n     *\n     * @param wd                  WebDriver instance\n     * @param currentChunkIndex   Zero-based index of the chunk to get\n     * @param totalNumberOfChunks Total number of chunks\n     * @return Current screenshot chunk (viewport screenshot)\n     */\n    protected BufferedImage getChunk(WebDriver wd, int currentChunkIndex, int totalNumberOfChunks) {\n        return getShootingStrategy().getScreenshot(wd);\n    }\n\n    private Coords getShootingCoords(Set<Coords> coords, PageDimensions pageDimensions) {\n        if (coords == null || coords.isEmpty()) {\n            return new Coords(0, 0, pageDimensions.getViewportWidth(), pageDimensions.getPageHeight());\n        }\n        return extendShootingArea(Coords.unity(coords), pageDimensions);\n    }\n\n    private Set<Coords> shiftCoords(Set<Coords> coordsSet, Coords shootingArea) {\n        Set<Coords> shiftedCoords = new HashSet<>();\n        if (coordsSet != null) {\n            for (Coords coords : coordsSet) {\n                coords.y -= shootingArea.y;\n                shiftedCoords.add(coords);\n            }\n        }\n        return shiftedCoords;\n    }\n\n    private Coords extendShootingArea(Coords shootingCoords, PageDimensions pageDimensions) {\n        int halfViewport = pageDimensions.getViewportHeight() / 2;\n        shootingCoords.y = Math.max(shootingCoords.y - halfViewport / 2, 0);\n        shootingCoords.height = Math.min(shootingCoords.height + halfViewport, pageDimensions.getPageHeight());\n        return shootingCoords;\n    }\n\n    private void waitForScrolling() {\n        try {\n            Thread.sleep(scrollTimeout);\n        } catch (InterruptedException e) {\n            throw new IllegalStateException(\"Exception while waiting for scrolling\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/comparison/DiffMarkupPolicy.java",
    "content": "package pazone.ashot.comparison;\n\nimport java.awt.Color;\nimport java.awt.image.BufferedImage;\nimport java.awt.image.IndexColorModel;\n\nimport static java.awt.image.BufferedImage.TYPE_BYTE_INDEXED;\n\n/**\n * @author Rovniakov Viacheslav rovner@yandex-team.ru\n */\n\npublic abstract class DiffMarkupPolicy {\n\n    private static final int BITS_PER_PIXEL = 8;\n    private static final int COLOR_MAP_SIZE = 2;\n    private static final int TRANSPARENT_COLOR_INDEX = 0;\n    protected boolean marked = false;\n    protected int diffSizeTrigger;\n    protected BufferedImage diffImage;\n    protected Color diffColor = Color.RED;\n\n    public DiffMarkupPolicy withDiffColor(final Color diffColor) {\n        this.diffColor = diffColor;\n        return this;\n    }\n\n    public abstract BufferedImage getMarkedImage();\n\n    public abstract BufferedImage getTransparentMarkedImage();\n\n    public abstract void addDiffPoint(int x, int y);\n\n    @Override\n    public abstract boolean equals(Object obj);\n\n    @Override\n    public abstract int hashCode();\n\n    public abstract boolean hasDiff();\n\n    public abstract int getDiffSize();\n\n    public void setDiffImage(BufferedImage diffImage) {\n        this.diffImage = diffImage;\n    }\n\n    public void setDiffSizeTrigger(final int diffSizeTrigger) {\n        this.diffSizeTrigger = diffSizeTrigger;\n    }\n\n    public BufferedImage getDiffImage() {\n        return diffImage;\n    }\n\n    private IndexColorModel getColorModel() {\n        return new IndexColorModel(BITS_PER_PIXEL, COLOR_MAP_SIZE, getColorMap(), 0, false, TRANSPARENT_COLOR_INDEX);\n    }\n\n    private byte[] getColorMap() {\n        Color negativeColor = new Color(0xFFFFFF - diffColor.getRGB()); //negate diff color\n        return new byte[]{\n                (byte) negativeColor.getRed(),\n                (byte) negativeColor.getGreen(),\n                (byte) negativeColor.getBlue(),\n                (byte) diffColor.getRed(),\n                (byte) diffColor.getGreen(),\n                (byte) diffColor.getBlue()\n        };\n    }\n\n    protected BufferedImage getTransparentDiffImage(BufferedImage diffImage) {\n        return new BufferedImage(diffImage.getWidth(), diffImage.getHeight(), TYPE_BYTE_INDEXED, getColorModel());\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/comparison/ImageDiff.java",
    "content": "package pazone.ashot.comparison;\n\nimport java.awt.image.BufferedImage;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic class ImageDiff {\n\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public static final ImageDiff EMPTY_DIFF = new ImageDiff();\n\n    private final DiffMarkupPolicy diffMarkupPolicy;\n\n    public ImageDiff(DiffMarkupPolicy diffMarkupPolicy) {\n        this.diffMarkupPolicy = diffMarkupPolicy;\n    }\n\n    private ImageDiff() {\n        diffMarkupPolicy = new PointsMarkupPolicy();\n    }\n\n    /**\n     * Sets the maximum number of distinguished pixels when images are still considered the same.\n     *\n     * @param diffSizeTrigger the number of different pixels\n     * @return self for fluent style\n     */\n    public ImageDiff withDiffSizeTrigger(final int diffSizeTrigger) {\n        this.diffMarkupPolicy.setDiffSizeTrigger(diffSizeTrigger);\n        return this;\n    }\n\n    /**\n     * @return Diff image with empty spaces in diff areas.\n     */\n    public BufferedImage getDiffImage() {\n        return diffMarkupPolicy.getDiffImage();\n    }\n\n    /**\n     * Sets Diff image.\n     * @param image Image diff\n     */\n    public void setDiffImage(BufferedImage image) {\n        diffMarkupPolicy.setDiffImage(image);\n    }\n\n    public void addDiffPoint(int x, int y) {\n        diffMarkupPolicy.addDiffPoint(x, y);\n    }\n\n    /**\n     * Marks diff on inner image and returns it.\n     * Idempotent.\n     *\n     * @return marked diff image\n     */\n    public BufferedImage getMarkedImage() {\n        return diffMarkupPolicy.getMarkedImage();\n    }\n\n    /**\n     * Marks diff points on transparent canvas and returns it.\n     * Idempotent.\n     *\n     * @return marked diff image\n     */\n    public BufferedImage getTransparentMarkedImage() {\n        return diffMarkupPolicy.getTransparentMarkedImage();\n    }\n\n    /**\n     * Returns <tt>true</tt> if there are differences between images.\n     *\n     * @return <tt>true</tt> if there are differences between images.\n     */\n    public boolean hasDiff() {\n        return diffMarkupPolicy.hasDiff();\n    }\n\n    /**\n     * Returns number of points that differ.\n     *\n     * @return int - number of points that differ.\n     */\n    public int getDiffSize() {\n        return diffMarkupPolicy.getDiffSize();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof ImageDiff) {\n            ImageDiff item = (ImageDiff) obj;\n            return this.diffMarkupPolicy.equals(item.diffMarkupPolicy);\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        return this.diffMarkupPolicy.hashCode();\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/comparison/ImageDiffer.java",
    "content": "package pazone.ashot.comparison;\n\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.util.ImageBytesDiffer;\n\nimport java.awt.Color;\nimport java.awt.Graphics;\nimport java.awt.image.BufferedImage;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\n\nimport static pazone.ashot.util.ImageTool.rgbCompare;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic class ImageDiffer {\n\n    private static final int DEFAULT_COLOR_DISTORTION = 15;\n\n    private int colorDistortion = DEFAULT_COLOR_DISTORTION;\n    private DiffMarkupPolicy diffMarkupPolicy = new PointsMarkupPolicy();\n    private Color ignoredColor = null;\n\n\n    public ImageDiffer withIgnoredColor(final Color ignoreColor) {\n        this.ignoredColor = ignoreColor;\n        return this;\n    }\n\n\n    public ImageDiffer withColorDistortion(int distortion) {\n        this.colorDistortion = distortion;\n        return this;\n    }\n\n    /**\n     * Sets the diff markup policy.\n     *\n     * @param diffMarkupPolicy diff markup policy instance\n     * @return self for fluent style\n     * @see ImageMarkupPolicy\n     * @see PointsMarkupPolicy\n     */\n    public ImageDiffer withDiffMarkupPolicy(final DiffMarkupPolicy diffMarkupPolicy) {\n        this.diffMarkupPolicy = diffMarkupPolicy;\n        return this;\n    }\n\n    public ImageDiff makeDiff(Screenshot expected, Screenshot actual) {\n        ImageDiff diff = new ImageDiff(diffMarkupPolicy);\n\n        if (ImageBytesDiffer.areImagesEqual(expected, actual)) {\n            diff.setDiffImage(actual.getImage());\n        } else {\n            markDiffPoints(expected, actual, diff);\n        }\n\n        return diff;\n    }\n\n    protected void markDiffPoints(Screenshot expected, Screenshot actual, ImageDiff diff) {\n        Coords expectedImageCoords = Coords.ofImage(expected.getImage());\n        Coords actualImageCoords = Coords.ofImage(actual.getImage());\n\n        CoordsSet compareCoordsSet = new CoordsSet(\n                CoordsSet.union(actual.getCoordsToCompare(), expected.getCoordsToCompare()));\n        CoordsSet ignoreCoordsSet = new CoordsSet(\n                CoordsSet.intersection(actual.getIgnoredAreas(), expected.getIgnoredAreas()));\n\n        int width = Math.max(expected.getImage().getWidth(), actual.getImage().getWidth());\n        int height = Math.max(expected.getImage().getHeight(), actual.getImage().getHeight());\n        diff.setDiffImage(createDiffImage(expected.getImage(), actual.getImage(), width, height));\n\n        for (int i = 0; i < width; i++) {\n            for (int j = 0; j < height; j++) {\n                if (ignoreCoordsSet.contains(i, j)) {\n                    continue;\n                }\n                if (!isInsideBothImages(i, j, expectedImageCoords, actualImageCoords)\n                        || compareCoordsSet.contains(i, j) && hasDiffInChannel(expected, actual, i, j)) {\n                    diff.addDiffPoint(i, j);\n                }\n            }\n        }\n    }\n\n    private boolean hasDiffInChannel(Screenshot expected, Screenshot actual, int i, int j) {\n        if (ignoredColor != null && rgbCompare(expected.getImage().getRGB(i, j), ignoredColor.getRGB(), 0)) {\n           return false;\n        }\n\n        return !rgbCompare(expected.getImage().getRGB(i, j), actual.getImage().getRGB(i, j), colorDistortion);\n    }\n\n    public ImageDiff makeDiff(BufferedImage expected, BufferedImage actual) {\n        return makeDiff(new Screenshot(expected), new Screenshot(actual));\n    }\n\n    private BufferedImage createDiffImage(BufferedImage expectedImage, BufferedImage actualImage, int width,\n            int height) {\n        BufferedImage diffImage = new BufferedImage(width, height, actualImage.getType());\n        paintImage(actualImage, diffImage);\n        paintImage(expectedImage, diffImage);\n        return diffImage;\n    }\n\n    private void paintImage(BufferedImage image, BufferedImage diffImage) {\n        Graphics graphics = diffImage.getGraphics();\n        graphics.drawImage(image, 0, 0, null);\n        graphics.dispose();\n    }\n\n    private boolean isInsideBothImages(int i, int j, Coords expected, Coords actual) {\n        return expected.contains(i, j) && actual.contains(i, j);\n    }\n\n    private static class CoordsSet {\n\n        private final boolean isSingle;\n        private final Coords minRectangle;\n        private final Set<Coords> coordsSet;\n\n        CoordsSet(Set<Coords> coordsSet) {\n            isSingle = coordsSet.size() == 1;\n            this.coordsSet = coordsSet;\n            int minX = Integer.MAX_VALUE;\n            int minY = Integer.MAX_VALUE;\n            int maxX = 0;\n            int maxY = 0;\n            for (Coords coords : coordsSet) {\n                minX = Math.min(minX, (int) coords.getMinX());\n                minY = Math.min(minY, (int) coords.getMinY());\n                maxX = Math.max(maxX, (int) coords.getMaxX());\n                maxY = Math.max(maxY, (int) coords.getMaxY());\n            }\n            minRectangle = new Coords(minX, minY, maxX - minX, maxY - minY);\n        }\n\n        private boolean contains(int i, int j) {\n            return inaccurateContains(i, j) && accurateContains(i, j);\n        }\n\n        private boolean inaccurateContains(int i, int j) {\n            return minRectangle.contains(i, j);\n        }\n\n        private boolean accurateContains(int i, int j) {\n            return isSingle || coordsSet.stream().anyMatch(coords -> coords.contains(i, j));\n        }\n\n        private static Set<Coords> intersection(Set<Coords> coordsPool1, Set<Coords> coordsPool2) {\n            return Coords.intersection(coordsPool1, coordsPool2);\n        }\n\n        private static Set<Coords> union(Set<Coords> coordsPool1, Set<Coords> coordsPool2) {\n            Set<Coords> coordsPool = new LinkedHashSet<>();\n            coordsPool.addAll(coordsPool1);\n            coordsPool.addAll(coordsPool2);\n            return coordsPool;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/comparison/ImageMarkupPolicy.java",
    "content": "package pazone.ashot.comparison;\n\nimport java.awt.image.BufferedImage;\n\n/**\n * @author Rovniakov Viacheslav rovner@yandex-team.ru\n *\n */\n\npublic class ImageMarkupPolicy extends DiffMarkupPolicy {\n\n    private int diffPointCount;\n    private int xReference = Integer.MAX_VALUE;\n    private int yReference = Integer.MAX_VALUE;\n    private int xSum;\n    private int ySum;\n    private BufferedImage transparentDiffImage;\n\n    @Override\n    public void setDiffImage(BufferedImage diffImage) {\n        super.setDiffImage(diffImage);\n        transparentDiffImage = getTransparentDiffImage(diffImage);\n    }\n\n    @Override\n    public BufferedImage getMarkedImage() {\n        if (!marked) {\n            final int rgb = diffColor.getRGB();\n            for (int x = 0; x < diffImage.getWidth(); x++) {\n                for (int y = 0; y < diffImage.getHeight(); y++) {\n                    if (transparentDiffImage.getRGB(x, y) == rgb) {\n                        diffImage.setRGB(x, y, rgb);\n                    }\n                }\n            }\n            marked = true;\n        }\n        return diffImage;\n    }\n\n    @Override\n    public BufferedImage getTransparentMarkedImage() {\n        return transparentDiffImage;\n    }\n\n    @Override\n    public void addDiffPoint(int x, int y) {\n        diffPointCount++;\n        xReference = Math.min(xReference, x);\n        yReference = Math.min(yReference, y);\n        xSum += x;\n        ySum += y;\n        transparentDiffImage.setRGB(x, y, diffColor.getRGB());\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof ImageMarkupPolicy) {\n            ImageMarkupPolicy item = (ImageMarkupPolicy) obj;\n            return this.diffPointCount == item.diffPointCount\n                    && this.xSum - this.diffPointCount * this.xReference\n                        == item.xSum - item.diffPointCount * item.xReference\n                    && this.ySum - this.diffPointCount * this.yReference\n                        == item.ySum - item.diffPointCount * item.yReference;\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = diffPointCount;\n        result = 31 * result +  xSum - diffPointCount * xReference;\n        result = 31 * result +  ySum - diffPointCount * yReference;\n        return result;\n    }\n\n    @Override\n    public boolean hasDiff() {\n        return diffPointCount > diffSizeTrigger;\n    }\n\n    @Override\n    public int getDiffSize() {\n        return diffPointCount;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/comparison/PointsMarkupPolicy.java",
    "content": "package pazone.ashot.comparison;\n\nimport java.awt.Point;\nimport java.awt.image.BufferedImage;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\n\n/**\n * @author Rovniakov Viacheslav rovner@yandex-team.ru\n *\n */\n\npublic class PointsMarkupPolicy extends DiffMarkupPolicy {\n\n    private final Set<Point> diffPoints = new LinkedHashSet<>();\n    private Set<Point> deposedPoints = new LinkedHashSet<>();\n    private BufferedImage transparentMarkedImage = null;\n\n    @Override\n    public BufferedImage getMarkedImage() {\n        if (!marked) {\n            markDiffPoints(diffImage);\n            marked = true;\n        }\n        return diffImage;\n    }\n\n    @Override\n    public BufferedImage getTransparentMarkedImage() {\n        if (transparentMarkedImage == null) {\n            transparentMarkedImage = getTransparentDiffImage(diffImage);\n            markDiffPoints(transparentMarkedImage);\n        }\n        return transparentMarkedImage;\n    }\n\n    @Override\n    public void addDiffPoint(int x, int y) {\n        diffPoints.add(new Point(x, y));\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj instanceof PointsMarkupPolicy) {\n            PointsMarkupPolicy item = (PointsMarkupPolicy) obj;\n            return diffPoints.size() == item.diffPoints.size() && item.getDeposedPoints().containsAll(\n                    getDeposedPoints());\n        }\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        return getDeposedPoints().hashCode();\n    }\n\n    @Override\n    public boolean hasDiff() {\n        return diffPoints.size() > diffSizeTrigger;\n    }\n\n    @Override\n    public int getDiffSize() {\n        return diffPoints.size();\n    }\n\n    protected void markDiffPoints(BufferedImage image) {\n        int rgb = diffColor.getRGB();\n        for (Point dot : diffPoints) {\n            image.setRGB(dot.x, dot.y, rgb);\n        }\n    }\n\n    private Set<Point> getDeposedPoints() {\n        if (deposedPoints.isEmpty()) {\n            deposedPoints = deposeReference();\n        }\n        return deposedPoints;\n    }\n\n    private Point getReferenceCorner() {\n        Iterator<Point> iterator = diffPoints.iterator();\n        Point diffPoint = iterator.next();\n        double x = diffPoint.getX();\n        double y = diffPoint.getY();\n        while (iterator.hasNext()) {\n            diffPoint = iterator.next();\n            x = Math.min(x, diffPoint.getX());\n            y = Math.min(y, diffPoint.getY());\n        }\n        return new Point((int) x, (int) y);\n    }\n\n    private Set<Point> deposeReference() {\n        Point reference = getReferenceCorner();\n        Set<Point> referenced = new HashSet<>();\n        for (Point point : diffPoints) {\n            referenced.add(new Point(point.x - reference.x, point.y - reference.y));\n        }\n        return referenced;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/coordinates/Coords.java",
    "content": "package pazone.ashot.coordinates;\n\nimport com.google.gson.Gson;\n\nimport java.awt.Rectangle;\nimport java.awt.image.BufferedImage;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic class Coords extends Rectangle {\n\n    public static Set<Coords> intersection(Collection<Coords> coordsPool1, Collection<Coords> coordsPool2) {\n        Set<Coords> intersectedCoords = new HashSet<>();\n        for (Coords coords1 : coordsPool1) {\n            for (Coords coords2 : coordsPool2) {\n                Coords intersection = coords1.intersection(coords2);\n                if (!intersection.isEmpty()) {\n                    intersectedCoords.add(intersection);\n                }\n            }\n        }\n        return intersectedCoords;\n    }\n\n    public static Set<Coords> setReferenceCoords(Coords reference, Set<Coords> coordsSet) {\n        Set<Coords> referencedCoords = new HashSet<>();\n        for (Coords coords : coordsSet) {\n            referencedCoords.add(new Coords(\n                            coords.x - reference.x,\n                            coords.y - reference.y,\n                            coords.width,\n                            coords.height)\n            );\n        }\n        return referencedCoords;\n    }\n\n    public static Coords unity(Collection<Coords> coordsCollection) {\n        Coords unity = coordsCollection.iterator().next();\n        for (Coords coords : coordsCollection) {\n            unity = unity.union(coords);\n        }\n        return unity;\n    }\n\n    public static Coords ofImage(BufferedImage image) {\n        return new Coords(image.getWidth(), image.getHeight());\n    }\n\n    public Coords(Rectangle rectangle) {\n        super(rectangle);\n    }\n\n    public Coords(int x, int y, int width, int height) {\n        super(x, y, width, height);\n    }\n\n    public Coords(int width, int height) {\n        super(width, height);\n    }\n\n    public void reduceBy(int pixels) {\n        if (pixels < getWidth() / 2 && pixels < getHeight() / 2) {\n            this.x += pixels;\n            this.y += pixels;\n            this.width -= pixels;\n            this.height -= pixels;\n        }\n    }\n\n\n    @SuppressWarnings(\"NullableProblems\")\n    @Override\n    public Coords union(Rectangle r) {\n        return new Coords(super.union(r));\n    }\n\n    @SuppressWarnings(\"NullableProblems\")\n    @Override\n    public Coords intersection(Rectangle r) {\n        return new Coords(super.intersection(r));\n    }\n\n    @Override\n    public String toString() {\n        return new Gson().toJson(this);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/coordinates/CoordsPreparationStrategy.java",
    "content": "package pazone.ashot.coordinates;\n\nimport pazone.ashot.Screenshot;\n\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport static pazone.ashot.coordinates.Coords.intersection;\nimport static pazone.ashot.coordinates.Coords.setReferenceCoords;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic abstract class CoordsPreparationStrategy {\n\n    public static CoordsPreparationStrategy simple() {\n        return new CoordsPreparationStrategy() {\n            @Override\n            public Set<Coords> prepare(Collection<Coords> coordinates) {\n                return new HashSet<>(coordinates);\n            }\n        };\n    }\n\n    public static CoordsPreparationStrategy intersectingWith(final Screenshot screenshot) {\n        return new CoordsPreparationStrategy() {\n            @Override\n            public Set<Coords> prepare(Collection<Coords> coordinates) {\n                return intersection(screenshot.getCoordsToCompare(),\n                        setReferenceCoords(screenshot.getOriginShift(), new HashSet<>(coordinates)));\n            }\n        };\n    }\n\n    public abstract Set<Coords> prepare(Collection<Coords> coordinates);\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/coordinates/CoordsProvider.java",
    "content": "package pazone.ashot.coordinates;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\n\nimport java.io.Serializable;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic abstract class CoordsProvider implements Serializable {\n\n    public abstract Coords ofElement(WebDriver driver, WebElement element);\n\n    public Set<Coords> ofElements(WebDriver driver, Iterable<WebElement> elements) {\n        Set<Coords> elementsCoords = new HashSet<>();\n        for (WebElement element : elements) {\n            Coords elementCoords = ofElement(driver, element);\n            if (!elementCoords.isEmpty()) {\n                elementsCoords.add(elementCoords);\n            }\n        }\n        return Collections.unmodifiableSet(elementsCoords);\n    }\n\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public Set<Coords> ofElements(WebDriver driver, WebElement... elements) {\n        return ofElements(driver, Arrays.asList(elements));\n    }\n\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public Set<Coords> locatedBy(WebDriver driver, By locator) {\n        return ofElements(driver, driver.findElements(locator));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/coordinates/JqueryCoordsProvider.java",
    "content": "package pazone.ashot.coordinates;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport pazone.ashot.util.JsCoords;\n\n/**\n * @author pazone\n */\npublic class JqueryCoordsProvider extends CoordsProvider {\n\n    @Override\n    public Coords ofElement(WebDriver driver, WebElement element) {\n        return JsCoords.findCoordsWithJquery(driver, element);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/coordinates/WebDriverCoordsProvider.java",
    "content": "package pazone.ashot.coordinates;\n\nimport org.openqa.selenium.Dimension;\nimport org.openqa.selenium.Point;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\npublic class WebDriverCoordsProvider extends CoordsProvider {\n    @Override\n    public Coords ofElement(WebDriver driver, WebElement element) {\n        // Keep Point/Dimension as opposed to Rectangle because browsers like PhantomJS do not like the latter\n        Point point = element.getLocation();\n        Dimension dimension = element.getSize();\n        return new Coords(\n                point.getX(),\n                point.getY(),\n                dimension.getWidth(),\n                dimension.getHeight());\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cropper/DefaultCropper.java",
    "content": "package pazone.ashot.cropper;\n\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt.Graphics;\nimport java.awt.image.BufferedImage;\nimport java.util.Set;\n\nimport static pazone.ashot.coordinates.Coords.setReferenceCoords;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic class DefaultCropper extends ImageCropper {\n\n    @Override\n    public Screenshot cropScreenshot(BufferedImage image, Set<Coords> coordsToCompare) {\n        Coords cropArea = Coords.unity(coordsToCompare);\n\n        Coords imageIntersection = Coords.ofImage(image).intersection(cropArea);\n\n        if (imageIntersection.isEmpty()) {\n            return new Screenshot(image);\n        }\n\n        BufferedImage cropped = new BufferedImage(imageIntersection.width, imageIntersection.height, image.getType());\n        Graphics g = cropped.getGraphics();\n        g.drawImage(\n                image,\n                0, 0,\n                imageIntersection.width, imageIntersection.height,\n                cropArea.x, cropArea.y,\n                cropArea.x + imageIntersection.width, cropArea.y + imageIntersection.height,\n                null\n        );\n        g.dispose();\n        Screenshot screenshot = new Screenshot(cropped);\n        screenshot.setOriginShift(cropArea);\n        screenshot.setCoordsToCompare(setReferenceCoords(screenshot.getOriginShift(), coordsToCompare));\n        return screenshot;\n    }\n\n    protected Coords createCropArea(Set<Coords> coordsToCompare) {\n        return Coords.unity(coordsToCompare);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cropper/ImageCropper.java",
    "content": "package pazone.ashot.cropper;\n\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt.image.BufferedImage;\nimport java.io.Serializable;\nimport java.util.Set;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic abstract class ImageCropper implements Serializable {\n\n    public Screenshot crop(BufferedImage image, Set<Coords> cropArea) {\n        return cropArea.isEmpty()\n                ? new Screenshot(image)\n                : cropScreenshot(image, cropArea);\n    }\n\n    protected abstract Screenshot cropScreenshot(BufferedImage image, Set<Coords> coordsToCompare);\n\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cropper/indent/BlurFilter.java",
    "content": "package pazone.ashot.cropper.indent;\n\nimport java.awt.image.BufferedImage;\nimport java.awt.image.BufferedImageOp;\nimport java.awt.image.ConvolveOp;\nimport java.awt.image.Kernel;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic class BlurFilter implements IndentFilter {\n    @Override\n    public BufferedImage apply(BufferedImage image) {\n        Kernel kernel = new Kernel(3, 3,\n            new float[] {\n                1f / 9f, 1f / 9f, 1f / 9f,\n                1f / 9f, 1f / 9f, 1f / 9f,\n                1f / 9f, 1f / 9f, 1f / 9f\n            }\n        );\n\n        BufferedImageOp blurOp = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);\n        return blurOp.filter(image, null);\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cropper/indent/IndentCropper.java",
    "content": "package pazone.ashot.cropper.indent;\n\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.cropper.DefaultCropper;\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.util.ImageTool;\n\nimport java.awt.Graphics;\nimport java.awt.image.BufferedImage;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Set;\n\nimport static pazone.ashot.coordinates.Coords.setReferenceCoords;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic class IndentCropper extends DefaultCropper {\n\n    public static final int DEFAULT_INDENT = 50;\n\n    private int indent;\n\n    protected final List<IndentFilter> filters = new LinkedList<>();\n\n    public IndentCropper(final int indent) {\n        this.indent = indent;\n    }\n\n    public IndentCropper() {\n        this(DEFAULT_INDENT);\n    }\n\n    @Override\n    public Screenshot cropScreenshot(BufferedImage image, Set<Coords> coordsToCompare) {\n        Coords cropArea = createCropArea(coordsToCompare);\n        Coords indentMask = createIndentMask(cropArea, image);\n        Coords coordsWithIndent = applyIndentMask(cropArea, indentMask);\n        Screenshot croppedShot = super.cropScreenshot(image, Collections.singleton(coordsWithIndent));\n        croppedShot.setOriginShift(coordsWithIndent);\n        croppedShot.setCoordsToCompare(setReferenceCoords(coordsWithIndent, coordsToCompare));\n        List<NoFilteringArea> noFilteringAreas = createNotFilteringAreas(croppedShot);\n        croppedShot.setImage(applyFilters(croppedShot.getImage()));\n        pasteAreasToCompare(croppedShot.getImage(), noFilteringAreas);\n        return croppedShot;\n    }\n\n    protected Coords applyIndentMask(Coords origin, Coords mask) {\n        Coords spreadCoords = new Coords(0, 0);\n        spreadCoords.x = origin.x - mask.x;\n        spreadCoords.y = origin.y - mask.y;\n        spreadCoords.height = mask.y + origin.height + mask.height;\n        spreadCoords.width = mask.x + origin.width + mask.width;\n        return spreadCoords;\n    }\n\n    protected Coords createIndentMask(Coords originCoords, BufferedImage image) {\n        Coords indentMask = new Coords(originCoords);\n        indentMask.x = Math.min(indent, originCoords.x);\n        indentMask.y = Math.min(indent, originCoords.y);\n        indentMask.width = Math.min(indent, image.getWidth() - originCoords.x - originCoords.width);\n        indentMask.height = Math.min(indent, image.getHeight() - originCoords.y - originCoords.height);\n        return indentMask;\n    }\n\n    protected List<NoFilteringArea> createNotFilteringAreas(Screenshot screenshot) {\n        List<NoFilteringArea> noFilteringAreas = new ArrayList<>();\n        for (Coords noFilteringCoords : screenshot.getCoordsToCompare()) {\n            if (noFilteringCoords.intersects(Coords.ofImage(screenshot.getImage()))) {\n                noFilteringAreas.add(new NoFilteringArea(screenshot.getImage(), noFilteringCoords));\n            }\n        }\n        return noFilteringAreas;\n    }\n\n    protected void pasteAreasToCompare(BufferedImage filtered, List<NoFilteringArea> noFilteringAreas) {\n        Graphics graphics = filtered.getGraphics();\n        for (NoFilteringArea noFilteringArea : noFilteringAreas) {\n            graphics.drawImage(\n                    noFilteringArea.getSubimage(),\n                    noFilteringArea.getCoords().x,\n                    noFilteringArea.getCoords().y,\n                    null);\n        }\n        graphics.dispose();\n    }\n\n    public IndentCropper addIndentFilter(IndentFilter filter) {\n        this.filters.add(filter);\n        return this;\n    }\n\n    protected BufferedImage applyFilters(BufferedImage image) {\n        for (IndentFilter filter : filters) {\n            image = filter.apply(image);\n        }\n        return image;\n    }\n\n    private static final class NoFilteringArea {\n        private final BufferedImage subimage;\n        private final Coords coords;\n\n        private NoFilteringArea(BufferedImage origin, Coords noFilterCoords) {\n            this.subimage = ImageTool.subImage(origin, noFilterCoords);\n            this.coords = noFilterCoords;\n        }\n\n        public BufferedImage getSubimage() {\n            return subimage;\n        }\n\n        public Coords getCoords() {\n            return coords;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cropper/indent/IndentFilerFactory.java",
    "content": "package pazone.ashot.cropper.indent;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic final class IndentFilerFactory {\n\n    private IndentFilerFactory() {\n        throw new UnsupportedOperationException();\n    }\n\n\n    public static BlurFilter blur() {\n        return new BlurFilter();\n    }\n\n    public static MonochromeFilter monochrome() {\n        return new MonochromeFilter();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cropper/indent/IndentFilter.java",
    "content": "package pazone.ashot.cropper.indent;\n\nimport java.awt.image.BufferedImage;\nimport java.io.Serializable;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic interface IndentFilter extends Serializable {\n\n    BufferedImage apply(BufferedImage image);\n\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cropper/indent/MonochromeFilter.java",
    "content": "package pazone.ashot.cropper.indent;\n\nimport pazone.ashot.util.ImageTool;\n\nimport java.awt.image.BufferedImage;\n\nimport javax.swing.GrayFilter;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic class MonochromeFilter implements IndentFilter {\n    @Override\n    public BufferedImage apply(BufferedImage image) {\n        return darken(image);\n    }\n\n    private BufferedImage darken(BufferedImage image) {\n        return ImageTool.toBufferedImage(GrayFilter.createDisabledImage(image));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cutter/CutStrategy.java",
    "content": "package pazone.ashot.cutter;\n\nimport org.openqa.selenium.WebDriver;\n\nimport java.io.Serializable;\n\n/**\n * @author <a href=\"frolic@yandex-team.ru\">Vyacheslav Frolov</a>\n */\npublic interface CutStrategy extends Serializable {\n\n    /**\n     * Obtains height of header that will be cut off from initial screenshot.\n     * @param driver - webDriver\n     * @return height of header in pixels\n     */\n    int getHeaderHeight(WebDriver driver);\n\n    /**\n     * Obtains height of footer that will be cut off from initial screenshot.\n     * @param driver - webDriver\n     * @return height of header in pixels\n     */\n    int getFooterHeight(WebDriver driver);\n\n    /**\n     * Obtains width of left bar that will be cut off from initial screenshot.\n     *\n     * @param driver - webDriver\n     * @return width of the left bar in pixels\n     */\n    default int getLeftBarWidth(WebDriver driver) {\n        return 0;\n    }\n\n    /**\n     * Obtains width of right bar that will be cut off from initial screenshot.\n     *\n     * @param driver - webDriver\n     * @return width of the right bar in pixels\n     */\n    default int getRightBarWidth(WebDriver driver) {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cutter/FixedCutStrategy.java",
    "content": "package pazone.ashot.cutter;\n\nimport org.openqa.selenium.WebDriver;\n\n/**\n * Strategy for cutting header and footer of a constant height.\n *\n * @author <a href=\"frolic@yandex-team.ru\">Vyacheslav Frolov</a>\n */\npublic class FixedCutStrategy implements CutStrategy {\n    private final int headerToCut;\n    private final int footerToCut;\n    private final int leftBarToCut;\n    private final int rightBarToCut;\n\n    public FixedCutStrategy(int headerToCut, int footerToCut) {\n        this(headerToCut, footerToCut, 0, 0);\n    }\n\n    public FixedCutStrategy(int headerToCut, int footerToCut, int leftBarToCut, int rightBarToCut) {\n        this.headerToCut = headerToCut;\n        this.footerToCut = footerToCut;\n        this.leftBarToCut = leftBarToCut;\n        this.rightBarToCut = rightBarToCut;\n    }\n\n    @Override\n    public int getHeaderHeight(WebDriver driver) {\n        return headerToCut;\n    }\n\n    @Override\n    public int getFooterHeight(WebDriver driver) {\n        return footerToCut;\n    }\n\n    @Override\n    public int getLeftBarWidth(WebDriver driver) {\n        return leftBarToCut;\n    }\n\n    @Override\n    public int getRightBarWidth(WebDriver driver) {\n        return rightBarToCut;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/cutter/VariableCutStrategy.java",
    "content": "package pazone.ashot.cutter;\n\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.InvalidViewportHeightException;\n\n/**\n * Strategy for cutting header and footer with variable height.<br>\n * For example, Safari browser in iOS 8 introduced a feature (so called 'minimal-ui')<br>\n * when browser's header might be 65px or 41px (with address bar hidden).<br>\n * This strategy will get current height of browser's header and footer.\n * @author <a href=\"frolic@yandex-team.ru\">Vyacheslav Frolov</a>\n */\npublic class VariableCutStrategy implements CutStrategy {\n\n    public static final String SCRIPT = \"var h = window.innerHeight || document.documentElement.clientHeight; \"\n            + \"return h;\";\n    private final int headerMin;\n    private final int headerMax;\n    private final int windowInnerHeightMin;\n    private final int footerMax;\n    private final int footerMin;\n\n    /**\n     * @param headerMin - minimal header height (for Safari iOS 8 it is 41px)\n     * @param headerMax - maximum header height (for Safari iOS 8 it is 65px)\n     * @param footerMin - minimal footer height (for Safari iOS 8 it is 0px)\n     * @param footerMax - maximum footer height (for Safari iOS 8 it is 89px)\n     * @param windowInnerHeightMin - minimal height of viewportPasting (when header with address bar shown).<br />\n     *                             For real device iPad 2 it is 960px (portrait) and 674px (landscape).<br />\n     *                             For simulated iPad 2 it is 1225px (portrait) and 687px (landscape).\n     */\n    public VariableCutStrategy(int headerMin, int headerMax, int footerMin, int footerMax, int windowInnerHeightMin) {\n        this.headerMin = headerMin;\n        this.headerMax = headerMax;\n        this.footerMin = footerMin;\n        this.footerMax = footerMax;\n        this.windowInnerHeightMin = windowInnerHeightMin;\n    }\n\n    public VariableCutStrategy(int headerMin, int headerMax, int footerMax, int windowInnerHeightMin) {\n        this(headerMin, headerMax, 0, footerMax, windowInnerHeightMin);\n    }\n\n    public VariableCutStrategy(int headerMin, int headerMax, int windowInnerHeightMin) {\n        this(headerMin, headerMax, 0, windowInnerHeightMin);\n    }\n\n    @Override\n    public int getHeaderHeight(WebDriver driver) {\n        return getCutHeight((JavascriptExecutor) driver, headerMin, headerMax);\n\n    }\n\n    @Override\n    public int getFooterHeight(WebDriver driver) {\n        if (0 == footerMax && 0 == footerMin) {\n            return 0;\n        }\n        return getCutHeight((JavascriptExecutor) driver, footerMin, footerMax);\n    }\n\n    private int getCutHeight(JavascriptExecutor driver, int heightMin, int heightMax) {\n        final int innerHeight = getWindowInnerHeight(driver);\n        return innerHeight > windowInnerHeightMin ? heightMin : heightMax;\n    }\n\n    private int getWindowInnerHeight(JavascriptExecutor driver) {\n        final Number innerHeight;\n\n        try {\n            innerHeight = (Number) driver.executeScript(SCRIPT);\n        } catch (ClassCastException e) {\n            throw new InvalidViewportHeightException(\"Could not acquire window.innerHeight property!\", e);\n        }\n\n        if (innerHeight == null) {\n            throw new InvalidViewportHeightException(\n                    \"Could not acquire window.innerHeight property! Returned value is null.\");\n        }\n\n        return innerHeight.intValue();\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/util/ImageBytesDiffer.java",
    "content": "package pazone.ashot.util;\n\nimport pazone.ashot.Screenshot;\n\nimport java.awt.image.BufferedImage;\nimport java.awt.image.DataBuffer;\n\n/**\n * @author <a href=\"frolic@yandex-team.ru\">Viacheslav Frolov</a>\n */\npublic final class ImageBytesDiffer {\n\n    private ImageBytesDiffer() {\n        throw new UnsupportedOperationException();\n    }\n\n    public static boolean areImagesEqual(Screenshot expected, Screenshot actual) {\n        return areImagesEqual(expected.getImage(), actual.getImage());\n    }\n\n    public static boolean areImagesEqual(BufferedImage expected, BufferedImage actual) {\n        return  expected.getHeight() == actual.getHeight()\n                && expected.getWidth() == actual.getWidth()\n                && actual.getColorModel().equals(expected.getColorModel())\n                && areImagesBuffersEqual(expected.getRaster().getDataBuffer(), actual.getRaster().getDataBuffer());\n    }\n\n    private static boolean areImagesBuffersEqual(DataBuffer expected, DataBuffer actual) {\n        return actual.getDataType() == expected.getDataType()\n                && actual.getNumBanks() == expected.getNumBanks()\n                && actual.getSize() == expected.getSize()\n                && areImagesBytesEqual(actual, expected);\n    }\n\n    private static boolean areImagesBytesEqual(DataBuffer expected, DataBuffer actual) {\n        for (int bank = 0; bank < expected.getNumBanks(); bank++) {\n            for (int i = 0; i < expected.getSize(); i++) {\n                if (expected.getElem(bank, i) != actual.getElem(bank, i)) {\n                    return false;\n                }\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/util/ImageTool.java",
    "content": "package pazone.ashot.util;\n\nimport org.hamcrest.Description;\nimport org.hamcrest.Matcher;\nimport org.hamcrest.TypeSafeMatcher;\nimport pazone.ashot.Screenshot;\nimport pazone.ashot.coordinates.Coords;\n\nimport javax.imageio.ImageIO;\nimport java.awt.Graphics2D;\nimport java.awt.Image;\nimport java.awt.image.BufferedImage;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic final class ImageTool {\n\n    private ImageTool() {\n        throw new UnsupportedOperationException();\n    }\n\n    public static BufferedImage subImage(BufferedImage origin, Coords crop) {\n        Coords intersection = Coords.ofImage(origin).intersection(crop);\n        return origin.getSubimage(intersection.x, intersection.y, intersection.width, intersection.height);\n    }\n\n    public static Coords spreadCoordsInsideImage(Coords coordinates, int indent, BufferedImage image) {\n        return new Coords(Math.max(0, coordinates.x - indent),\n                Math.max(0, coordinates.y - indent),\n                Math.min(image.getWidth(), coordinates.width + indent),\n                Math.min(image.getHeight(), coordinates.height + indent));\n\n    }\n\n    public static boolean rgbCompare(int rgb1, int rgb2, int inaccuracy) {\n        if (inaccuracy == 0) {\n            return rgb1 == rgb2;\n        }\n        int red1 = (rgb1 & 0x00FF0000) >> 16;\n        int green1 = (rgb1 & 0x0000FF00) >> 8;\n        int blue1 = (rgb1 & 0x000000FF);\n        int red2 = (rgb2 & 0x00FF0000) >> 16;\n        int green2 = (rgb2 & 0x0000FF00) >> 8;\n        int blue2 = (rgb2 & 0x000000FF);\n        return Math.abs(red1 - red2) <= inaccuracy\n                && Math.abs(green1 - green2) <= inaccuracy\n                && Math.abs(blue1 - blue2) <= inaccuracy;\n    }\n\n    public static Matcher<BufferedImage> equalImage(final BufferedImage second) {\n        return new TypeSafeMatcher<BufferedImage>() {\n\n            @Override\n            protected boolean matchesSafely(BufferedImage first) {\n                if (!Coords.ofImage(first).equals(Coords.ofImage(second))) {\n                    return false;\n                }\n                for (int x = 0; x < first.getWidth(); x++) {\n                    for (int y = 0; y < first.getHeight(); y++) {\n                        if (!rgbCompare(first.getRGB(x, y), second.getRGB(x, y), 10)) {\n                            return false;\n                        }\n                    }\n                }\n                return true;\n            }\n\n            @Override\n            public void describeTo(Description description) {\n\n            }\n        };\n    }\n\n    @SuppressWarnings(\"UnusedDeclaration\")\n    public static byte[] toByteArray(Screenshot screenshot) throws IOException {\n        return toByteArray(screenshot.getImage());\n    }\n\n    public static byte[] toByteArray(BufferedImage image) throws IOException {\n        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {\n            ImageIO.write(image, \"png\", baos);\n            return baos.toByteArray();\n        }\n    }\n\n    public static BufferedImage toBufferedImage(Image img) {\n        if (img instanceof BufferedImage) {\n            return (BufferedImage) img;\n        }\n\n        BufferedImage bufferedImage = new BufferedImage(\n            img.getWidth(null),\n            img.getHeight(null),\n            BufferedImage.TYPE_INT_ARGB\n        );\n        Graphics2D graphics = bufferedImage.createGraphics();\n        graphics.drawImage(img, 0, 0, null);\n        graphics.dispose();\n        return bufferedImage;\n    }\n\n    public static BufferedImage toBufferedImage(byte[] imageBytes) throws IOException {\n        try (ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes)) {\n            return ImageIO.read(bais);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/util/InnerScript.java",
    "content": "package pazone.ashot.util;\n\nimport java.nio.charset.StandardCharsets;\n\nimport org.apache.commons.io.IOUtils;\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.WebDriver;\n\nimport static java.lang.Thread.currentThread;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic final class InnerScript {\n\n    private InnerScript() {\n        throw new UnsupportedOperationException();\n    }\n\n    public static <T> T execute(String path, WebDriver driver, Object... args) {\n        try {\n            String script = IOUtils.toString(currentThread().getContextClassLoader().getResourceAsStream(path),\n                    StandardCharsets.UTF_8);\n            //noinspection unchecked\n            return (T) ((JavascriptExecutor) driver).executeScript(script, args);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/pazone/ashot/util/JsCoords.java",
    "content": "package pazone.ashot.util;\n\nimport java.util.List;\n\nimport com.google.gson.Gson;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport pazone.ashot.coordinates.Coords;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n\npublic final class JsCoords {\n\n    public static final String COORDS_JS_PATH = \"js/coords-single.js\";\n\n    private JsCoords() {\n        throw new UnsupportedOperationException();\n    }\n\n    public static Coords findCoordsWithJquery(WebDriver driver, WebElement element) {\n        List<?> result = InnerScript.execute(COORDS_JS_PATH, driver, element);\n        if (result.isEmpty()) {\n            throw new RuntimeException(\"Unable to find coordinates with jQuery.\");\n        }\n        return new Gson().fromJson((String) result.get(0), Coords.class);\n    }\n\n}\n"
  },
  {
    "path": "src/main/resources/js/coords-single.js",
    "content": "function Coords(el) {\n    this.left = parseInt(el.offset().left);\n    this.top = parseInt(el.offset().top);\n    this.right = parseInt(this.left + el.outerWidth());\n    this.bottom = parseInt(this.top + el.outerHeight());\n}\n\nCoords.prototype.toString = function () {\n    var x = Math.max(this.left, 0);\n    var y = Math.max(this.top, 0);\n    return JSON.stringify({\n        x:x,\n        y:y,\n        width:this.right - x,\n        height:this.bottom - y\n    });\n};\n\nreturn [(new Coords($(arguments[0]))).toString()];\n"
  },
  {
    "path": "src/main/resources/js/page_dimensions.js",
    "content": "var body = document.body;\nvar documentElement = document.documentElement;\nvar pageHeight = Math.max(body.scrollHeight, body.offsetHeight, documentElement.clientHeight,\n    documentElement.scrollHeight, documentElement.offsetHeight);\nvar viewportHeight = window.innerHeight || documentElement.clientHeight|| body.clientHeight;\nvar viewportWidth = window.innerWidth || documentElement.clientWidth || body.clientWidth;\nreturn {\"pageHeight\": pageHeight, \"viewportHeight\": viewportHeight, \"viewportWidth\": viewportWidth}"
  },
  {
    "path": "src/test/java/pazone/ashot/CdpShootingStrategyTest.java",
    "content": "package pazone.ashot;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\nimport java.util.Base64;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.chromium.HasCdp;\n\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.util.ImageTool;\nimport pazone.ashot.util.TestImageUtils;\n\n@ExtendWith(MockitoExtension.class)\nclass CdpShootingStrategyTest {\n    private final ShootingStrategy strategy = new CdpShootingStrategy();\n\n    @Mock(extraInterfaces = HasCdp.class)\n    private WebDriver webDriver;\n\n    @Test\n    void testPageScreenshot() throws IOException {\n        BufferedImage expected = TestImageUtils.IMAGE_A_SMALL;\n        String base = Base64.getEncoder().encodeToString(ImageTool.toByteArray(expected));\n\n        when(((HasCdp) webDriver).executeCdpCommand(\"Page.captureScreenshot\", Map.of(\"captureBeyondViewport\", true)))\n                .thenReturn(Map.of(\"data\", base));\n\n        BufferedImage actual = strategy.getScreenshot(webDriver);\n        TestImageUtils.assertImageEquals(actual, expected);\n    }\n\n    @Test\n    void testElementScreenshot() throws IOException {\n        BufferedImage expected = TestImageUtils.IMAGE_A_SMALL;\n        String base = Base64.getEncoder().encodeToString(ImageTool.toByteArray(expected));\n        Coords coords = new Coords(1, 2, 3, 4);\n\n        when(((HasCdp) webDriver).executeCdpCommand(\"Page.captureScreenshot\", Map.of(\"captureBeyondViewport\", true,\n                \"clip\",\n                Map.of(\"x\", coords.x, \"y\", coords.y, \"width\", coords.width, \"height\", coords.height, \"scale\", 1))))\n                        .thenReturn(Map.of(\"data\", base));\n\n        BufferedImage actual = strategy.getScreenshot(webDriver, Set.of(coords));\n        TestImageUtils.assertImageEquals(actual, expected);\n    }\n\n    @Test\n    void testUnsupportedCdp() {\n        WebDriver driver = mock(WebDriver.class);\n        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,\n                () -> strategy.getScreenshot(driver));\n        assertEquals(\"WebDriver instance must support Chrome DevTools protocol\", thrown.getMessage());\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/CroppersTest.java",
    "content": "package pazone.ashot;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport pazone.ashot.coordinates.Coords;\nimport pazone.ashot.cropper.DefaultCropper;\nimport pazone.ashot.cropper.ImageCropper;\nimport pazone.ashot.cropper.indent.IndentCropper;\nimport pazone.ashot.cropper.indent.IndentFilerFactory;\n\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\nimport static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;\nimport static pazone.ashot.util.TestImageUtils.assertImageEquals;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\nclass CroppersTest {\n\n    private static final Set<Coords> OUTSIDE_IMAGE = Collections.singleton(new Coords(20, 20, 200, 90));\n    private static final Set<Coords> INSIDE_IMAGE = Collections.singleton(new Coords(20, 20, 30, 30));\n\n    static Stream<Arguments> outsideCropperData() {\n        return Stream.of(\n                Arguments.of(new DefaultCropper(), \"img/expected/outside_dc.png\"),\n                Arguments.of(new IndentCropper(10), \"img/expected/outside_ic.png\")\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"outsideCropperData\")\n    void testElementOutsideImageCropper(ImageCropper cropper, String expectedImagePath) {\n        Screenshot screenshot = cropper.crop(IMAGE_A_SMALL, OUTSIDE_IMAGE);\n        assertImageEquals(screenshot.getImage(), expectedImagePath);\n    }\n\n    @Test\n    void testElementInsideImageIndentCropperWithFilter() {\n        Screenshot screenshot = new IndentCropper()\n            .addIndentFilter(IndentFilerFactory.blur())\n            .addIndentFilter(IndentFilerFactory.monochrome())\n            .cropScreenshot(IMAGE_A_SMALL, INSIDE_IMAGE);\n        assertImageEquals(screenshot.getImage(), \"img/expected/inside_icf.png\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/CuttingDecoratorTest.java",
    "content": "package pazone.ashot;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static pazone.ashot.util.TestImageUtils.assertImageEquals;\n\nimport java.util.function.UnaryOperator;\n\nimport org.junit.jupiter.api.Test;\n\nimport pazone.ashot.cutter.FixedCutStrategy;\nimport pazone.ashot.util.TestImageUtils;\n\nclass CuttingDecoratorTest {\n    public static final String CUT_FROM_ALL_THE_SIDES_PNG = \"cut-from-all-the-sides.png\";\n\n    @Test\n    void shouldCutFromAllTheSides() {\n        testCuttingFromAllTheSides(cd -> cd.withCut(35, 35, 40, 40), CUT_FROM_ALL_THE_SIDES_PNG);\n    }\n\n    @Test\n    void shouldCutFromAllTheSidesUsingFixedCutStrategy() {\n        testCuttingFromAllTheSides(cd -> cd.withCutStrategy(new FixedCutStrategy(35, 35, 40, 40)),\n            CUT_FROM_ALL_THE_SIDES_PNG);\n    }\n\n    @Test\n    void shouldCutOnlyFooterAndHeader() {\n        testCuttingFromAllTheSides(cd -> cd.withCut(35, 35), \"cut-footer-header.png\");\n    }\n\n    private void testCuttingFromAllTheSides(UnaryOperator<CuttingDecorator> settings, String expectedImageName) {\n        ShootingStrategy shootingStrategy = mock(ShootingStrategy.class);\n        CuttingDecorator cuttingDecorator = new CuttingDecorator(shootingStrategy);\n        when(shootingStrategy.getScreenshot(null)).thenReturn(TestImageUtils.IMAGE_B_SMALL);\n        assertImageEquals(settings.apply(cuttingDecorator).getScreenshot(null),  \"img/expected/\" + expectedImageName);\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/DiffMarkupPolicyTest.java",
    "content": "package pazone.ashot;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport pazone.ashot.comparison.DiffMarkupPolicy;\nimport pazone.ashot.comparison.ImageMarkupPolicy;\nimport pazone.ashot.comparison.PointsMarkupPolicy;\n\nimport java.awt.Point;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.not;\nimport static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;\nimport static pazone.ashot.util.TestImageUtils.IMAGE_B_SMALL;\n\n/**\n * @author Rovniakov Viacheslav rovner@yandex-team.ru\n */\nclass DiffMarkupPolicyTest {\n\n    private static Stream<Arguments> data() {\n        return Stream.of(\n                Arguments.of(new PointsMarkupPolicy(), new PointsMarkupPolicy()),\n                Arguments.of(new ImageMarkupPolicy(), new ImageMarkupPolicy())\n        );\n    }\n\n    private void initDiffMarkupPolicies(DiffMarkupPolicy diffMarkupPolicyA, DiffMarkupPolicy diffMarkupPolicyB) {\n        diffMarkupPolicyA.setDiffImage(IMAGE_A_SMALL);\n        diffMarkupPolicyB.setDiffImage(IMAGE_B_SMALL);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testEquality(DiffMarkupPolicy diffMarkupPolicyA, DiffMarkupPolicy diffMarkupPolicyB) {\n        initDiffMarkupPolicies(diffMarkupPolicyA, diffMarkupPolicyB);\n        addDiffPoints(getDiffPointsA(), diffMarkupPolicyA, 1, 2);\n        addDiffPoints(getDiffPointsA(), diffMarkupPolicyB, 0, 3);\n        assertThat(diffMarkupPolicyA, equalTo(diffMarkupPolicyB));\n        assertThat(diffMarkupPolicyA.hashCode(), equalTo(diffMarkupPolicyB.hashCode()));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testNotEquality(DiffMarkupPolicy diffMarkupPolicyA, DiffMarkupPolicy diffMarkupPolicyB) {\n        initDiffMarkupPolicies(diffMarkupPolicyA, diffMarkupPolicyB);\n        addDiffPoints(getDiffPointsA(), diffMarkupPolicyA, 0, 0);\n        addDiffPoints(getDiffPointsB(), diffMarkupPolicyB, 0, 0);\n        assertThat(diffMarkupPolicyA, not(equalTo(diffMarkupPolicyB)));\n        assertThat(diffMarkupPolicyA.hashCode(), not(equalTo(diffMarkupPolicyB.hashCode())));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testNotEqualityByNumber(DiffMarkupPolicy diffMarkupPolicyA, DiffMarkupPolicy diffMarkupPolicyB) {\n        initDiffMarkupPolicies(diffMarkupPolicyA, diffMarkupPolicyB);\n        addDiffPoints(getDiffPointsA(), diffMarkupPolicyA, 0, 0);\n        addDiffPoints(getDiffPointsB(), diffMarkupPolicyA, 0, 0);\n        diffMarkupPolicyB.addDiffPoint(0, 0);\n        assertThat(diffMarkupPolicyA, not(equalTo(diffMarkupPolicyB)));\n        assertThat(diffMarkupPolicyA.hashCode(), not(equalTo(diffMarkupPolicyB.hashCode())));\n    }\n\n    private Set<Point> getDiffPointsA() {\n        return new HashSet<Point>() {{\n            add(new Point(3, 4));\n            add(new Point(3, 5));\n            add(new Point(3, 6));\n            add(new Point(4, 4));\n            add(new Point(4, 5));\n            add(new Point(4, 6));\n        }};\n    }\n\n    private Set<Point> getDiffPointsB() {\n        return new HashSet<Point>() {{\n            add(new Point(3, 4));\n            add(new Point(3, 5));\n            add(new Point(3, 6));\n            add(new Point(4, 4));\n            add(new Point(4, 5));\n            add(new Point(4, 7));\n        }};\n    }\n\n    private void addDiffPoints(Set<Point> points, DiffMarkupPolicy diffMarkupPolicy, int xShift, int yShift) {\n        for (Point point : points) {\n            diffMarkupPolicy.addDiffPoint((int) point.getX() - xShift, (int) point.getY() - yShift);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/DifferTest.java",
    "content": "package pazone.ashot;\n\nimport pazone.ashot.comparison.DiffMarkupPolicy;\nimport pazone.ashot.comparison.ImageDiff;\nimport pazone.ashot.comparison.ImageDiffer;\nimport pazone.ashot.comparison.ImageMarkupPolicy;\nimport pazone.ashot.comparison.PointsMarkupPolicy;\nimport pazone.ashot.coordinates.Coords;\n\nimport java.awt.Color;\nimport java.awt.image.BufferedImage;\nimport java.util.Collections;\nimport java.util.stream.Stream;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertAll;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;\nimport static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL_PATH;\nimport static pazone.ashot.util.TestImageUtils.IMAGE_B_SMALL;\nimport static pazone.ashot.util.TestImageUtils.assertImageEquals;\nimport static pazone.ashot.util.TestImageUtils.loadImage;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\nclass DifferTest {\n\n    private static final BufferedImage IMAGE_B_BIG = loadImage(\"img/B_b.png\");\n    private static final String IMAGE_IGNORED_TEMPLATE = \"img/ignore_color_template.png\";\n    private static final String IMAGE_IGNORED_PASS = \"img/ignore_color_pass.png\";\n    private static final String IMAGE_IGNORED_FAIL = \"img/ignore_color_fail.png\";\n\n    private static Stream<DiffMarkupPolicy> data() {\n        return Stream.of(new PointsMarkupPolicy(), new ImageMarkupPolicy());\n    }\n\n    private ImageDiffer createImageDiffer(DiffMarkupPolicy diffMarkupPolicy) {\n        return new ImageDiffer()\n                .withColorDistortion(10)\n                .withDiffMarkupPolicy(diffMarkupPolicy.withDiffColor(Color.RED));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testSameSizeDiff(DiffMarkupPolicy diffMarkupPolicy) {\n        ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(IMAGE_A_SMALL, IMAGE_B_SMALL);\n\n        assertAll(\n                () -> assertImageEquals(diff.getMarkedImage(), \"img/expected/same_size_diff.png\"),\n                () -> assertImageEquals(diff.getTransparentMarkedImage(), \"img/expected/transparent_diff.png\"),\n                () -> assertThat(diff.withDiffSizeTrigger(624).hasDiff(), is(false)),\n                () -> assertThat(diff.withDiffSizeTrigger(623).hasDiff(), is(true))\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testDifferentSizeDiff(DiffMarkupPolicy diffMarkupPolicy) {\n        ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(IMAGE_B_SMALL, IMAGE_B_BIG);\n        assertImageEquals(diff.getMarkedImage(), \"img/expected/different_size_diff.png\");\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testSetDiffColor(DiffMarkupPolicy diffMarkupPolicy) {\n        ImageDiffer greenDiffer = new ImageDiffer()\n                .withDiffMarkupPolicy(diffMarkupPolicy.withDiffColor(Color.GREEN));\n        ImageDiff diff = greenDiffer.makeDiff(IMAGE_A_SMALL, IMAGE_B_SMALL);\n        assertImageEquals(diff.getMarkedImage(), \"img/expected/green_diff.png\");\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testEqualImagesDiff(DiffMarkupPolicy diffMarkupPolicy) {\n        ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(IMAGE_A_SMALL, IMAGE_A_SMALL);\n        assertFalse(diff.hasDiff());\n    }\n\n    static Stream<Arguments> dataWithIgnoredColorDiff() {\n        return Stream.of(\n                Arguments.of(Color.MAGENTA, IMAGE_IGNORED_PASS, false),\n                Arguments.of(Color.MAGENTA, IMAGE_IGNORED_FAIL, true),\n                Arguments.of(Color.RED,     IMAGE_IGNORED_PASS, true)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"dataWithIgnoredColorDiff\")\n    void testDiffImagesWithIgnoredColorDiff(Color ignoredColor, String imageToCompare, boolean hasDiff) {\n        ImageDiffer imageDifferWithIgnored = new ImageDiffer().withIgnoredColor(ignoredColor);\n        ImageDiff diff = imageDifferWithIgnored.makeDiff(loadImage(IMAGE_IGNORED_TEMPLATE), loadImage(imageToCompare));\n        assertThat(diff.hasDiff(), equalTo(hasDiff));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testIgnoredCoordsSame(DiffMarkupPolicy diffMarkupPolicy) {\n        Screenshot a = createScreenshotWithIgnoredAreas(IMAGE_A_SMALL, new Coords(50, 50));\n        Screenshot b = createScreenshotWithIgnoredAreas(IMAGE_B_SMALL, new Coords(50, 50));\n        ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(a, b);\n        assertImageEquals(diff.getMarkedImage(), \"img/expected/ignore_coords_same.png\");\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testIgnoredCoordsNotSame(DiffMarkupPolicy diffMarkupPolicy) {\n        Screenshot a = createScreenshotWithIgnoredAreas(IMAGE_A_SMALL, new Coords(55, 55));\n        Screenshot b = createScreenshotWithIgnoredAreas(IMAGE_B_SMALL, new Coords(80, 80));\n        ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(a, b);\n        assertImageEquals(diff.getMarkedImage(), \"img/expected/ignore_coords_not_same.png\");\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testCoordsToCompareAndIgnoredCombine(DiffMarkupPolicy diffMarkupPolicy) {\n        Screenshot a = createScreenshotWithIgnoredAreas(IMAGE_A_SMALL, new Coords(60, 60));\n        a.setCoordsToCompare(Collections.singleton(new Coords(50, 50, 100, 100)));\n        Screenshot b = createScreenshotWithIgnoredAreas(IMAGE_B_SMALL, new Coords(80, 80));\n        b.setCoordsToCompare(Collections.singleton(new Coords(50, 50, 100, 100)));\n        ImageDiff diff = createImageDiffer(diffMarkupPolicy).makeDiff(a, b);\n        assertImageEquals(diff.getMarkedImage(), \"img/expected/combined_diff.png\");\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    void testDiffSize(DiffMarkupPolicy diffMarkupPolicy) {\n        String path = IMAGE_A_SMALL_PATH;\n        BufferedImage image1 = loadImage(path);\n        BufferedImage image2 = loadImage(path);\n\n        int rgb = Color.GREEN.getRGB();\n        int diffSize = 10;\n        for (int i = 0; i < diffSize; i++) {\n            image2.setRGB(i, 1, rgb);\n        }\n\n        ImageDiff imageDiff = createImageDiffer(diffMarkupPolicy).makeDiff(image1, image2);\n        assertEquals(diffSize, imageDiff.getDiffSize(), \"Should have diff size \" + diffSize);\n    }\n\n    private Screenshot createScreenshotWithIgnoredAreas(BufferedImage image, Coords ignoredArea) {\n        Screenshot screenshot = new Screenshot(image);\n        screenshot.setIgnoredAreas(Collections.singleton(ignoredArea));\n        return screenshot;\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/ImageBytesDifferTest.java",
    "content": "package pazone.ashot;\n\nimport org.junit.jupiter.api.DynamicTest;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestFactory;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.DynamicTest.dynamicTest;\nimport static pazone.ashot.util.ImageBytesDiffer.areImagesEqual;\nimport static pazone.ashot.util.TestImageUtils.loadImage;\n\nimport java.util.stream.Stream;\n\n/**\n * @author <a href=\"frolic@yandex-team.ru\">Viacheslav Frolov</a>\n */\nclass ImageBytesDifferTest {\n\n    @TestFactory\n    Stream<DynamicTest> testDifferentImages() {\n        return Stream.of(\n                dynamicTest(\"Different size\", () -> testDifferentImages(\"img/SolidColor_scaled.png\")),\n                dynamicTest(\"One pixel difference\", () -> testDifferentImages(\"img/SolidColor_1px_red.png\")),\n                dynamicTest(\"Color mode difference\", () -> testDifferentImages(\"img/SolidColor_indexed.png\"))\n        );\n    }\n\n    void testDifferentImages(String path) {\n        assertFalse(areImagesEqual(loadImage(\"img/SolidColor.png\"), loadImage(path)), \"Images should differ\");\n    }\n\n    @Test\n    void testEqualImages() {\n        assertTrue(areImagesEqual(loadImage(\"img/SolidColor.png\"), loadImage(\"img/SolidColor.png\")),\n                \"Images should equal\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/RotatingDecoratorTest.java",
    "content": "package pazone.ashot;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.openqa.selenium.OutputType;\nimport org.openqa.selenium.TakesScreenshot;\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.cutter.FixedCutStrategy;\nimport pazone.ashot.util.ImageTool;\n\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\n\nimport static org.mockito.Mockito.when;\nimport static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;\nimport static pazone.ashot.util.TestImageUtils.assertImageEquals;\n\n/**\n * @author <a href=\"rovner@yandex-team.ru\">Rovniakov Viacheslav</a>\n */\n@ExtendWith(MockitoExtension.class)\nclass RotatingDecoratorTest {\n\n    @Mock(extraInterfaces = TakesScreenshot.class)\n    private WebDriver webDriver;\n\n    @Test\n    void testRotating() throws IOException {\n        when(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES)).thenReturn(\n                ImageTool.toByteArray(IMAGE_A_SMALL));\n        ShootingStrategy strategy = new RotatingDecorator(new FixedCutStrategy(0, 0), new SimpleShootingStrategy());\n        BufferedImage screenshot = strategy.getScreenshot(webDriver);\n        assertImageEquals(screenshot, \"img/expected/rotated.png\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/ScalingDecoratorTest.java",
    "content": "package pazone.ashot;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.openqa.selenium.OutputType;\nimport org.openqa.selenium.TakesScreenshot;\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.util.ImageTool;\nimport pazone.ashot.util.TestImageUtils;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.when;\nimport static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;\nimport static pazone.ashot.util.TestImageUtils.assertImageEquals;\n\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\n\n/**\n * @author <a href=\"pazone@yandex-team.ru\">Pavel Zorin</a>\n */\n@ExtendWith(MockitoExtension.class)\nclass ScalingDecoratorTest {\n\n    @Mock(extraInterfaces = TakesScreenshot.class)\n    private WebDriver webDriver;\n\n    @CsvSource({\n        \"2, img/expected/dpr.png\",\n        \"1, \" + TestImageUtils.IMAGE_A_SMALL_PATH\n    })\n    @ParameterizedTest\n    void testDpr(float dpr, String expectedImagePath) throws IOException {\n        when(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES)).thenReturn(\n                ImageTool.toByteArray(IMAGE_A_SMALL));\n        ShootingStrategy dpr2Strategy = new ScalingDecorator(new SimpleShootingStrategy()).withDpr(dpr);\n        BufferedImage screenshot = dpr2Strategy.getScreenshot(webDriver);\n        assertImageEquals(screenshot, expectedImagePath);\n        assertEquals(IMAGE_A_SMALL.getType(), screenshot.getType());\n    }\n}\n\n"
  },
  {
    "path": "src/test/java/pazone/ashot/SerializeScreenshotTest.java",
    "content": "package pazone.ashot;\n\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport pazone.ashot.coordinates.Coords;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static pazone.ashot.util.TestImageUtils.IMAGE_A_SMALL;\nimport static pazone.ashot.util.TestImageUtils.assertImageEquals;\n\n/**\n * @author <a href=\"eoff@yandex-team.ru\">Maksim Mukosey</a>\n */\nclass SerializeScreenshotTest {\n\n    static Stream<Set<Coords>> ignoredAreas() {\n        return Stream.of(\n                Collections.emptySet(),\n                Collections.singleton(new Coords(20, 20, 200, 90))\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"ignoredAreas\")\n    void testSerialization(Set<Coords> ignoredAreas, @TempDir Path tempDir) throws IOException, ClassNotFoundException {\n        File serializedFile = tempDir.resolve(\"serialized\").toFile();\n        Screenshot screenshot = new Screenshot(IMAGE_A_SMALL);\n        screenshot.setIgnoredAreas(ignoredAreas);\n\n        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(serializedFile))) {\n            objectOutputStream.writeObject(screenshot);\n        }\n\n        try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(serializedFile))) {\n            Screenshot deserialized = (Screenshot) objectInputStream.readObject();\n            assertThat(deserialized.getCoordsToCompare(), equalTo(screenshot.getCoordsToCompare()));\n            assertThat(deserialized.getIgnoredAreas(), equalTo(screenshot.getIgnoredAreas()));\n            assertImageEquals(deserialized.getImage(), screenshot.getImage());\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/VariableCutStrategyTest.java",
    "content": "package pazone.ashot;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.cutter.CutStrategy;\nimport pazone.ashot.cutter.VariableCutStrategy;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.when;\n\n/**\n * @author <a href=\"frolic@yandex-team.ru\">Vyacheslav Frolov</a>\n */\n@ExtendWith(MockitoExtension.class)\nclass VariableCutStrategyTest {\n\n    private static final int MAX_HEADER_HEIGHT = 65;\n    private static final int MIN_HEADER_HEIGHT = 41;\n    private static final int MIN_INNER_HEIGHT = 960;\n\n    private final CutStrategy strategy = new VariableCutStrategy(MIN_HEADER_HEIGHT, MAX_HEADER_HEIGHT,\n            MIN_INNER_HEIGHT);\n\n    @Mock(extraInterfaces = JavascriptExecutor.class)\n    private WebDriver webDriver;\n\n    @ParameterizedTest\n    @CsvSource({\n            \"960, 65\",\n            \"984, 41\"\n    })\n    void testGetBrowserHeaderHeight(long viewportHeight, int browserHeaderHeight) {\n        mockViewportInnerHeight(viewportHeight);\n        int headerHeight = strategy.getHeaderHeight(webDriver);\n        assertThat(\"Header height should be detected correctly\", browserHeaderHeight, is(headerHeight));\n    }\n\n    @ParameterizedTest\n    @CsvSource({\n            \"a string\",\n            \",\"\n    })\n    void testGetBrowserHeaderHeightWithInvalidViewportHeight(String viewportHeight) {\n        mockViewportInnerHeight(viewportHeight);\n        assertThrows(InvalidViewportHeightException.class, () -> strategy.getHeaderHeight(webDriver));\n    }\n\n    private void mockViewportInnerHeight(Object viewportHeight) {\n        when(((JavascriptExecutor) webDriver).executeScript(VariableCutStrategy.SCRIPT)).thenReturn(viewportHeight);\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/VerticalPastingShootingStrategyTest.java",
    "content": "package pazone.ashot;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.Mock;\nimport org.mockito.Spy;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.OutputType;\nimport org.openqa.selenium.TakesScreenshot;\nimport org.openqa.selenium.WebDriver;\nimport pazone.ashot.coordinates.Coords;\n\nimport javax.imageio.ImageIO;\nimport java.awt.image.BufferedImage;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\nimport static java.awt.image.BufferedImage.TYPE_4BYTE_ABGR_PRE;\nimport static java.util.Collections.singleton;\nimport static org.hamcrest.CoreMatchers.everyItem;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.hasProperty;\nimport static org.hamcrest.Matchers.is;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(MockitoExtension.class)\nclass VerticalPastingShootingStrategyTest {\n\n    private static final int VIEWPORT_HEIGHT = 80;\n    private static final int DEFAULT_PAGE_HEIGHT = VIEWPORT_HEIGHT * 10 + VIEWPORT_HEIGHT / 2;\n    private static final int PAGE_WIDTH = 100;\n    private static final int DEFAULT_COORDS_INDENT = VIEWPORT_HEIGHT / 2;\n\n    private final BufferedImage viewPortShot = new BufferedImage(PAGE_WIDTH, VIEWPORT_HEIGHT, TYPE_4BYTE_ABGR_PRE);\n    private Coords shootingCoords;\n    private BufferedImage screenshot;\n    private Set<Coords> preparedCoords;\n\n    @Mock(extraInterfaces = {JavascriptExecutor.class, TakesScreenshot.class})\n    private WebDriver webDriver;\n\n    @Spy\n    private ViewportPastingDecorator shootingStrategy = new MockVerticalPastingShootingDecorator(\n            new SimpleShootingStrategy()).withScrollTimeout(0);\n\n    static Stream<Arguments> timesData() {\n        return Stream.of(\n                Arguments.of(VIEWPORT_HEIGHT,     2),\n                Arguments.of(VIEWPORT_HEIGHT / 2, 1),\n                Arguments.of(VIEWPORT_HEIGHT * 3, 4),\n                Arguments.of(0,                   1)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"timesData\")\n    void testTimes(int height, int times) throws IOException {\n        givenCoordsWithHeight(height);\n        whenTakingScreenshot(shootingCoords);\n        thenShootTimes(times);\n        thenScreenshotIsHeight(height + DEFAULT_COORDS_INDENT);\n    }\n\n    @Test\n    void testCoordsShiftWithDefaultIndent() throws IOException {\n        givenCoordsWithHeight(VIEWPORT_HEIGHT / 3);\n        whenTakingScreenshot(shootingCoords);\n        whenPreparingCoords(singleton(shootingCoords));\n        thenCoordsShifted();\n    }\n\n    @Test\n    void testScreenshotFullPage() throws IOException {\n        whenTakingScreenshot();\n        thenShootTimes(11);\n        thenScreenshotIsHeight(DEFAULT_PAGE_HEIGHT);\n    }\n\n    private byte[] getImageAsBytes() throws IOException {\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        ImageIO.write(viewPortShot, \"PNG\", baos);\n        return baos.toByteArray();\n    }\n\n    private void mockScreenshotting() throws IOException {\n        when(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES)).thenReturn(getImageAsBytes());\n    }\n\n    private void givenCoordsWithHeight(int height) {\n        shootingCoords = new Coords(0, VIEWPORT_HEIGHT * 4, PAGE_WIDTH, height);\n    }\n\n    private void whenTakingScreenshot() throws IOException {\n        mockScreenshotting();\n        screenshot = shootingStrategy.getScreenshot(webDriver);\n    }\n\n    private void whenTakingScreenshot(Coords coords) throws IOException {\n        mockScreenshotting();\n        screenshot = shootingStrategy.getScreenshot(webDriver, singleton(coords));\n    }\n\n    private void whenPreparingCoords(Set<Coords> coords) {\n        preparedCoords = shootingStrategy.prepareCoords(coords);\n    }\n\n    private void thenCoordsShifted() {\n        assertThat(\"Coords should be shifted correctly\", preparedCoords,\n                everyItem(hasProperty(\"y\", is((double) DEFAULT_COORDS_INDENT / 2))));\n    }\n\n    private void thenScreenshotIsHeight(int shotHeight) {\n        assertThat(\"Screenshot height should be correct\", screenshot.getHeight(), is(shotHeight));\n    }\n\n    private void thenShootTimes(int times) {\n        verify(((TakesScreenshot) webDriver), times(times)).getScreenshotAs(OutputType.BYTES);\n        verify(shootingStrategy, times(times + 1)).scrollVertically(eq((JavascriptExecutor) webDriver), anyInt());\n    }\n\n    static class MockVerticalPastingShootingDecorator extends ViewportPastingDecorator {\n\n        MockVerticalPastingShootingDecorator(ShootingStrategy strategy) {\n            super(strategy);\n        }\n\n        @Override\n        protected PageDimensions getPageDimensions(WebDriver driver) {\n            return new PageDimensions(DEFAULT_PAGE_HEIGHT, PAGE_WIDTH, VIEWPORT_HEIGHT);\n        }\n\n        @Override\n        public int getCurrentScrollY(JavascriptExecutor js) {\n            return 0;\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/coordinates/WebDriverCoordsProviderTest.java",
    "content": "package pazone.ashot.coordinates;\n\nimport static org.junit.jupiter.api.Assertions.assertAll;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.Test;\nimport org.openqa.selenium.Dimension;\nimport org.openqa.selenium.Point;\nimport org.openqa.selenium.WebElement;\n\nclass WebDriverCoordsProviderTest {\n    private WebDriverCoordsProvider webDriverCoordsProvider = new WebDriverCoordsProvider();\n\n    @Test\n    void testGetCoordinatesOfElement() {\n        WebElement element = mock(WebElement.class);\n        int x = 1;\n        int y = 2;\n        int height = 3;\n        int width = 4;\n        when(element.getLocation()).thenReturn(new Point(x, y));\n        when(element.getSize()).thenReturn(new Dimension(width, height));\n        Coords coords = webDriverCoordsProvider.ofElement(null, element);\n        assertAll(\n                () -> assertEquals(x, coords.x),\n                () -> assertEquals(y, coords.y),\n                () -> assertEquals(height, coords.height),\n                () -> assertEquals(width, coords.width)\n        );\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/util/ImageToolTest.java",
    "content": "package pazone.ashot.util;\n\nimport java.io.IOException;\n\nimport org.junit.jupiter.api.Test;\n\nclass ImageToolTest {\n    @Test\n    void shouldConvertImageToBytesAndViceVersa() throws IOException {\n        byte[] imageBytes = ImageTool.toByteArray(TestImageUtils.IMAGE_A_SMALL);\n        TestImageUtils.assertImageEquals(TestImageUtils.IMAGE_A_SMALL, ImageTool.toBufferedImage(imageBytes));\n    }\n}\n"
  },
  {
    "path": "src/test/java/pazone/ashot/util/TestImageUtils.java",
    "content": "package pazone.ashot.util;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.awt.image.BufferedImage;\nimport java.io.IOException;\n\nimport javax.imageio.ImageIO;\n\npublic final class TestImageUtils {\n\n    public static final String IMAGE_A_SMALL_PATH = \"img/A_s.png\";\n    public static final BufferedImage IMAGE_A_SMALL = loadImage(IMAGE_A_SMALL_PATH);\n    public static final BufferedImage IMAGE_B_SMALL = loadImage(\"img/B_s.png\");\n\n    private TestImageUtils() {\n    }\n\n    public static BufferedImage loadImage(String path) {\n        try {\n            return ImageIO.read(ClassLoader.getSystemResourceAsStream(path));\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static void assertImageEquals(BufferedImage actualImage, String expectedImagePath) {\n        assertImageEquals(actualImage, loadImage(expectedImagePath));\n    }\n\n    public static void assertImageEquals(BufferedImage actualImage, BufferedImage expectedImage) {\n        assertThat(actualImage, ImageTool.equalImage(expectedImage));\n    }\n}\n"
  }
]