Added project
This commit is contained in:
66
.gitignore
vendored
66
.gitignore
vendored
@@ -1,52 +1,14 @@
|
|||||||
# ---> Kotlin
|
/target/
|
||||||
# Compiled class file
|
/.idea/
|
||||||
*.class
|
/*.iml
|
||||||
|
/chromedriver
|
||||||
# Log file
|
/geckodriver
|
||||||
*.log
|
/operadriver
|
||||||
|
/*.svg
|
||||||
# BlueJ files
|
/*.png
|
||||||
*.ctxt
|
/*.properties
|
||||||
|
/svgs/
|
||||||
# Mobile Tools for Java (J2ME)
|
/out/
|
||||||
.mtj.tmp/
|
/*.html
|
||||||
|
/*.exe
|
||||||
# Package Files #
|
/*.log
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
||||||
replay_pid*
|
|
||||||
|
|
||||||
# ---> Java
|
|
||||||
# Compiled class file
|
|
||||||
*.class
|
|
||||||
|
|
||||||
# Log file
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# BlueJ files
|
|
||||||
*.ctxt
|
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.jar
|
|
||||||
*.war
|
|
||||||
*.nar
|
|
||||||
*.ear
|
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
|
||||||
replay_pid*
|
|
||||||
|
|
||||||
20
LICENSE
20
LICENSE
@@ -1,9 +1,21 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) <year> <copyright holders>
|
Copyright (c) 2018 Alexandr
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|||||||
48
README.md
48
README.md
@@ -1,10 +1,52 @@
|
|||||||
# visual-quality-control-tool
|
[](https://gitlab.resprojects.ru/mrresident/visual-quality-control-tool/commits/master)
|
||||||
|
|
||||||
VQCT (Visual quality control tool)
|
# VQCT (Visual quality control tool)
|
||||||
|
|
||||||
Основные возможности:
|
## Основные возможности:
|
||||||
|
|
||||||
1. Рендер поданного на вход svg файла в браузере.
|
1. Рендер поданного на вход svg файла в браузере.
|
||||||
2. Создание скриншот получившегося рендера в браузере, с последующим сохранением в формате png в выбранный каталог.
|
2. Создание скриншот получившегося рендера в браузере, с последующим сохранением в формате png в выбранный каталог.
|
||||||
3. Сравнительный анализ получившегося результата с оценочным png файлом, который был также подан на вход программе.
|
3. Сравнительный анализ получившегося результата с оценочным png файлом, который был также подан на вход программе.
|
||||||
4. Формирование результата сравнения.
|
4. Формирование результата сравнения.
|
||||||
|
|
||||||
|
Программа работает через комнадную строку, ниже перечислены параметры запуска:
|
||||||
|
|
||||||
|
Usage: `[keys] <path_to_svg_file> <path_to_base_png_file>`
|
||||||
|
|
||||||
|
Overview:
|
||||||
|
|
||||||
|
[keys]
|
||||||
|
|
||||||
|
`--help` - вывод подсказки с кратким описанием параметров запуска программы.
|
||||||
|
|
||||||
|
`--setup` - запускает мастер установки, где пользователю предлагается указать пути к браузерам (chrome, opera, firefox и ie), веб-драйверам для браузеров, путь где установлен ImageMagick, а так же два параметра которые используется утилитой compare в ImageMagick. Может использоваться с другими ключами.
|
||||||
|
|
||||||
|
`--load-settings=<path_to_file_settings>` - задать путь к файлу настроек программы `<settings_file_name>.properties`. Если по указанному пути файл не найден, то будет автоматически создан файл с настройками пол умолчанию. Если запустить программу с ключём `--setup`, то вызовиться мастер установки, данные которого будут сохранены в указанным ключем `--load-settings` файл.
|
||||||
|
|
||||||
|
`--browsers=<list_browsers>` - список браузеров которые будут использованы во время работы программы. Данные перечисляются через запятую. Допустимые значения для данного ключа: `chrome, opera, firefox и ie`. Если не задать данный ключ при запуске, то по умолчанию будут задействованы все четыре браузера. `Example: --browsers=opera,ie`
|
||||||
|
|
||||||
|
`--out=<path_to_output_screenshot_dir>` - полный путь к выходному каталогу для скриншотов. Если каталог не существует, автоматически создается каталог и подкаталоги. Если путь не указан, скриншоты будут храниться в `{current_execute_dir}/{file_name_with_ext} _screenshots`
|
||||||
|
|
||||||
|
`<path_to_svg_file>` - путь к SVG файлу. `Example: /home/user/pics/svg_file.svg`. Если полные путь не указан, тогда будет использоваться следующее `{current_execute_dir}/svg_file.svg`.
|
||||||
|
|
||||||
|
`<path_to_base_png_file>` - путь к базовому (контрольному) PNG файлу. `Example: /home/user/pics/base_png_file.png`. Если полные путь не указан, тогда будет использоваться следующее `{current_execute_dir}/base_png_file.png`.
|
||||||
|
|
||||||
|
Для корректной работы необходимо следующее:
|
||||||
|
|
||||||
|
1. Браузеры - Chrome/Chromium, Opera, Firefox и IE (если программа запускается в windows)
|
||||||
|
2. Selenium webdriver для выше перечисленных браузеров.
|
||||||
|
3. ImageMagick v 6.x.x (для работы с ImageMagick используется java wrapper - im4java)
|
||||||
|
|
||||||
|
## Рендер svg файла в браузере
|
||||||
|
|
||||||
|
Процесс работы:
|
||||||
|
|
||||||
|
1. Инициализация веб-драйвера посредствам перечисления параметров запуска выбранного браузера.
|
||||||
|
2. Задание размеров окна браузера - ширина и высота;
|
||||||
|
3. Задание ссылки на ресурс, который необходимо открыть в веб-браузере;
|
||||||
|
4. Непосредственно рендер открытого ресурса;
|
||||||
|
5. Сохранение результата рендера в файл формата png по указанному, при запуске программы, пути.
|
||||||
|
|
||||||
|
## Сравнение png файлов
|
||||||
|
|
||||||
|
Данный этап представляет собой сравнительный анализ выходных данных, полученных при рендере исходного svg файла в выбранных браузерах, с оценочным png файлом. Программа должна сообщить, идентичны ли сравниваемые png файлы с оценочным или нет. Если есть отличия, то будет сформирован графический файл в формате png, в котором будут отображаться области в которых нашлись отличия. Для сравнения использует поставляемая в комплект ImageMagick утилита - compare (более подробно, можно прочитать тут https://www.imagemagick.org/Usage/compare/#metrics).
|
||||||
116
pom.xml
Normal file
116
pom.xml
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>ru.resprojects</groupId>
|
||||||
|
<artifactId>vqct</artifactId>
|
||||||
|
<version>0.1</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>vqct</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<kotlin.version>1.2.30</kotlin.version>
|
||||||
|
<main.class>ru.resprojects.MainKt</main.class>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-test-junit</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-java</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-api</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.6</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.im4java</groupId>
|
||||||
|
<artifactId>im4java</artifactId>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.microutils</groupId>
|
||||||
|
<artifactId>kotlin-logging</artifactId>
|
||||||
|
<version>1.5.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>1.7.5</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
||||||
|
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>compile</id>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>test-compile</id>
|
||||||
|
<phase>test-compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>2.6</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>${main.class}</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
206
src/main/kotlin/ru/resprojects/Main.kt
Normal file
206
src/main/kotlin/ru/resprojects/Main.kt
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Aleksandr
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.resprojects
|
||||||
|
|
||||||
|
import ru.resprojects.vqct.*
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Aleksandr Aleksandrov aka mrResident
|
||||||
|
*/
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
val usageMessage = """
|
||||||
|
Argument list error!
|
||||||
|
|
||||||
|
Usage: [keys] <path_to_svg_file> <path_to_base_png_file>
|
||||||
|
|
||||||
|
Overview:
|
||||||
|
|
||||||
|
[keys]
|
||||||
|
|
||||||
|
--setup - running setup wizard.
|
||||||
|
|
||||||
|
--load-settings=<path_to_file_settings> - start the program with an alternative path to the settings file.
|
||||||
|
|
||||||
|
--browsers=<list_browsers> - run the program with a list of browsers that should be launched to load svg and taking screenshot.
|
||||||
|
Browsers list are separated by commas. Valid values for this key: chrome, firefox, opera, ie. Example: --browsers=opera,ie
|
||||||
|
|
||||||
|
--out=<path_to_output_screenshot_dir> - full path to output directory for screenshots.
|
||||||
|
If directory is not existing then directory and subdirectories automatically is created.
|
||||||
|
If path is not specified then screenshots will be storage in {current_execute_dir}/{file_name_with_ext}_screenshots
|
||||||
|
|
||||||
|
<path_to_svg_file> - path to SVG file. Example: /home/user/pics/svg_file.svg.
|
||||||
|
If full path is not specified then used {current_execute_dir}/svg_file.svg.
|
||||||
|
|
||||||
|
<path_to_base_png_file> - path to base (control) PNG file. Example: /home/user/pics/base_png_file.png.
|
||||||
|
If full path is not specified then used {current_execute_dir}/base_png_file.png.
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for program.
|
||||||
|
* @param args arguments
|
||||||
|
*/
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
println("Starting work...")
|
||||||
|
try {
|
||||||
|
VQCT(parseArguments(args)).launch()
|
||||||
|
} catch (err: IllegalArgumentException) {
|
||||||
|
println("${err.message}")
|
||||||
|
println("Working stopped with ERROR!")
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error(err) { "$err" }
|
||||||
|
println("Working stopped with ERROR! For more information see logger.log file!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input argument list parse.
|
||||||
|
* @param args input program argument list
|
||||||
|
* @return ArgumentsData - parsed input data
|
||||||
|
*/
|
||||||
|
fun parseArguments(args: Array<String>): ArgumentsData {
|
||||||
|
if (args.isEmpty()) {
|
||||||
|
logger.error { "Arguments count error. Must be specified minimum two arguments <path_to_svg_file> <path_to_png_file>" }
|
||||||
|
throw IllegalArgumentException(usageMessage)
|
||||||
|
}
|
||||||
|
val argumentsData = ArgumentsData()
|
||||||
|
args.forEach {
|
||||||
|
if (it.toLowerCase() == "--help") {
|
||||||
|
throw IllegalArgumentException(usageMessage)
|
||||||
|
}
|
||||||
|
if (it.toLowerCase() == "--setup") {
|
||||||
|
argumentsData.isSetup = true
|
||||||
|
}
|
||||||
|
if (it.endsWith(".svg", true)) {
|
||||||
|
argumentsData.svgFile = File(it)
|
||||||
|
if (!argumentsData.svgFile!!.exists()) {
|
||||||
|
logger.error { "ERROR: File ${argumentsData.svgFile!!.absolutePath} is not found!" }
|
||||||
|
throw IllegalArgumentException("ERROR: File ${argumentsData.svgFile!!.absolutePath} is not found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (it.endsWith(".png", true)) {
|
||||||
|
argumentsData.basePngFile = File(it)
|
||||||
|
if (!argumentsData.basePngFile!!.exists()) {
|
||||||
|
logger.error { "ERROR: File ${argumentsData.basePngFile!!.absolutePath} is not found!" }
|
||||||
|
throw IllegalArgumentException("ERROR: File ${argumentsData.basePngFile!!.absolutePath} is not found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((argumentsData.svgFile == null || argumentsData.basePngFile == null) && !argumentsData.isSetup) {
|
||||||
|
logger.error { "ERROR: Not specified SVG file or Base PNG file! Must be specified minimum two arguments <path_to_svg_file> <path_to_png_file>" }
|
||||||
|
throw IllegalArgumentException("ERROR: Not specified SVG file or Base PNG file! Must be specified minimum two arguments <path_to_svg_file> <path_to_png_file>")
|
||||||
|
} else {
|
||||||
|
if ((argumentsData.svgFile == null && argumentsData.basePngFile == null) && argumentsData.isSetup) {
|
||||||
|
return argumentsData
|
||||||
|
} else if ((argumentsData.svgFile == null || argumentsData.basePngFile == null) && argumentsData.isSetup) {
|
||||||
|
logger.error { "ERROR: Not specified SVG file or Base PNG file! Must be specified minimum two arguments <path_to_svg_file> <path_to_png_file>" }
|
||||||
|
throw IllegalArgumentException("ERROR: Not specified SVG file or Base PNG file! Must be specified minimum two arguments <path_to_svg_file> <path_to_png_file>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args.forEach {
|
||||||
|
if (!(it.endsWith(".png", true) || it.endsWith(".svg", true))) {
|
||||||
|
if (it.startsWith("--")) {
|
||||||
|
if (it.startsWith("--load-settings", true)) {
|
||||||
|
if (it.split("=").size == 2 && it.split("=")[1].isNotEmpty()) {
|
||||||
|
argumentsData.settingsFile = File(it.split("=")[1])
|
||||||
|
if (argumentsData.settingsFile!!.exists()) {
|
||||||
|
if (argumentsData.settingsFile!!.extension != "properties") {
|
||||||
|
logger.error { "Invalid properties file ${argumentsData.settingsFile!!.absolutePath}!" }
|
||||||
|
throw IllegalArgumentException("ERROR: Invalid properties file ${argumentsData.settingsFile!!.absolutePath}!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error { "Properties file ${argumentsData.settingsFile!!.absolutePath} is not found!" }
|
||||||
|
throw IllegalArgumentException("ERROR: Properties file ${argumentsData.settingsFile!!.absolutePath} is not found!")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error { "Invalid key value --load-settings. Must be --load-settings=<path_to_properties_file>" }
|
||||||
|
throw IllegalArgumentException("ERROR: Invalid key value --load-settings. Must be --load-settings=<path_to_properties_file>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (it.startsWith("--browsers", true)) {
|
||||||
|
if (it.split("=").size == 2 && it.split("=")[1].isNotEmpty()) {
|
||||||
|
argumentsData.browsers = it.split("=")[1].toUpperCase().split(",")
|
||||||
|
for (i in 0 .. (argumentsData.browsers!!.size - 1)) {
|
||||||
|
try {
|
||||||
|
Browsers.valueOf(argumentsData.browsers!![i])
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error { "Invalid value \"${argumentsData.browsers!![i]}\" for key --browsers. Must be --browsers=<list_of_browsers>. Browsers list are separated by commas. Valid values for this key: chrome, firefox, opera, ie." }
|
||||||
|
throw IllegalArgumentException("ERROR: Invalid value \"${argumentsData.browsers!![i]}\" for key --browsers. Must be --browsers=<list_of_browsers>. Browsers list are separated by commas. Valid values for this key: chrome, firefox, opera, ie.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error { "Invalid key value --browsers. Must be --browsers=<list_of_browsers>. Browsers list are separated by commas. Valid values for this key: chrome, firefox, opera, ie." }
|
||||||
|
throw IllegalArgumentException("ERROR: Invalid key value --browsers. Must be --browsers=<list_of_browsers>. Browsers list are separated by commas. Valid values for this key: chrome, firefox, opera, ie.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (it.startsWith("--out", true)) {
|
||||||
|
if (it.toLowerCase().split("=").size == 2 && it.split("=")[1].isNotEmpty()) {
|
||||||
|
argumentsData.outputDirectory = if (it.split("=")[1].endsWith("/") || it.split("=")[1].endsWith("\\")) {
|
||||||
|
File("${it.split("=")[1]}${argumentsData.svgFile!!.name.replace('.', '_')}_screenshots")
|
||||||
|
} else {
|
||||||
|
File("${it.split("=")[1]}${System.getProperty("file.separator")}${argumentsData.svgFile!!.name.replace('.', '_')}_screenshots")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error { "Invalid key value --out. Must be --out=<path_to_output_screenshot_dir>." }
|
||||||
|
throw IllegalArgumentException("ERROR: Invalid key value --out. Must be --out=<path_to_output_screenshot_dir>.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error { "Invalid input data \"$it\"" }
|
||||||
|
throw IllegalArgumentException("Invalid input data \"$it\".\n$usageMessage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (argumentsData.outputDirectory == null) {
|
||||||
|
argumentsData.outputDirectory = File("${System.getProperties().getProperty("user.dir")}${System.getProperty("file.separator")}${argumentsData.svgFile!!.name.replace('.', '_')}_screenshots")
|
||||||
|
}
|
||||||
|
if (argumentsData.browsers == null) {
|
||||||
|
val tmp = mutableListOf<String>()
|
||||||
|
Browsers.values().forEach {
|
||||||
|
tmp.add(it.name)
|
||||||
|
}
|
||||||
|
argumentsData.browsers = tmp.toList()
|
||||||
|
}
|
||||||
|
return argumentsData
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data storage for parse program argument list.
|
||||||
|
* @param svgFile - input svg file. This is file will be rendered in browser.
|
||||||
|
* @param basePngFile - input png file. This is base file for comparing with output screenshots.
|
||||||
|
* @param outputDirectory - full path to output directory for screenshots.
|
||||||
|
* @param settingsFile - settings file.
|
||||||
|
* @param browsers - list of browsers that should be launched to load svg and taking screenshot.
|
||||||
|
* @param isSetup - flag for setup wizard.
|
||||||
|
*/
|
||||||
|
data class ArgumentsData(
|
||||||
|
var svgFile: File? = null,
|
||||||
|
var basePngFile: File? = null,
|
||||||
|
var outputDirectory: File? = null,
|
||||||
|
var settingsFile: File? = null,
|
||||||
|
var isSetup: Boolean = false,
|
||||||
|
var browsers: List<String>? = null
|
||||||
|
)
|
||||||
272
src/main/kotlin/ru/resprojects/vqct/ImageUtils.kt
Normal file
272
src/main/kotlin/ru/resprojects/vqct/ImageUtils.kt
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Aleksandr
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.resprojects.vqct
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.im4java.core.ImageCommand
|
||||||
|
import org.im4java.core.ConvertCmd
|
||||||
|
import org.im4java.core.IdentifyCmd
|
||||||
|
import org.im4java.core.CompositeCmd
|
||||||
|
import org.im4java.core.CompareCmd
|
||||||
|
import org.im4java.core.IMOperation
|
||||||
|
import org.im4java.process.ArrayListErrorConsumer
|
||||||
|
import org.im4java.process.ArrayListOutputConsumer
|
||||||
|
import org.openqa.selenium.Dimension
|
||||||
|
import java.awt.Toolkit
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Aleksandr Aleksandrov aka mrResident
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageMagick utilities
|
||||||
|
*/
|
||||||
|
private enum class CommandType {
|
||||||
|
CONVERT, IDENTIFY, COMPOSITECMD, COMPARE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImgaeMagick tools.
|
||||||
|
* @param settings program settings
|
||||||
|
*/
|
||||||
|
class IMUtils(private val settings: ProgramSettings) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting IM utilities.
|
||||||
|
* @param commandType ImageMagick utilities
|
||||||
|
* @return IM command
|
||||||
|
*/
|
||||||
|
private fun getImageCommand(commandType: CommandType): ImageCommand {
|
||||||
|
try {
|
||||||
|
val imPath = settings.getImageMagick().absolutePath
|
||||||
|
val imageCommand = when (commandType) {
|
||||||
|
CommandType.CONVERT -> ConvertCmd(false)
|
||||||
|
CommandType.IDENTIFY -> IdentifyCmd(false)
|
||||||
|
CommandType.COMPOSITECMD -> CompositeCmd(false)
|
||||||
|
CommandType.COMPARE -> CompareCmd(false)
|
||||||
|
}
|
||||||
|
imageCommand.searchPath = imPath
|
||||||
|
return imageCommand
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw FileNotFoundException("${err.message}. ImageMagick v 6.x.x is not found! For download ImageMagick please visit https://legacy.imagemagick.org/script/download.php")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting info from graphic file.
|
||||||
|
* @param imageFile graphic file
|
||||||
|
* @return image information
|
||||||
|
*/
|
||||||
|
fun getImageInfo(imageFile: File): ImageInfo {
|
||||||
|
if (!imageFile.exists()) {
|
||||||
|
throw FileNotFoundException("Image file ${imageFile.absolutePath} not found!")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val operation = IMOperation()
|
||||||
|
operation.format("%w,%h,%Q,%m")
|
||||||
|
operation.addImage()
|
||||||
|
val identyfyCmd = getImageCommand(CommandType.IDENTIFY)
|
||||||
|
val output = ArrayListOutputConsumer()
|
||||||
|
identyfyCmd.setOutputConsumer(output)
|
||||||
|
identyfyCmd.run(operation, imageFile.absolutePath)
|
||||||
|
val list = mutableListOf<String>()
|
||||||
|
list.addAll(output.output[0].split(","))
|
||||||
|
list.add(imageFile.absolutePath)
|
||||||
|
return ImageInfo(list)
|
||||||
|
} catch (err: Exception) {
|
||||||
|
throw IllegalStateException("Error while reading image file ${imageFile.absolutePath}. ${err.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cropping input image image. For more information see IM manual https://www.imagemagick.org/Usage/crop/#crop
|
||||||
|
* @param inputImage input image that needes cropping
|
||||||
|
* @param outputImage output cropping image
|
||||||
|
* @param dimension dimension (part or all image) for cropping
|
||||||
|
* @param x count of pixels for cropping
|
||||||
|
* @param y count of pixels for cropping
|
||||||
|
* @return false if appeared error while image cropping
|
||||||
|
*/
|
||||||
|
fun cropImage(inputImage: File, outputImage: File, dimension: Dimension, x: Int, y: Int): Boolean {
|
||||||
|
if (!inputImage.exists()) {
|
||||||
|
throw FileNotFoundException("Input file ${inputImage.absolutePath} not found!")
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
val operation = IMOperation()
|
||||||
|
operation.addImage(inputImage.absolutePath)
|
||||||
|
operation.crop(dimension.getWidth(), dimension.getHeight(), x, y)
|
||||||
|
operation.p_repage()
|
||||||
|
operation.addImage(outputImage.absolutePath)
|
||||||
|
val convert = getImageCommand(CommandType.CONVERT)
|
||||||
|
convert.run(operation)
|
||||||
|
true
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error(err) { "Error while cropping image: ${err.message}" }
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trimming input image image. For more information see IM manual https://www.imagemagick.org/Usage/crop/#trim
|
||||||
|
* @param inputImage input image that needes trimming
|
||||||
|
* @param outputImage output trimmed image
|
||||||
|
* @return false if appeared error while image trimming
|
||||||
|
*/
|
||||||
|
fun trimImage(inputImage: File, outputImage: File): Boolean {
|
||||||
|
if (!inputImage.exists()) {
|
||||||
|
throw FileNotFoundException("Input file ${inputImage.absolutePath} not found!")
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
val operation = IMOperation()
|
||||||
|
operation.addImage(inputImage.absolutePath)
|
||||||
|
operation.trim()
|
||||||
|
operation.p_repage()
|
||||||
|
operation.addImage(outputImage.absolutePath)
|
||||||
|
val convert = getImageCommand(CommandType.CONVERT)
|
||||||
|
convert.run(operation)
|
||||||
|
true
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error(err) { "Error while trimming image: ${err.message}" }
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizing input image file. For more information see IM manual https://www.imagemagick.org/Usage/resize/#resize
|
||||||
|
* @param inputImage input image that needes resizing
|
||||||
|
* @param outputImage output resized image
|
||||||
|
* @param width new image width
|
||||||
|
* @param height new image height
|
||||||
|
* @return false if appeared error while image resized
|
||||||
|
*/
|
||||||
|
fun resizeImage(inputImage: File, outputImage: File, width: Int, height: Int): Boolean {
|
||||||
|
if (!inputImage.exists()) {
|
||||||
|
throw FileNotFoundException("Input file ${inputImage.absolutePath} not found!")
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
val operation = IMOperation()
|
||||||
|
operation.addImage(inputImage.absolutePath)
|
||||||
|
operation.resize(width, height)
|
||||||
|
operation.addImage(outputImage.absolutePath)
|
||||||
|
val convert = getImageCommand(CommandType.CONVERT)
|
||||||
|
convert.run(operation)
|
||||||
|
true
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error(err) { "Error while resizing image: ${err.message}" }
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparing two image. For more information see IM manual https://www.imagemagick.org/Usage/compare/
|
||||||
|
* @param inputImage_1 first image for comparing
|
||||||
|
* @param inputImage_2 second image for comparing
|
||||||
|
* @param outputImage output image with comparing result
|
||||||
|
* @return absolute error count, number of different pixels
|
||||||
|
*/
|
||||||
|
fun compareImage(inputImage_1: File, inputImage_2: File, outputImage: File): Int {
|
||||||
|
if (!inputImage_1.exists()) {
|
||||||
|
throw FileNotFoundException("Input file ${inputImage_1.absolutePath} not found!")
|
||||||
|
}
|
||||||
|
if (!inputImage_2.exists()) {
|
||||||
|
throw FileNotFoundException("Input file ${inputImage_2.absolutePath} not found!")
|
||||||
|
}
|
||||||
|
val operation = IMOperation()
|
||||||
|
val compareCmd = getImageCommand(CommandType.COMPARE)
|
||||||
|
val error = ArrayListErrorConsumer()
|
||||||
|
compareCmd.setErrorConsumer(error)
|
||||||
|
operation.metric("ae")
|
||||||
|
operation.fuzz(settings.getImFuzz(), true)
|
||||||
|
operation.addImage(inputImage_1.absolutePath)
|
||||||
|
operation.addImage(inputImage_2.absolutePath)
|
||||||
|
operation.addImage(outputImage.absolutePath)
|
||||||
|
val isEquals = try {
|
||||||
|
compareCmd.run(operation)
|
||||||
|
true
|
||||||
|
} catch (err: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
return if (!isEquals) {
|
||||||
|
if (!error.output.isEmpty()) {
|
||||||
|
try {
|
||||||
|
error.output[0].toInt()
|
||||||
|
} catch (err: NumberFormatException) {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image file information.
|
||||||
|
* @param inputData image information (returned from IM)
|
||||||
|
*/
|
||||||
|
data class ImageInfo(private val inputData: List<String>) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image size (width and height).
|
||||||
|
*/
|
||||||
|
val imageSize: Dimension
|
||||||
|
/**
|
||||||
|
* Image compression quality.
|
||||||
|
*/
|
||||||
|
val imageQuality: Double
|
||||||
|
/**
|
||||||
|
* Image file format.
|
||||||
|
*/
|
||||||
|
val imageFormat: String
|
||||||
|
/**
|
||||||
|
* Image file.
|
||||||
|
*/
|
||||||
|
val imageFile: File
|
||||||
|
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
imageSize = Dimension(inputData[0].toInt(), inputData[1].toInt())
|
||||||
|
imageQuality = inputData[2].toDouble()
|
||||||
|
imageFormat = inputData[3]
|
||||||
|
imageFile = File(inputData[4])
|
||||||
|
} catch (err: Exception) {
|
||||||
|
throw IllegalStateException("Can't get image info.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current screen resolution.
|
||||||
|
*/
|
||||||
|
fun getCurrentScreenResolution(): Dimension {
|
||||||
|
return Dimension(Toolkit.getDefaultToolkit().screenSize.width, Toolkit.getDefaultToolkit().screenSize.height)
|
||||||
|
}
|
||||||
211
src/main/kotlin/ru/resprojects/vqct/SvgRender.kt
Normal file
211
src/main/kotlin/ru/resprojects/vqct/SvgRender.kt
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Aleksandr
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.resprojects.vqct
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.openqa.selenium.*
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriver
|
||||||
|
import org.openqa.selenium.chrome.ChromeOptions
|
||||||
|
import org.openqa.selenium.firefox.FirefoxDriver
|
||||||
|
import org.openqa.selenium.firefox.FirefoxOptions
|
||||||
|
import org.openqa.selenium.ie.InternetExplorerDriver
|
||||||
|
import org.openqa.selenium.opera.OperaDriver
|
||||||
|
import org.openqa.selenium.opera.OperaOptions
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.FileWriter
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.imageio.ImageIO
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Aleksandr Aleksandrov aka mrResident
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render input svg file in browser and taking screenshot
|
||||||
|
*/
|
||||||
|
abstract class SvgRender(val settings: ProgramSettings) {
|
||||||
|
|
||||||
|
abstract fun getScreenshot(dimension: Dimension): File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTML wrapper for SVG-file
|
||||||
|
* @param inputFile SVG-file
|
||||||
|
* @param dimension dimension for wrapped svg-file.
|
||||||
|
* @param patch place slash at the start for svg-file path. This is patch actual for windows-version of the Firefox browser.
|
||||||
|
* @return html file for SVG-file
|
||||||
|
*/
|
||||||
|
protected fun htmlWrapperCreate(inputFile: File, dimension: Dimension, patch: Boolean = false): File {
|
||||||
|
val outFile = File("${FileUtils.getTempDirectory()}${System.getProperty("file.separator")}wrapper.html")
|
||||||
|
val outHTML = FileWriter(outFile)
|
||||||
|
val htmlDoc = """
|
||||||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Getting screenshot from svg file ${inputFile.name}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img name="svg_file" src="${if (patch) "/" else ""}${inputFile.absolutePath}" width="${dimension.getWidth()}" height="${dimension.getHeight()}" alt="svg_file">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""".trimIndent()
|
||||||
|
try {
|
||||||
|
outHTML.write(htmlDoc)
|
||||||
|
return outFile
|
||||||
|
} catch (err: IOException) {
|
||||||
|
throw IllegalStateException("Can not write to file ${outFile.absolutePath}")
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
outHTML.close()
|
||||||
|
} catch (err: IOException) {
|
||||||
|
throw IllegalStateException("Can not write to file ${outFile.absolutePath}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taking screenshot from browser.
|
||||||
|
* @param driver selenium WebDriver for browser.
|
||||||
|
* @return screenshot file
|
||||||
|
*/
|
||||||
|
protected fun shoot(driver: WebDriver): File {
|
||||||
|
return try {
|
||||||
|
val screen = (driver as TakesScreenshot).getScreenshotAs(OutputType.FILE)
|
||||||
|
val webElement = driver.findElement(By.name("svg_file"))
|
||||||
|
val img = ImageIO.read(screen)
|
||||||
|
val dest = img.getSubimage(webElement.location.getX(), webElement.location.getY(), webElement.size.getWidth(), webElement.size.getHeight())
|
||||||
|
ImageIO.write(dest, "png", screen)
|
||||||
|
driver.manage().window().size = Dimension(640, 480)
|
||||||
|
driver.quit()
|
||||||
|
screen
|
||||||
|
} catch (err: Exception) {
|
||||||
|
driver.quit()
|
||||||
|
throw IllegalStateException("Can not get correct screenshot! Cause: ${err.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opening svg file and taking screenshot in chrome/chromium browser.
|
||||||
|
*/
|
||||||
|
class SvgRenderByChrome(settings: ProgramSettings, private val svgFile: File): SvgRender(settings) {
|
||||||
|
override fun getScreenshot(dimension: Dimension): File {
|
||||||
|
try {
|
||||||
|
System.setProperty("webdriver.chrome.driver", settings.getWebDriver(Browsers.CHROME).absolutePath)
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw IllegalStateException("Web driver for Google Chrome / Chromium is not found. Webdriver may be downloaded from https://sites.google.com/a/chromium.org/chromedriver/downloads")
|
||||||
|
}
|
||||||
|
val options = ChromeOptions()
|
||||||
|
try {
|
||||||
|
options.setBinary(settings.getBrowser(Browsers.CHROME).absolutePath)
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw IllegalStateException("Browser Google Chrome / Chromium is not found")
|
||||||
|
}
|
||||||
|
val driver = ChromeDriver(options)
|
||||||
|
driver.manage().window().fullscreen()
|
||||||
|
driver.get(htmlWrapperCreate(svgFile, dimension).toURI().toString())
|
||||||
|
return shoot(driver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opening svg file and taking screenshot in Firefox browser.
|
||||||
|
*/
|
||||||
|
class SvgRenderByFirefox(settings: ProgramSettings, private val svgFile: File): SvgRender(settings) {
|
||||||
|
override fun getScreenshot(dimension: Dimension): File {
|
||||||
|
try {
|
||||||
|
System.setProperty("webdriver.firefox.driver", settings.getWebDriver(Browsers.FIREFOX).absolutePath)
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw IllegalStateException("Web driver for Firefox is not found. Web driver can download from https://github.com/mozilla/geckodriver/releases")
|
||||||
|
}
|
||||||
|
val options = FirefoxOptions()
|
||||||
|
try {
|
||||||
|
options.setBinary(settings.getBrowser(Browsers.FIREFOX).absolutePath)
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw IllegalStateException("Browser Firefox is not found")
|
||||||
|
}
|
||||||
|
val driver = FirefoxDriver(options)
|
||||||
|
driver.manage().window().fullscreen()
|
||||||
|
if (getOSName() == TypeOfOS.WINDOWS) {
|
||||||
|
driver.get(htmlWrapperCreate(svgFile, dimension, patch = true).toURI().toString())
|
||||||
|
} else {
|
||||||
|
driver.get(htmlWrapperCreate(svgFile, dimension).toURI().toString())
|
||||||
|
}
|
||||||
|
return shoot(driver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opening svg file and taking screenshot in Opera browser.
|
||||||
|
*/
|
||||||
|
class SvgRenderByOpera(settings: ProgramSettings, private val svgFile: File): SvgRender(settings) {
|
||||||
|
override fun getScreenshot(dimension: Dimension): File {
|
||||||
|
try {
|
||||||
|
System.setProperty("webdriver.opera.driver", settings.getWebDriver(Browsers.OPERA).absolutePath)
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw IllegalStateException("Web driver for Opera is not found. Webdriver may be downloaded from https://github.com/operasoftware/operachromiumdriver/releases")
|
||||||
|
}
|
||||||
|
val options = OperaOptions()
|
||||||
|
try {
|
||||||
|
options.setBinary(settings.getBrowser(Browsers.OPERA).absolutePath)
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw IllegalStateException("Browser Opera is not found")
|
||||||
|
}
|
||||||
|
val driver = OperaDriver(options)
|
||||||
|
|
||||||
|
driver.manage().window().fullscreen()
|
||||||
|
driver.get(htmlWrapperCreate(svgFile, dimension).toURI().toString())
|
||||||
|
val result = shoot(driver)
|
||||||
|
if (getOSName() == TypeOfOS.WINDOWS) {
|
||||||
|
Runtime.getRuntime().exec("taskkill /f /im opera.exe")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opening svg file and taking screenshot in IE browser.
|
||||||
|
*/
|
||||||
|
class SvgRenderByIE(settings: ProgramSettings, private val svgFile: File): SvgRender(settings) {
|
||||||
|
override fun getScreenshot(dimension: Dimension): File {
|
||||||
|
try {
|
||||||
|
System.setProperty("webdriver.ie.driver", settings.getWebDriver(Browsers.IE).absolutePath)
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw IllegalStateException("Web driver for IE is not found. Webdriver may be downloaded from https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
settings.getBrowser(Browsers.IE).absolutePath
|
||||||
|
} catch (err: FileNotFoundException) {
|
||||||
|
throw IllegalStateException("Browser IE is not found")
|
||||||
|
}
|
||||||
|
val driver = InternetExplorerDriver()
|
||||||
|
driver.manage().window().fullscreen()
|
||||||
|
driver.get(htmlWrapperCreate(svgFile, dimension).toURI().toString())
|
||||||
|
driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
|
||||||
|
return shoot(driver)
|
||||||
|
}
|
||||||
|
}
|
||||||
579
src/main/kotlin/ru/resprojects/vqct/Utils.kt
Normal file
579
src/main/kotlin/ru/resprojects/vqct/Utils.kt
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Aleksandr
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
package ru.resprojects.vqct
|
||||||
|
|
||||||
|
import ru.resprojects.ArgumentsData
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.Properties
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Aleksandr Aleksandrov aka mrResident
|
||||||
|
*/
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
enum class TypeOfOS {
|
||||||
|
LINUX, WINDOWS
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Browsers {
|
||||||
|
CHROME, FIREFOX, OPERA, IE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get OS name from system settings.
|
||||||
|
*/
|
||||||
|
fun getOSName(): TypeOfOS {
|
||||||
|
val osName = System.getProperties().getProperty("os.name").toLowerCase().split(" ")
|
||||||
|
return if (!osName.isEmpty()) {
|
||||||
|
when (osName[0]) {
|
||||||
|
"linux" -> TypeOfOS.LINUX
|
||||||
|
"windows" -> TypeOfOS.WINDOWS
|
||||||
|
else -> {
|
||||||
|
throw IllegalStateException("Unsupported OS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("Unknown OS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generating output file.
|
||||||
|
* @param inputFile source input file with the full path and name
|
||||||
|
* @param outputDir output directory
|
||||||
|
* @param postfix addition line for output file name
|
||||||
|
* @param extension extension for output file name
|
||||||
|
* @param return output file
|
||||||
|
*/
|
||||||
|
fun outputFileConstructor(inputFile: File, outputDir: String,
|
||||||
|
postfix: String = "", extension: String = "png"): File {
|
||||||
|
return File(outputDir, "${inputFile.name.replace('.', '_')}$postfix.$extension")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for storage program settings.
|
||||||
|
* @param setupWizard run setup wizard
|
||||||
|
* @param settingsFile load custom settings file if setupWizard = false
|
||||||
|
* If setupWizard = true and settingsFile is not null then settings file
|
||||||
|
* created in custom else settings file created in default file.
|
||||||
|
*/
|
||||||
|
class ProgramSettings(setupWizard: Boolean = false, settingsFile: File? = null) {
|
||||||
|
|
||||||
|
private val prop = Properties()
|
||||||
|
private val defaultSettingsName = "vqct.properties"
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (setupWizard) {
|
||||||
|
if (settingsFile != null) {
|
||||||
|
resetSettings(settingsFile, setupWizard)
|
||||||
|
} else {
|
||||||
|
resetSettings(File(defaultSettingsName), setupWizard)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (settingsFile != null) {
|
||||||
|
if (settingsFile.exists()) {
|
||||||
|
loadSettings(settingsFile)
|
||||||
|
} else {
|
||||||
|
resetSettings(settingsFile)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (File(defaultSettingsName).exists()) {
|
||||||
|
loadSettings()
|
||||||
|
} else {
|
||||||
|
resetSettings(File(defaultSettingsName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get file from settings.
|
||||||
|
* @param name settings key
|
||||||
|
* @throws FileNotFoundException if keys value is empty or file from settings value not found
|
||||||
|
*/
|
||||||
|
private fun getFile(name: String): File {
|
||||||
|
return if (prop.getProperty(name) != null && prop.getProperty(name).isNotEmpty()) {
|
||||||
|
val file = File(prop.getProperty(name))
|
||||||
|
if (file.exists()) {
|
||||||
|
file
|
||||||
|
} else {
|
||||||
|
throw FileNotFoundException("File ${file.absolutePath} not found")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw FileNotFoundException("Can't get file because parameter \"$name\" in settings file is empty.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get browser execute file from settings.
|
||||||
|
* @param browsers type of browser
|
||||||
|
* @throws IllegalStateException if settings file is empty or not found
|
||||||
|
*/
|
||||||
|
fun getBrowser(browsers: Browsers): File {
|
||||||
|
if (prop.isEmpty) {
|
||||||
|
throw IllegalStateException("Property file is empty or not found.")
|
||||||
|
}
|
||||||
|
return when(browsers) {
|
||||||
|
Browsers.CHROME -> getFile(browsers.name)
|
||||||
|
Browsers.FIREFOX -> getFile(browsers.name)
|
||||||
|
Browsers.OPERA -> getFile(browsers.name)
|
||||||
|
Browsers.IE -> getFile(browsers.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get selenium browser execute file from settings.
|
||||||
|
* @param browsers type of browser
|
||||||
|
* @throws IllegalStateException if settings file is empty or not found
|
||||||
|
*/
|
||||||
|
fun getWebDriver(browsers: Browsers): File {
|
||||||
|
if (prop.isEmpty) {
|
||||||
|
throw IllegalStateException("Property file is empty or not found.")
|
||||||
|
}
|
||||||
|
return when(browsers) {
|
||||||
|
Browsers.CHROME -> getFile("${browsers.name}_WEBDRIVER")
|
||||||
|
Browsers.FIREFOX -> getFile("${browsers.name}_WEBDRIVER")
|
||||||
|
Browsers.OPERA -> getFile("${browsers.name}_WEBDRIVER")
|
||||||
|
Browsers.IE -> getFile("${browsers.name}_WEBDRIVER")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ImageMagick execute file from settings.
|
||||||
|
* @throws IllegalStateException if settings file is empty or not found
|
||||||
|
* @throws FileNotFoundException if keys value is empty or file from settings value not found
|
||||||
|
*/
|
||||||
|
fun getImageMagick():File {
|
||||||
|
if (prop.isEmpty) {
|
||||||
|
throw IllegalStateException("Property file is empty or not found.")
|
||||||
|
}
|
||||||
|
return if (prop.getProperty("IMAGEMAGICK") != null && prop.getProperty("IMAGEMAGICK").isNotEmpty()) {
|
||||||
|
val file = File(prop.getProperty("IMAGEMAGICK"))
|
||||||
|
if (file.exists()) {
|
||||||
|
file
|
||||||
|
} else {
|
||||||
|
throw FileNotFoundException("File ${file.absolutePath} not found.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw FileNotFoundException("Can't get file because parameter \"IMAGEMAGICK\" in settings file is empty.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageMagick compare value fuzz. Fuzz is "Fuzz Factor" using by ImageMagick to
|
||||||
|
* ignore minor differences between the two images.
|
||||||
|
*/
|
||||||
|
fun getImFuzz(): Double {
|
||||||
|
if (prop.isEmpty) {
|
||||||
|
throw IllegalStateException("Property file is empty or not found.")
|
||||||
|
}
|
||||||
|
return if (prop.getProperty("imCompareFuzzValue") != null && prop.getProperty("imCompareFuzzValue").isNotEmpty()) {
|
||||||
|
try {
|
||||||
|
prop.getProperty("imCompareFuzzValue").toDouble()
|
||||||
|
} catch (err: NumberFormatException) {
|
||||||
|
throw IllegalStateException("Value of parameter \"imCompareFuzzValue\" is not Double.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("Parameter \"imCompareFuzzValue\" in settings file is empty.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Threshold value for count of the actual number of pixels that were masked,
|
||||||
|
* at the current "Fuzz Factor". This value used by ImageMagick for compare images.
|
||||||
|
*/
|
||||||
|
fun getImErrPixelCountThreshold(): Int {
|
||||||
|
if (prop.isEmpty) {
|
||||||
|
throw IllegalStateException("Property file is empty or not found.")
|
||||||
|
}
|
||||||
|
return if (prop.getProperty("imCompareErrCountPixelThreshold") != null && prop.getProperty("imCompareErrCountPixelThreshold").isNotEmpty()) {
|
||||||
|
try {
|
||||||
|
prop.getProperty("imCompareErrCountPixelThreshold").toInt()
|
||||||
|
} catch (err: NumberFormatException) {
|
||||||
|
throw IllegalStateException("Value of parameter \"imCompareErrCountPixelThreshold\" is not Integer.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("Parameter \"imCompareErrCountPixelThreshold\" in settings file is empty.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load settings from file. If file not found then created new settings file
|
||||||
|
* in current directory.
|
||||||
|
* @param settingsFile full path to settings file. If the file is not specified,
|
||||||
|
* then an attempt is made to load the settings from the vqct.properties file,
|
||||||
|
* which is in the same directory as the executable file.
|
||||||
|
*/
|
||||||
|
private fun loadSettings(settingsFile: File = File(defaultSettingsName)) {
|
||||||
|
prop.clear()
|
||||||
|
if (settingsFile.exists()) {
|
||||||
|
prop.load(settingsFile.inputStream())
|
||||||
|
} else {
|
||||||
|
throw FileNotFoundException("Settings file ${settingsFile.absolutePath} is not found.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the program settings. If the settings file is not found,
|
||||||
|
* then a new file is created, otherwise the new data is saved
|
||||||
|
* in the current file.
|
||||||
|
* @param settingsFile full path to settings file. If file is not specified,
|
||||||
|
* then settings save to default file vqct.properties, which is in the same
|
||||||
|
* directory as the executable file.
|
||||||
|
*/
|
||||||
|
private fun resetSettings(settingsFile: File, setupWizard: Boolean = false) {
|
||||||
|
prop.clear()
|
||||||
|
var str: String
|
||||||
|
var file: File
|
||||||
|
if (setupWizard) {
|
||||||
|
println("Start setup wizard. Current OS ${getOSName()}")
|
||||||
|
}
|
||||||
|
when(getOSName()) {
|
||||||
|
TypeOfOS.LINUX -> {
|
||||||
|
if (setupWizard) {
|
||||||
|
file = File("/usr/bin/google-chrome")
|
||||||
|
println("1. Path to CHROME browser [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty(Browsers.CHROME.name, if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("/usr/bin/opera")
|
||||||
|
println("2. Path to OPERA browser [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty(Browsers.OPERA.name, if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("/usr/bin/firefox")
|
||||||
|
println("3. Path to FIREFOX browser [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty(Browsers.FIREFOX.name, if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("chromedriver")
|
||||||
|
println("4. Path to selenium webdriver for CHROME [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("${Browsers.CHROME.name}_WEBDRIVER", if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("operadriver")
|
||||||
|
println("5. Path to selenium webdriver for OPERA [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("${Browsers.OPERA.name}_WEBDRIVER", if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("geckodriver")
|
||||||
|
println("6. Path to selenium webdriver for FIREFOX [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("${Browsers.FIREFOX.name}_WEBDRIVER", if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("/usr/bin/convert")
|
||||||
|
println("7. Path to ImageMagick [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("IMAGEMAGICK", if (str.isNotEmpty()) str else if (file.exists()) "/usr/bin" else "")
|
||||||
|
} else {
|
||||||
|
prop.setProperty(Browsers.CHROME.name, if (File("/usr/bin/google-chrome").exists()) "/usr/bin/google-chrome" else "")
|
||||||
|
prop.setProperty(Browsers.OPERA.name, if (File("/usr/bin/opera").exists()) "/usr/bin/opera" else "")
|
||||||
|
prop.setProperty(Browsers.FIREFOX.name, if (File("/usr/bin/firefox").exists()) "/usr/bin/firefox" else "")
|
||||||
|
file = File("chromedriver")
|
||||||
|
prop.setProperty("${Browsers.CHROME.name}_WEBDRIVER", if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("operadriver")
|
||||||
|
prop.setProperty("${Browsers.OPERA.name}_WEBDRIVER", if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("geckodriver")
|
||||||
|
prop.setProperty("${Browsers.FIREFOX.name}_WEBDRIVER", if (file.exists()) file.absolutePath else "")
|
||||||
|
prop.setProperty("IMAGEMAGICK", if (File("/usr/bin/convert").exists()) "/usr/bin" else "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TypeOfOS.WINDOWS -> {
|
||||||
|
if (setupWizard) {
|
||||||
|
file = File("C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe")
|
||||||
|
if (!file.exists())
|
||||||
|
file = File("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe")
|
||||||
|
println("1. Path to CHROME browser [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty(Browsers.CHROME.name, if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("C:\\Program Files (x86)\\Opera\\launcher.exe")
|
||||||
|
if (!file.exists())
|
||||||
|
file = File("C:\\Program Files\\Opera\\launcher.exe")
|
||||||
|
println("2. Path to OPERA browser [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty(Browsers.OPERA.name, if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe")
|
||||||
|
if (!file.exists())
|
||||||
|
file = File("C:\\Program Files\\Mozilla Firefox\\firefox.exe")
|
||||||
|
println("3. Path to FIREFOX browser [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty(Browsers.FIREFOX.name, if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("C:\\Program Files\\Internet Explorer\\iexplore.exe")
|
||||||
|
println("4. Path to IE browser [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty(Browsers.IE.name, if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("chromedriver.exe")
|
||||||
|
println("5. Path to selenium webdriver for CHROME [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("${Browsers.CHROME.name}_WEBDRIVER", if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("operadriver.exe")
|
||||||
|
println("6. Path to selenium webdriver for OPERA [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("${Browsers.OPERA.name}_WEBDRIVER", if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("geckodriver.exe")
|
||||||
|
println("7. Path to selenium webdriver for FIREFOX [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("${Browsers.FIREFOX.name}_WEBDRIVER", if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("IEDriverServer.exe")
|
||||||
|
println("8. Path to selenium webdriver for IE [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("${Browsers.IE.name}_WEBDRIVER", if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("C:\\Program Files (x86)\\ImageMagick-6.9.9-Q16")
|
||||||
|
if (!file.exists())
|
||||||
|
file = File("C:\\Program Files\\ImageMagick-6.9.9-Q16")
|
||||||
|
println("9. Path to ImageMagick [default ${if (file.exists())
|
||||||
|
file.absolutePath else "not found"}]. Enter for use default path or input yor path: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("IMAGEMAGICK", if (str.isNotEmpty()) str else if (file.exists()) file.absolutePath else "")
|
||||||
|
} else {
|
||||||
|
file = File("C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe")
|
||||||
|
if (!file.exists())
|
||||||
|
file = File("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe")
|
||||||
|
prop.setProperty(Browsers.CHROME.name, if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("C:\\Program Files (x86)\\Opera\\launcher.exe")
|
||||||
|
if (!file.exists())
|
||||||
|
file = File("C:\\Program Files\\Opera\\launcher.exe")
|
||||||
|
prop.setProperty(Browsers.OPERA.name, if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe")
|
||||||
|
if (!file.exists())
|
||||||
|
file = File("C:\\Program Files\\Mozilla Firefox\\firefox.exe")
|
||||||
|
prop.setProperty(Browsers.FIREFOX.name, if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("C:\\Program Files\\Internet Explorer\\iexplore.exe")
|
||||||
|
prop.setProperty(Browsers.IE.name, if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("chromedriver.exe")
|
||||||
|
prop.setProperty("${Browsers.CHROME.name}_WEBDRIVER", if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("operadriver.exe")
|
||||||
|
prop.setProperty("${Browsers.OPERA.name}_WEBDRIVER", if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("geckodriver.exe")
|
||||||
|
prop.setProperty("${Browsers.FIREFOX.name}_WEBDRIVER", if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("IEDriverServer.exe")
|
||||||
|
prop.setProperty("${Browsers.IE.name}_WEBDRIVER", if (file.exists()) file.absolutePath else "")
|
||||||
|
file = File("C:\\Program Files\\ImageMagick-6.9.9-Q16")
|
||||||
|
prop.setProperty("IMAGEMAGICK", if (file.exists()) file.absolutePath else "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (setupWizard) {
|
||||||
|
println("Enter compare Fuzz value [default 15]. Enter for use default path or use own value: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("imCompareFuzzValue", if (str.isNotEmpty()) str else "15")
|
||||||
|
println("Enter compare compare error count pixel threshold value [default 500]. Enter for use default path or use own value: ")
|
||||||
|
str = readLine()!!
|
||||||
|
prop.setProperty("imCompareErrCountPixelThreshold", if (str.isNotEmpty()) str else "500")
|
||||||
|
} else {
|
||||||
|
prop.setProperty("imCompareFuzzValue", "15")
|
||||||
|
prop.setProperty("imCompareErrCountPixelThreshold", "500")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
prop.store(settingsFile.outputStream(), null)
|
||||||
|
} catch (err: IOException) {
|
||||||
|
throw IllegalStateException("Can't save program settings in file ${settingsFile.absolutePath}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VQCT(private val argumentsData: ArgumentsData) {
|
||||||
|
|
||||||
|
private val settings = ProgramSettings(argumentsData.isSetup, argumentsData.settingsFile)
|
||||||
|
private val imUtils = IMUtils(settings)
|
||||||
|
|
||||||
|
init {
|
||||||
|
logger.info { "Init" }
|
||||||
|
if (!((argumentsData.svgFile == null || argumentsData.basePngFile == null) && argumentsData.isSetup)) {
|
||||||
|
if (imUtils.getImageInfo(argumentsData.svgFile!!).imageFile.extension.toLowerCase() != "svg") {
|
||||||
|
throw IllegalStateException("Can not get info from ${argumentsData.svgFile!!.absolutePath}. May be file is not svg format or file is corrupted!")
|
||||||
|
}
|
||||||
|
if (imUtils.getImageInfo(argumentsData.basePngFile!!).imageFile.extension.toLowerCase() != "png") {
|
||||||
|
throw IllegalStateException("Can not get info from ${argumentsData.basePngFile!!.absolutePath}. May be file is not png format or file is corrupted!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launch() {
|
||||||
|
if ((argumentsData.svgFile == null || argumentsData.basePngFile == null) && argumentsData.isSetup) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.info { "Start processing" }
|
||||||
|
logger.info { "Checking the base png file. If the base png file is larger than the screen resolution, then need to change the picture size of the base png file to the current screen size." }
|
||||||
|
val outFile = prepareBasePNG(argumentsData.basePngFile!!)
|
||||||
|
val resultMessages = mutableListOf<String>()
|
||||||
|
var comparingResult: Boolean
|
||||||
|
argumentsData.browsers!!.forEach {
|
||||||
|
when(it.toUpperCase()) {
|
||||||
|
Browsers.CHROME.name -> {
|
||||||
|
logger.info { "1. Open the svg file in the Chrome / Chromium browser and take a screenshot from this browser." }
|
||||||
|
try {
|
||||||
|
val screenByChrome = outputFileConstructor(argumentsData.svgFile!!, argumentsData.outputDirectory!!.absolutePath, "-screen-by-chrome")
|
||||||
|
FileUtils.copyFile(
|
||||||
|
SvgRenderByChrome(settings, argumentsData.svgFile!!).getScreenshot(imUtils.getImageInfo(outFile).imageSize),
|
||||||
|
screenByChrome
|
||||||
|
)
|
||||||
|
comparingResult = comparing(outFile, screenByChrome)
|
||||||
|
logger.info { "Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from CHROME: ${if (comparingResult) "is equals" else "is not equals"}" }
|
||||||
|
resultMessages.add("Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from CHROME: ${if (comparingResult) "is equals" else "is not equals"}")
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error(err) { "Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from OPERA: ERROR! Info: ${err.message}" }
|
||||||
|
resultMessages.add("Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from OPERA: ERROR! For more information see logger.log file.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Browsers.FIREFOX.name -> {
|
||||||
|
logger.info {"2. Open the svg file in the Firefox browser and take a screenshot from this browser."}
|
||||||
|
try {
|
||||||
|
val screenByFirefox = outputFileConstructor(argumentsData.svgFile!!, argumentsData.outputDirectory!!.absolutePath, "-screen-by-firefox")
|
||||||
|
FileUtils.copyFile(
|
||||||
|
SvgRenderByFirefox(settings, argumentsData.svgFile!!).getScreenshot(imUtils.getImageInfo(outFile).imageSize),
|
||||||
|
screenByFirefox
|
||||||
|
)
|
||||||
|
comparingResult = comparing(outFile, screenByFirefox)
|
||||||
|
logger.info { "Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from FIREFOX: ${if (comparingResult) "is equals" else "is not equals"}" }
|
||||||
|
resultMessages.add("Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from FIREFOX: ${if (comparingResult) "is equals" else "is not equals"}")
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error(err) { "Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from OPERA: ERROR! Info: ${err.message}" }
|
||||||
|
resultMessages.add("Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from OPERA: ERROR! For more information see logger.log file.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Browsers.OPERA.name -> {
|
||||||
|
logger.info { "3. Open the svg file in the Opera browser and take a screenshot from this browser." }
|
||||||
|
try {
|
||||||
|
val screenByOpera = outputFileConstructor(argumentsData.svgFile!!, argumentsData.outputDirectory!!.absolutePath, "-screen-by-opera")
|
||||||
|
FileUtils.copyFile(
|
||||||
|
SvgRenderByOpera(settings, argumentsData.svgFile!!).getScreenshot(imUtils.getImageInfo(outFile).imageSize),
|
||||||
|
screenByOpera
|
||||||
|
)
|
||||||
|
comparingResult = comparing(outFile, screenByOpera)
|
||||||
|
logger.info { "Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from OPERA: ${if (comparingResult) "is equals" else "is not equals"}" }
|
||||||
|
resultMessages.add("Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from OPERA: ${if (comparingResult) "is equals" else "is not equals"}")
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error(err) { "Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from OPERA: ERROR! Info: ${err.message}" }
|
||||||
|
resultMessages.add("Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from OPERA: ERROR! For more information see logger.log file.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Browsers.IE.name -> {
|
||||||
|
if (getOSName() == TypeOfOS.WINDOWS) {
|
||||||
|
logger.info { "4. Open the svg file in the IE browser and take a screenshot from this browser." }
|
||||||
|
try {
|
||||||
|
val screenByIE = outputFileConstructor(argumentsData.svgFile!!, argumentsData.outputDirectory!!.absolutePath, "-screen-by-ie")
|
||||||
|
FileUtils.copyFile(
|
||||||
|
SvgRenderByIE(settings, argumentsData.svgFile!!).getScreenshot(imUtils.getImageInfo(outFile).imageSize),
|
||||||
|
screenByIE
|
||||||
|
)
|
||||||
|
comparingResult = comparing(outFile, screenByIE)
|
||||||
|
logger.info { "Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from IE: ${if (comparingResult) "is equals" else "is not equals"}" }
|
||||||
|
resultMessages.add("Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from IE: ${if (comparingResult) "is equals" else "is not equals"}")
|
||||||
|
} catch (err: Exception) {
|
||||||
|
logger.error(err) { "Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from IE: ERROR! Info: ${err.message}" }
|
||||||
|
resultMessages.add("Result of the comparing base file ${argumentsData.basePngFile!!.name} with screenshot getting from IE: ERROR! For more information see logger.log file.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultMessages.add("Output directory: ${argumentsData.outputDirectory!!.absolutePath}")
|
||||||
|
resultMessages.forEach { println(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analized base png file and if need this file may be modified.
|
||||||
|
* If dimension of base png file more than current screen resolution, then
|
||||||
|
* base png file dimension will be resized to the current screen size.
|
||||||
|
* @param basePNG - base PNG file
|
||||||
|
* @return modified base PNG file.
|
||||||
|
*/
|
||||||
|
private fun prepareBasePNG(basePNG: File): File {
|
||||||
|
val basePNGInfo = imUtils.getImageInfo(basePNG)
|
||||||
|
val widthPercentage = if (basePNGInfo.imageSize.getWidth() > getCurrentScreenResolution().getWidth()) {
|
||||||
|
(((getCurrentScreenResolution().getWidth() - 100) / (basePNGInfo.imageSize.getWidth() * 1.0f)) * 100).roundToInt()
|
||||||
|
} else {
|
||||||
|
100
|
||||||
|
}
|
||||||
|
val heightPercentage = if (basePNGInfo.imageSize.getHeight() > getCurrentScreenResolution().getHeight()) {
|
||||||
|
(((getCurrentScreenResolution().getHeight() - 100) / (basePNGInfo.imageSize.getHeight() * 1.0f)) * 100).roundToInt()
|
||||||
|
} else {
|
||||||
|
100
|
||||||
|
}
|
||||||
|
val percentage = when {
|
||||||
|
widthPercentage < heightPercentage -> widthPercentage
|
||||||
|
widthPercentage > heightPercentage -> heightPercentage
|
||||||
|
widthPercentage == heightPercentage -> widthPercentage
|
||||||
|
else -> 100
|
||||||
|
}
|
||||||
|
FileUtils.forceMkdir(argumentsData.outputDirectory!!)
|
||||||
|
return when {
|
||||||
|
percentage == 100 -> {
|
||||||
|
logger.info { "Size picture of the base png file staying without changes." }
|
||||||
|
basePNG
|
||||||
|
}
|
||||||
|
percentage != 100 -> {
|
||||||
|
val outFile = outputFileConstructor(basePNG, argumentsData.outputDirectory!!.absolutePath, postfix = "-resize")
|
||||||
|
if (imUtils.resizeImage(
|
||||||
|
basePNG,
|
||||||
|
outFile,
|
||||||
|
width = (basePNGInfo.imageSize.getWidth() * percentage / (100 * 1.0f)).roundToInt(),
|
||||||
|
height = (basePNGInfo.imageSize.getHeight() * percentage / (100 * 1.0f)).roundToInt()
|
||||||
|
)) {
|
||||||
|
logger.info { "Size picture of the base png file changed from ${basePNGInfo.imageSize} to the ${imUtils.getImageInfo(outFile).imageSize}" }
|
||||||
|
outFile
|
||||||
|
} else {
|
||||||
|
logger.info { "Size picture of the base png file staying without changes." }
|
||||||
|
basePNG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
logger.info { "Size picture of the base png file staying without changes." }
|
||||||
|
basePNG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparing base image with screenshots from browsers.
|
||||||
|
* @param basePNG
|
||||||
|
* @param screenshot
|
||||||
|
* @return result png file.
|
||||||
|
*/
|
||||||
|
private fun comparing(basePNG: File, screenshot: File): Boolean {
|
||||||
|
val result = imUtils.compareImage(
|
||||||
|
basePNG,
|
||||||
|
screenshot,
|
||||||
|
outputFileConstructor(screenshot, argumentsData.outputDirectory!!.absolutePath, "-compare-result")
|
||||||
|
)
|
||||||
|
logger.info { "Comparing base png file ${basePNG.absolutePath} with screenshot file ${screenshot.absolutePath}. Current absolute error count (number of different pixels) is equals to $result (current threshold = ${settings.getImErrPixelCountThreshold()})" }
|
||||||
|
return result in 0..settings.getImErrPixelCountThreshold()
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/main/resources/simplelogger.properties
Normal file
35
src/main/resources/simplelogger.properties
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# SLF4J's SimpleLogger configuration file
|
||||||
|
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
|
||||||
|
|
||||||
|
org.slf4j.simpleLogger.logFile=logger.log
|
||||||
|
|
||||||
|
# Default logging detail level for all instances of SimpleLogger.
|
||||||
|
# Must be one of ("trace", "debug", "info", "warn", or "error").
|
||||||
|
# If not specified, defaults to "info".
|
||||||
|
# org.slf4j.simpleLogger.defaultLogLevel=debug
|
||||||
|
|
||||||
|
# Logging detail level for a SimpleLogger instance named "xxxxx".
|
||||||
|
# Must be one of ("trace", "debug", "info", "warn", or "error").
|
||||||
|
# If not specified, the default logging detail level is used.
|
||||||
|
|
||||||
|
# Set to true if you want the current date and time to be included in output messages.
|
||||||
|
# Default is false, and will output the number of milliseconds elapsed since startup.
|
||||||
|
org.slf4j.simpleLogger.showDateTime=true
|
||||||
|
|
||||||
|
# The date and time format to be used in the output messages.
|
||||||
|
# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
|
||||||
|
# If the format is not specified or is invalid, the default format is used.
|
||||||
|
# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
|
||||||
|
org.slf4j.simpleLogger.dateTimeFormat=dd-MM-yyyy HH:mm:ss
|
||||||
|
|
||||||
|
# Set to true if you want to output the current thread name.
|
||||||
|
# Defaults to true.
|
||||||
|
org.slf4j.simpleLogger.showThreadName=false
|
||||||
|
|
||||||
|
# Set to true if you want the Logger instance name to be included in output messages.
|
||||||
|
# Defaults to true.
|
||||||
|
org.slf4j.simpleLogger.showLogName=false
|
||||||
|
|
||||||
|
# Set to true if you want the last component of the name to be included in output messages.
|
||||||
|
# Defaults to false.
|
||||||
|
#org.slf4j.simpleLogger.showShortLogName=false
|
||||||
505
src/test/kotlin/ru/resprojects/MainTest.kt
Normal file
505
src/test/kotlin/ru/resprojects/MainTest.kt
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Aleksandr
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.resprojects
|
||||||
|
|
||||||
|
import ru.resprojects.vqct.Browsers
|
||||||
|
import ru.resprojects.vqct.ProgramSettings
|
||||||
|
import org.junit.Assume
|
||||||
|
import org.openqa.selenium.Dimension
|
||||||
|
import ru.resprojects.vqct.IMUtils
|
||||||
|
import ru.resprojects.vqct.ImageInfo
|
||||||
|
import java.io.*
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Aleksandr Aleksandrov aka mrResident
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MainTest {
|
||||||
|
|
||||||
|
private val testSvg = Thread.currentThread().contextClassLoader.getResource("gallardo.svg").toURI().path
|
||||||
|
private val testPng = Thread.currentThread().contextClassLoader.getResource("gallardo.png").toURI().path
|
||||||
|
private val testPng1 = Thread.currentThread().contextClassLoader.getResource("flag_top.png").toURI().path
|
||||||
|
private val testProperties = Thread.currentThread().contextClassLoader.getResource("vqct.properties").toURI().path
|
||||||
|
private val testFile = Thread.currentThread().contextClassLoader.getResource("simplelogger.properties").toURI().path
|
||||||
|
private val testOtherFile = Thread.currentThread().contextClassLoader.getResource("logger.log").toURI().path
|
||||||
|
private val launchTest = try {
|
||||||
|
ProgramSettings().getBrowser(Browsers.CHROME)
|
||||||
|
true
|
||||||
|
} catch (err: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
private val errorMessage1 = "ERROR: Not specified SVG file or Base PNG file! Must be specified minimum two arguments <path_to_svg_file> <path_to_png_file>"
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `run program without argument's`() {
|
||||||
|
Assume.assumeTrue(launchTest)
|
||||||
|
try {
|
||||||
|
main(arrayOf())
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertEquals(usageMessage, err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `run program with minimal arguments`() {
|
||||||
|
Assume.assumeTrue(launchTest)
|
||||||
|
main(arrayOf(testSvg, testPng))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `program stopped with error message`() {
|
||||||
|
val baOut = ByteArrayOutputStream()
|
||||||
|
val out = PrintStream(baOut)
|
||||||
|
val oldout = System.out
|
||||||
|
val olderr = System.err
|
||||||
|
System.setOut(out)
|
||||||
|
System.setErr(out)
|
||||||
|
main(arrayOf(testSvg, "123.png"))
|
||||||
|
System.setOut(oldout)
|
||||||
|
System.setErr(olderr)
|
||||||
|
val s = String(baOut.toByteArray())
|
||||||
|
assertTrue {
|
||||||
|
s.contains("Working stopped with ERROR!")
|
||||||
|
}
|
||||||
|
baOut.reset()
|
||||||
|
System.setOut(out)
|
||||||
|
System.setErr(out)
|
||||||
|
main(arrayOf("--load-settings=$testProperties",testSvg, testPng))
|
||||||
|
System.setOut(oldout)
|
||||||
|
System.setErr(olderr)
|
||||||
|
val s1 = String(baOut.toByteArray())
|
||||||
|
assertTrue {
|
||||||
|
s1.contains("Working stopped with ERROR! For more information see logger.log file!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `run programm with --setup key`() {
|
||||||
|
val `in` = ByteArrayInputStream("\n\n\n\n\n\n\n\n\n".toByteArray())
|
||||||
|
val old = System.`in`
|
||||||
|
System.setIn(`in`)
|
||||||
|
main(arrayOf("--setup"))
|
||||||
|
System.setIn(old)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `run programm with --setup key and minimal arguments`() {
|
||||||
|
Assume.assumeTrue(launchTest)
|
||||||
|
val `in` = ByteArrayInputStream("\n\n\n\n\n\n\n\n\n".toByteArray())
|
||||||
|
val old = System.`in`
|
||||||
|
System.setIn(`in`)
|
||||||
|
main(arrayOf("--setup", testSvg, testPng))
|
||||||
|
System.setIn(old)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `run program with --help key`() {
|
||||||
|
val baOut = ByteArrayOutputStream()
|
||||||
|
val out = PrintStream(baOut)
|
||||||
|
val oldout = System.out
|
||||||
|
val olderr = System.err
|
||||||
|
System.setOut(out)
|
||||||
|
System.setErr(out)
|
||||||
|
main(arrayOf("--help"))
|
||||||
|
System.setOut(oldout)
|
||||||
|
System.setErr(olderr)
|
||||||
|
val s = String(baOut.toByteArray())
|
||||||
|
assertTrue {
|
||||||
|
s.contains(usageMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun `throws exception if input data for parseArguments is empty`() {
|
||||||
|
parseArguments(arrayOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments return correct data`() {
|
||||||
|
val argData = parseArguments(arrayOf("--setup", "--load-settings=$testProperties", "--browsers=chrome,opera", "--out=/out/dir", testSvg, testPng))
|
||||||
|
assertNotNull(argData.svgFile)
|
||||||
|
assertNotNull(argData.basePngFile)
|
||||||
|
assertNotNull(argData.outputDirectory)
|
||||||
|
assertNotNull(argData.browsers)
|
||||||
|
assertNotNull(argData.settingsFile)
|
||||||
|
assertTrue {
|
||||||
|
argData.isSetup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments return correct data if used key --out with short path or fullt path`() {
|
||||||
|
val argData = parseArguments(arrayOf("--out=/out/dir", testSvg, testPng))
|
||||||
|
assertNotNull(argData.outputDirectory)
|
||||||
|
val argData1 = parseArguments(arrayOf("--out=dir/path", testSvg, testPng))
|
||||||
|
assertNotNull(argData1.outputDirectory)
|
||||||
|
val argData2 = parseArguments(arrayOf("--out=dir/path/", testSvg, testPng))
|
||||||
|
assertNotNull(argData2.outputDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments return correct data if input only one key --setup`() {
|
||||||
|
val argData = parseArguments(arrayOf("--setup"))
|
||||||
|
assertNull(argData.svgFile)
|
||||||
|
assertNull(argData.basePngFile)
|
||||||
|
assertNull(argData.outputDirectory)
|
||||||
|
assertNull(argData.browsers)
|
||||||
|
assertNull(argData.settingsFile)
|
||||||
|
assertTrue {
|
||||||
|
argData.isSetup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments throws if not specified minimal arguments`() {
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf(testSvg))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertEquals(errorMessage1, err.message)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf(testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertEquals(errorMessage1, err.message)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("123.svg", testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: File")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf(testSvg, "123.png"))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: File")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments throws if key value --load-settings is invalid`() {
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--load-settings=$testOtherFile", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Invalid properties file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--load-settings=/123/file.properties", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Properties file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--load-settings", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Invalid key value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--load-settings=", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Invalid key value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments throws if key value --browsers is invalid`() {
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--browsers", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Invalid key value --browsers.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--browsers=", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Invalid key value --browsers.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--browsers=123,chrome", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Invalid value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments throws if key value --out is invalid`() {
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--out", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Invalid key value --out.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--out=", testSvg, testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("ERROR: Invalid key value --out.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments throws if not specified minimal arguments with key --setup`() {
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf(testSvg, "--setup"))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertEquals(errorMessage1, err.message)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf("--setup", testPng))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertEquals(errorMessage1, err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseArguments throws if unexpected key value`() {
|
||||||
|
try {
|
||||||
|
parseArguments(arrayOf(testSvg, testPng, "-key"))
|
||||||
|
} catch (err: Exception) {
|
||||||
|
println(err.message)
|
||||||
|
assertTrue {
|
||||||
|
err.message!!.startsWith("Invalid input", ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create settings file`() {
|
||||||
|
ProgramSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `load settings from custom file`() {
|
||||||
|
val file = File("custom.properties")
|
||||||
|
val prop = Properties()
|
||||||
|
prop.setProperty(Browsers.CHROME.name, "")
|
||||||
|
prop.store(file.outputStream(), null)
|
||||||
|
ProgramSettings(false, File("custom.properties"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `auto creating settings in custom file`() {
|
||||||
|
ProgramSettings(false, File("custom_2.properties"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `throws exception if parameter value is not found`() {
|
||||||
|
val file = File("vqct_test.properties")
|
||||||
|
val prop = Properties()
|
||||||
|
prop.setProperty("any_data", "any_data")
|
||||||
|
prop.store(file.outputStream(), null)
|
||||||
|
val ps = ProgramSettings(false, file)
|
||||||
|
try {
|
||||||
|
ps.getBrowser(Browsers.CHROME)
|
||||||
|
} catch (err: Exception) {
|
||||||
|
assertEquals("Can't get file because parameter \"CHROME\" in settings file is empty.", err.message.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FileNotFoundException::class)
|
||||||
|
fun `throws exception if browser not found`() {
|
||||||
|
val file = File("vqct_test.properties")
|
||||||
|
val prop = Properties()
|
||||||
|
prop.setProperty(Browsers.CHROME.name, "")
|
||||||
|
prop.store(file.outputStream(), null)
|
||||||
|
val ps = ProgramSettings(false, file)
|
||||||
|
ps.getBrowser(Browsers.CHROME)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FileNotFoundException::class)
|
||||||
|
fun `throws exception if webdriver not found`() {
|
||||||
|
val file = File("vqct_test.properties")
|
||||||
|
val prop = Properties()
|
||||||
|
prop.setProperty("${Browsers.CHROME.name}_WEBDRIVER", "")
|
||||||
|
prop.store(file.outputStream(), null)
|
||||||
|
val ps = ProgramSettings(false, file)
|
||||||
|
ps.getWebDriver(Browsers.CHROME)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FileNotFoundException::class)
|
||||||
|
fun `throws exception if imagemagick not found`() {
|
||||||
|
val file = File("vqct_test.properties")
|
||||||
|
val prop = Properties()
|
||||||
|
prop.setProperty("IMAGEMAGICK", "")
|
||||||
|
prop.store(file.outputStream(), null)
|
||||||
|
val ps = ProgramSettings(false, file)
|
||||||
|
ps.getImageMagick()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException::class)
|
||||||
|
fun `throws exception while save settings file`() {
|
||||||
|
val file = File("/prop.properties")
|
||||||
|
ProgramSettings(false, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Get default imCompareFuzzValue value`() {
|
||||||
|
val ps = ProgramSettings()
|
||||||
|
assertEquals(15.0, ps.getImFuzz())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Get default imCompareErrCountPixelThreshold value`() {
|
||||||
|
val ps = ProgramSettings()
|
||||||
|
assertEquals(500, ps.getImErrPixelCountThreshold())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Throws exception while get value from parameter imCompareFuzzValue`() {
|
||||||
|
val file = File("vqct_test.properties")
|
||||||
|
val prop = Properties()
|
||||||
|
prop.setProperty("imCompareFuzzValue", "12d")
|
||||||
|
prop.store(file.outputStream(), null)
|
||||||
|
val ps = ProgramSettings(false, file)
|
||||||
|
try {
|
||||||
|
ps.getImFuzz()
|
||||||
|
} catch (err: IllegalStateException) {
|
||||||
|
assertEquals("Value of parameter \"imCompareFuzzValue\" is not Double.", err.message.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Throws exception while get value from parameter imCompareErrCountPixelThreshold`() {
|
||||||
|
val file = File("vqct_test.properties")
|
||||||
|
val prop = Properties()
|
||||||
|
prop.setProperty("imCompareErrCountPixelThreshold", "12d")
|
||||||
|
prop.store(file.outputStream(), null)
|
||||||
|
val ps = ProgramSettings(false, file)
|
||||||
|
try {
|
||||||
|
ps.getImErrPixelCountThreshold()
|
||||||
|
} catch (err: IllegalStateException) {
|
||||||
|
assertEquals("Value of parameter \"imCompareErrCountPixelThreshold\" is not Integer.", err.message.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Getting Image Info from file`() {
|
||||||
|
val imageUtils = IMUtils(ProgramSettings())
|
||||||
|
val imageInfo = imageUtils.getImageInfo(File(testPng))
|
||||||
|
println("Image size = ${imageInfo.imageSize}")
|
||||||
|
println("Image quality = ${imageInfo.imageQuality}")
|
||||||
|
println("Image format = ${imageInfo.imageFormat}")
|
||||||
|
println("Image file path = ${imageInfo.imageFile.absolutePath}")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FileNotFoundException::class)
|
||||||
|
fun `Throws exception while getting image info if file not exists`() {
|
||||||
|
IMUtils(ProgramSettings()).getImageInfo(File("/123"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException::class)
|
||||||
|
fun `Throws exception while getting image info if file not image`() {
|
||||||
|
IMUtils(ProgramSettings()).getImageInfo(File(testFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException::class)
|
||||||
|
fun `Throws exception while getting Image Info`() {
|
||||||
|
ImageInfo(listOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Cropping image`() {
|
||||||
|
val imUtils = IMUtils(ProgramSettings())
|
||||||
|
val testPngFile = File(testPng)
|
||||||
|
assertEquals(true, imUtils.cropImage(
|
||||||
|
testPngFile,
|
||||||
|
File("testpng_cropping.png"),
|
||||||
|
imUtils.getImageInfo(testPngFile).imageSize,
|
||||||
|
-100, -100
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Error while cropping image`() {
|
||||||
|
val imUtils = IMUtils(ProgramSettings())
|
||||||
|
val testPngFile = File(testFile)
|
||||||
|
assertEquals(false, imUtils.cropImage(
|
||||||
|
testPngFile,
|
||||||
|
File("testpng_cropping.png"),
|
||||||
|
Dimension(2048, 2048),
|
||||||
|
0, 0
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Trimming image`() {
|
||||||
|
val imUtils = IMUtils(ProgramSettings())
|
||||||
|
val testPngFile = File(testPng1)
|
||||||
|
assertEquals(true, imUtils.trimImage(
|
||||||
|
testPngFile,
|
||||||
|
File("testpng1_trimming.png")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Error while trimming image`() {
|
||||||
|
val imUtils = IMUtils(ProgramSettings())
|
||||||
|
val testPngFile = File(testFile)
|
||||||
|
assertEquals(false, imUtils.trimImage(
|
||||||
|
testPngFile,
|
||||||
|
File("testpng1_trimming.png")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Resizing image`() {
|
||||||
|
val imUtils = IMUtils(ProgramSettings())
|
||||||
|
val testPngFile = File(testPng)
|
||||||
|
assertEquals(true, imUtils.resizeImage(
|
||||||
|
testPngFile,
|
||||||
|
File("testpng_resising.png"),
|
||||||
|
256, 256
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Error while resizing image`() {
|
||||||
|
val imUtils = IMUtils(ProgramSettings())
|
||||||
|
val testPngFile = File(testFile)
|
||||||
|
assertEquals(false, imUtils.resizeImage(
|
||||||
|
testPngFile,
|
||||||
|
File("testpng_resising.png"),
|
||||||
|
256, 256
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/test/resources/flag_top.png
Normal file
BIN
src/test/resources/flag_top.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
23
src/test/resources/flag_top.svg
Normal file
23
src/test/resources/flag_top.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 1013 1308.9" style="enable-background:new 0 0 1013 1308.9;" xml:space="preserve">
|
||||||
|
<path d="M415.8,332.5l3,1.5c1,0.5,2.1-0.2,2.1-1.3v-42.2c0-0.5-0.3-1-0.7-1.3l-0.9-0.5c-20.2-11.3-36.8-17.9-54-21.5l-2.5-0.5
|
||||||
|
c-0.9-0.2-1.7,0.5-1.7,1.4v38.2c0,0.9-0.7,1.5-1.6,1.4c-14-1.5-27.9-1.9-43-1.1c-0.8,0-1.5-0.6-1.5-1.4v-41.1c0-0.8-0.7-1.5-1.5-1.4
|
||||||
|
l-2.1,0.1c-16.5,0.9-33.5,2.7-53.4,5.7l-1.6,0.3c-0.7,0.1-1.2,0.7-1.2,1.4V315c0,0.7-0.5,1.3-1.1,1.4c-7.6,1.7-15.3,3.5-22.8,5.2
|
||||||
|
c-6.7,1.5-13.6,3.1-20.5,4.6c-0.9,0.2-1.8-0.5-1.8-1.4v-46.2c0-0.9-0.8-1.6-1.7-1.4l-2.3,0.4c-19.7,3-36.4,4.8-52.7,5.7l-1.9,0.1
|
||||||
|
c-0.8,0-1.4,0.7-1.4,1.4v50.1c0,0.8-0.6,1.4-1.4,1.4c-15.3,0.8-29.4,0.4-43.5-1.2c-0.7-0.1-1.3-0.7-1.3-1.4v-53
|
||||||
|
c0-0.7-0.5-1.3-1.1-1.4l-1.5-0.3c-16.7-3.7-33.2-10.3-52-20.8l-3.1-1.7c-1-0.5-2.1,0.2-2.1,1.3v62.8c0,0.5,0.3,1,0.8,1.3l1,0.5
|
||||||
|
c16.6,8.3,32.6,13.9,50.1,17.7c0.7,0.1,1.1,0.7,1.1,1.4v46.7c0,0.9-0.8,1.6-1.7,1.4c-16.2-2.8-30.8-7.2-46.4-14.2l-2.9-1.3
|
||||||
|
c-1-0.4-2,0.3-2,1.3v62.5c0,0.6,0.4,1.1,0.9,1.3l1.2,0.5c18.1,7,34.8,10.9,54,12.7l2.2,0.2c0.8,0.1,1.6-0.6,1.6-1.4v-52.7
|
||||||
|
c0-0.8,0.7-1.5,1.5-1.4c4.7,0.3,9.3,0.4,14,0.4c9.4,0,18.9-0.5,29-1.6c0.8-0.1,1.6,0.6,1.6,1.4v49.9c0,0.9,0.8,1.6,1.7,1.4l2.5-0.5
|
||||||
|
c16.5-3.6,33.5-8.2,53.3-14.5l1.3-0.4c0.6-0.2,1-0.7,1-1.4V384c0-0.6,0.4-1.2,1.1-1.4c7.7-2.1,15.5-4.3,23-6.5
|
||||||
|
c6.6-1.9,13.5-3.8,20.3-5.7c0.9-0.3,1.8,0.4,1.8,1.4v43.3c0,1,0.9,1.7,1.9,1.4l2.7-0.9c19.7-6.2,36.4-10.8,52.7-14.3l1.5-0.3
|
||||||
|
c0.7-0.1,1.1-0.7,1.1-1.4v-41.4c0-0.7,0.6-1.4,1.3-1.4c15.3-1.7,29.4-2.1,43.5-1.2c0.8,0,1.4,0.7,1.4,1.4v38.4
|
||||||
|
c0,0.7,0.6,1.4,1.3,1.4l1.8,0.2c18.5,1.7,34.6,5.4,52,12.2l2.8,1.1c0.9,0.4,2-0.3,2-1.3v-42c0-0.6-0.3-1.1-0.9-1.3l-1.1-0.5
|
||||||
|
c-16.8-7.4-32.3-12.1-49.9-15c-0.7-0.1-1.2-0.7-1.2-1.4v-31.6c0-0.9,0.9-1.6,1.8-1.4C385.8,319.5,400.2,324.7,415.8,332.5z
|
||||||
|
M306.8,351.7c-13.5,2.3-27.4,5.3-43.2,9.3c-0.9,0.2-1.8-0.5-1.8-1.4v-37.3c0-0.7,0.5-1.3,1.2-1.4c15.9-3.2,29.9-5.5,43.4-7.2
|
||||||
|
c0.9-0.1,1.6,0.6,1.6,1.4v35.1C308,351,307.5,351.6,306.8,351.7z M200.8,378.4c-15.9,4.1-29.9,7.2-43.4,9.6
|
||||||
|
c-0.9,0.2-1.7-0.5-1.7-1.4v-43.3c0-0.7,0.5-1.3,1.3-1.4c13.5-1.6,27.4-3.9,43.1-7c0.9-0.2,1.7,0.5,1.7,1.4V377
|
||||||
|
C201.9,377.7,201.5,378.3,200.8,378.4z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/test/resources/gallardo.png
Normal file
BIN
src/test/resources/gallardo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 826 KiB |
5155
src/test/resources/gallardo.svg
Normal file
5155
src/test/resources/gallardo.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 333 KiB |
1
src/test/resources/logger.log
Normal file
1
src/test/resources/logger.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
18-03-2018 00:46:06 INFO Init
|
||||||
40
src/test/resources/simplelogger.properties
Normal file
40
src/test/resources/simplelogger.properties
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# SLF4J's SimpleLogger configuration file
|
||||||
|
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
|
||||||
|
|
||||||
|
org.slf4j.simpleLogger.logFile=logger_test.log
|
||||||
|
|
||||||
|
# Default logging detail level for all instances of SimpleLogger.
|
||||||
|
# Must be one of ("trace", "debug", "info", "warn", or "error").
|
||||||
|
# If not specified, defaults to "info".
|
||||||
|
org.slf4j.simpleLogger.defaultLogLevel=debug
|
||||||
|
|
||||||
|
# Logging detail level for a SimpleLogger instance named "xxxxx".
|
||||||
|
# Must be one of ("trace", "debug", "info", "warn", or "error").
|
||||||
|
# If not specified, the default logging detail level is used.
|
||||||
|
org.slf4j.simpleLogger.log.debug=debug
|
||||||
|
org.slf4j.simpleLogger.log.error=error
|
||||||
|
org.slf4j.simpleLogger.log.info=info
|
||||||
|
org.slf4j.simpleLogger.log.trace=trace
|
||||||
|
org.slf4j.simpleLogger.log.warn=warn
|
||||||
|
|
||||||
|
# Set to true if you want the current date and time to be included in output messages.
|
||||||
|
# Default is false, and will output the number of milliseconds elapsed since startup.
|
||||||
|
org.slf4j.simpleLogger.showDateTime=true
|
||||||
|
|
||||||
|
# The date and time format to be used in the output messages.
|
||||||
|
# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
|
||||||
|
# If the format is not specified or is invalid, the default format is used.
|
||||||
|
# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
|
||||||
|
org.slf4j.simpleLogger.dateTimeFormat=dd-MM-yyyy HH:mm:ss
|
||||||
|
|
||||||
|
# Set to true if you want to output the current thread name.
|
||||||
|
# Defaults to true.
|
||||||
|
org.slf4j.simpleLogger.showThreadName=true
|
||||||
|
|
||||||
|
# Set to true if you want the Logger instance name to be included in output messages.
|
||||||
|
# Defaults to true.
|
||||||
|
org.slf4j.simpleLogger.showLogName=true
|
||||||
|
|
||||||
|
# Set to true if you want the last component of the name to be included in output messages.
|
||||||
|
# Defaults to false.
|
||||||
|
org.slf4j.simpleLogger.showShortLogName=true
|
||||||
10
src/test/resources/vqct.properties
Normal file
10
src/test/resources/vqct.properties
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#Sun Mar 18 01:36:25 MSK 2018
|
||||||
|
IMAGEMAGICK=
|
||||||
|
CHROME=/usr/bin/google-chrome
|
||||||
|
FIREFOX_WEBDRIVER=/media/mrresident/archive/Nextcloud/projects/gitlab/visual-quality-control-tool/geckodriver
|
||||||
|
imCompareFuzzValue=15
|
||||||
|
CHROME_WEBDRIVER=/media/mrresident/archive/Nextcloud/projects/gitlab/visual-quality-control-tool/chromedriver
|
||||||
|
imCompareErrCountPixelThreshold=500
|
||||||
|
OPERA=/usr/bin/opera
|
||||||
|
FIREFOX=/usr/bin/firefox
|
||||||
|
OPERA_WEBDRIVER=/media/mrresident/archive/Nextcloud/projects/gitlab/visual-quality-control-tool/operadriver
|
||||||
Reference in New Issue
Block a user