Initial commit
This commit is contained in:
commit
f280b8c44c
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
*/target/*
|
||||||
|
*/allure-results/*
|
||||||
|
.idea/*
|
||||||
|
*.iml
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.ctxt
|
||||||
|
.mtj.tmp/
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
hs_err_pid*
|
||||||
|
*.allure/*
|
||||||
|
.build/
|
||||||
|
idea/
|
||||||
275
README.md
Normal file
275
README.md
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
## Шаблон Фреймворка для запуска автотестов
|
||||||
|
BDD фреймворк для автотестов на Java, использующий:
|
||||||
|
- [Selenide](https://ru.selenide.org) для тестирования Web UI
|
||||||
|
- [Cucumber](https://cucumber.io) для написания сценариев в стиле BDD
|
||||||
|
- [REST assured](https://rest-assured.io) для тестирования REST API
|
||||||
|
|
||||||
|
## Как начать писать WEB автотесты
|
||||||
|
## 1. Page Objects
|
||||||
|
В модуле ***autotest-web*** в директории ***src/main/java/pages*** находятся классы ***PageObjects***<br/>
|
||||||
|
**1.1** Каждый ***PageObject*** должен наследоваться от класса ***WebPage***<br/>
|
||||||
|
**1.2** Над классом необходимо проставить аннотацию *@Name(**value** = "<имя страницы>")*<br/>
|
||||||
|
Пример:<br/>
|
||||||
|
```java
|
||||||
|
@Name(value = "Главная страница приложения")
|
||||||
|
public class MainPage extends WebPage {
|
||||||
|
|
||||||
|
@Name("Слайдер")
|
||||||
|
private SelenideElement uuid = $(".slick-track");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## 2. Степы
|
||||||
|
**2.1** В классе со степами необходимо наследовать от класса AbstractWebSteps и конструктор класса следующим образом:<br/>
|
||||||
|
```java
|
||||||
|
public class WebActionSteps extends AbstractWebSteps {
|
||||||
|
|
||||||
|
public WebActionSteps(PageManager pageManager) {
|
||||||
|
super(pageManager);
|
||||||
|
}
|
||||||
|
// steps
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* ссылка ***pageManager*** хранит в себе инициализированный контекст текущей страницы, с помощью которой можно достать элемент через ***value*** аннотации ***@Name*** элемента <br/>
|
||||||
|
* При компиляции ссылка ***pageManager*** проинициализируется автоматически путем Dependency Injection через PicoContainer <br/>
|
||||||
|
* Более подробно о подходе можно ознакомиться по ссылке [Cucumber PicoContainer](https://cucumber.io/docs/cucumber/state/) <br/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**2.2** Пример инициализации страницы:<br/>
|
||||||
|
Для того, чтобы получить доступ к элементу, нам необходимо перед этим проинициализировать ***PageObject*** <br/>
|
||||||
|
**pageName** - это value аннотации **Name** класса ***PageObject*** - в нашем примере *"Google"*
|
||||||
|
```java
|
||||||
|
public void setPage(String pageName) {
|
||||||
|
WebPage page = getPage(pageName);
|
||||||
|
pageManager.setCurrentPage(page);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**2.3** Теперь страница проинициализирована и получить доступ к элементам можно по его имени ***(value)***<br/>
|
||||||
|
```java
|
||||||
|
@Если("кликнуть на элемент {string}")
|
||||||
|
public void clickOnElement(String elementName) {
|
||||||
|
SelenideElement element = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName);
|
||||||
|
element.shouldBe(visible).click();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**2.4** Осуществление мягких проверок (SoftAssert)<br/>
|
||||||
|
В конфигурации Selenide есть параметр Configuration.assertionMode, который не отрабатывает должным образом в связке с Cucumber и потому его использование в проекте не допускается.
|
||||||
|
Для осуществления мягких проверок следует использовать класс ru.lanit.at.assertion.AssertsManager.
|
||||||
|
Пример использования (Проверка утверждений у SelenideElement)
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static void elementContainsText(SelenideElement element, String text) {
|
||||||
|
element.execute(Commands.checkSoft(Condition.text(text), Duration.ofSeconds(10)));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**2.5** Расширение и изменения методов SelenideElement с помощью механизма SPI <br/>
|
||||||
|
Если нужно изменить поведение стандартных методов SelenideElement, это можно сделать след образом.
|
||||||
|
В классе ru.lanit.at.utils.selenide.extensions.CustomCommands, необходимо релизовать свою логику, например код ниже, будет перед осуществлением клика, прикладывать скриншот элемента, на который будет происходить нажатие.
|
||||||
|
```java
|
||||||
|
public <T> T execute(Object proxy, WebElementSource webElementSource, String methodName, @Nullable Object[] args) throws IOException {
|
||||||
|
if(methodName.equals("click")){
|
||||||
|
addAllureScreenshootElement((SelenideElement)proxy);
|
||||||
|
}
|
||||||
|
return super.execute(proxy, webElementSource, methodName, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAllureScreenshootElement(SelenideElement selenideElement){
|
||||||
|
AllureHelper.attachScreenShot("Клик на элементе ", selenideElement.getScreenshotAs(OutputType.BYTES));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Изменения логики формирования Page объектов, осуществляется в классе ru.lanit.at.utils.selenide.extensions.CustomSelenidePageFactory. С помощью этого класса можно расширить/изменить логику инициализации элементов.
|
||||||
|
|
||||||
|
## 3. Тесты
|
||||||
|
|
||||||
|
**3.1** Написание сценариев
|
||||||
|
```gherkin
|
||||||
|
#language:ru
|
||||||
|
Функционал: Поиск гугл
|
||||||
|
Сценарий: Открытие страницы google.com, ввод значения в поиск
|
||||||
|
|
||||||
|
* открыть браузер
|
||||||
|
* инициализация страницы "Google"
|
||||||
|
* ввести в поле "поле поиска" значение "Погода в Москве"
|
||||||
|
* на странице имеется элемент "результаты поиска"
|
||||||
|
* кликнуть на элемент "кнопка поиска"
|
||||||
|
* инициализация страницы "страница результатов поиска"
|
||||||
|
* на странице присутствует текст "Погода в Москве"
|
||||||
|
```
|
||||||
|
* Шаг 1 - открытие веб страницы
|
||||||
|
* Шаг 2 - инициализация ***PageObject*** через его ***value*** аннотации ***@Name***
|
||||||
|
* Шаг 3 - как в примере **2.3** получаем текущий элемент по его ***value*** аннотации ***@Name*** и производим действия/проверки
|
||||||
|
|
||||||
|
**3.2** Сценарии из фрагментов
|
||||||
|
Начиная с версии 1.2, фреймворк поддержиивает напиисание сценариев из фрагментов. Фрагменты должны храниться в
|
||||||
|
директории "fragments" в корне проекта в файлах с расширением *.feature. Каждый фрагмент должен быть выделен в отдельный
|
||||||
|
сценарий и аннотирован @fragment
|
||||||
|
```gherkin
|
||||||
|
@fragment
|
||||||
|
Сценарий: открытие виджета
|
||||||
|
|
||||||
|
* на странице имеется элемент "кнопка поиска"
|
||||||
|
* кликнуть на элемент "кнопка поиска"
|
||||||
|
* переход на страницу "Google страница результатов"
|
||||||
|
* на странице имеется элемент "виджет погоды"
|
||||||
|
```
|
||||||
|
Чтобы использовать фрагмент, в сценарии следует добавить шаг вида
|
||||||
|
|
||||||
|
```gherkin
|
||||||
|
* вызвать фрагмент "открытие виджета"
|
||||||
|
```
|
||||||
|
Передаваемый текстовый параметр шага должен соответвовать имени сценария, содержащего фрагмент. Перед запуском тестов,
|
||||||
|
фреймворк анализирует содержимое всех фича-файлов и заменяет шаг * вызвать фрагмент"{string}" на группу шагов из
|
||||||
|
соотвествующего фрагмента.
|
||||||
|
!IMPORTANT!
|
||||||
|
Чтобы функционал работал корректно, компиляцию проекта необходимо выполнять при помощи команды mvn compile.
|
||||||
|
Для запуска фич по отдельности через функционал или отладчик IDEA внесите измение в шаблон конфигураци Cucumber:
|
||||||
|
Run/Debug Configurations/ Edit configurations template / Cucumber Java / Build Добавить действие Maven "compile"
|
||||||
|

|
||||||
|
Для запуска фрагментов через Maven дополнительных настроек не требуется.
|
||||||
|
|
||||||
|
## 4. Настройки
|
||||||
|
В директории ***autotest-web/src/test/resources/config*** имеются примеры *config-файлов* для разных браузеров и общих настроек запуска.
|
||||||
|
|
||||||
|
configuration.properties
|
||||||
|
```properties
|
||||||
|
stand= названия стенда для тестирования в классе (ru.lanit.at.utils.Stand) содержатся адреса стендов
|
||||||
|
screen_after_step=false - необходимость прикреплять скриншот к каждому шагу
|
||||||
|
baseUrl=https://petstore.swagger.io/v2/ - базовый url для апи запросов
|
||||||
|
|
||||||
|
remoteUrl=127.0.0.1:4444 - адрес удаленного хаба/selenoid.
|
||||||
|
enableVNC=true - возможность отображения работы теста в браузере, переменная применима только selenoid.
|
||||||
|
enableVideo=true - возможность записывать видео, переменная применима только selenoid.
|
||||||
|
enableLog=true - флаг для сохранения логов selenoid контейнера, переменная применима только selenoid.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
chrome.properties
|
||||||
|
```properties
|
||||||
|
webdriver.browser.size=1920x1080 - разрешение браузера
|
||||||
|
webdriver.browser.name=chrome - название браузера
|
||||||
|
webdriver.timeoutSeconds=4 - таймаут ожидания состояния веб-элементов
|
||||||
|
polling.timeoutMs=200 - периодичность опроса веб-элемента
|
||||||
|
webdriver.version=91.0 - версия веб-драйвера
|
||||||
|
|
||||||
|
```
|
||||||
|
Так же с помощью одноименных переменных окружения можно переопределить данные настройки<br>
|
||||||
|
Драйвера скачиваются с помощью **WebDriverManager**, учитывайте это если находитесь в закрытом контуре
|
||||||
|
|
||||||
|
## Как начать писать API тесты
|
||||||
|
### Принцип написания тестов похож на подход создания и отправки запроса в Postman
|
||||||
|
*Шаг 1. Конфигурируем запрос с помощью шага*
|
||||||
|
```gherkin
|
||||||
|
* создать запрос
|
||||||
|
| method | path | body | url |
|
||||||
|
```
|
||||||
|
Если какой то из столбцов не указан в данном шаге, то он не учитывается в запросе
|
||||||
|
Например:
|
||||||
|
```gherkin
|
||||||
|
* создать запрос
|
||||||
|
| method | path | body |
|
||||||
|
| POST | /user | createUser.json |
|
||||||
|
ИЛИ
|
||||||
|
* создать запрос
|
||||||
|
| method | path | body |
|
||||||
|
| POST | /user | {<тело запроса>} |
|
||||||
|
ИЛИ
|
||||||
|
* создать запрос
|
||||||
|
| method | url |
|
||||||
|
| GET | https://petstore.swagger.io/v2/user/<username> |
|
||||||
|
```
|
||||||
|
* Можно указать ***basePath*** через одноименную системную переменную или в файле конфигурации ***configuration.properties***. Тогда вместо столбца url можно указывать просто path. И наоборот, если указать столбец url с полным url хоста и path то basePath не учитывается, даже если указан с системных переменных.
|
||||||
|
* Тело запроса - в качестве тела можно передать в таблицу, как просто текст, так и название файла ***json***, которое будет лежать по пути ***autotest-rest/src/test/resources/json***
|
||||||
|
|
||||||
|
*Шаг 2. Добавление Headers и Query*
|
||||||
|
```gherkin
|
||||||
|
* добавить header
|
||||||
|
| Content-Type | application/json |
|
||||||
|
* добавить query параметры
|
||||||
|
| city | Moscow |
|
||||||
|
```
|
||||||
|
*Шаг 3. Отправка запроса*
|
||||||
|
```gherkin
|
||||||
|
* отправить запрос
|
||||||
|
```
|
||||||
|
*Шаг 4. Проверка ответа*
|
||||||
|
```gherkin
|
||||||
|
* статус код 200
|
||||||
|
```
|
||||||
|
Если необходимо проверить тело ответа, то данные можно вытащить с помощью jsonpath. Значение сохранится в переменную из столбца 1<br/>
|
||||||
|
```gherkin
|
||||||
|
* извлечь данные
|
||||||
|
| user_id | $.message |
|
||||||
|
```
|
||||||
|
Проверить извлеченные данные можно с помощью шага:
|
||||||
|
```gherkin
|
||||||
|
* сравнить значения
|
||||||
|
| ${user_id} | != | null |
|
||||||
|
ИЛИ
|
||||||
|
| ${user_id} | == | 1234567890 |
|
||||||
|
ИЛИ
|
||||||
|
| ${user_id} | > | 0 |
|
||||||
|
ИЛИ
|
||||||
|
| ${user_id} | < | 100 |
|
||||||
|
ИЛИ
|
||||||
|
| ${user_id} | содержит | qwerty123 |
|
||||||
|
```
|
||||||
|
### Иная информация.
|
||||||
|
С помощью следующего шага можно сгенерить переменные для последующего использования в тесте
|
||||||
|
```gherkin
|
||||||
|
* сгенерировать переменные
|
||||||
|
| id | 0 |
|
||||||
|
| username | EEEEEEEE |
|
||||||
|
| firstName | EEEEEEEE |
|
||||||
|
| lastName | EEEEEEEE |
|
||||||
|
| email | EEEEEEE@EEEDDD.EE |
|
||||||
|
| password | DDDEEEDDDEEE |
|
||||||
|
```
|
||||||
|
**R** - случайная русская буква<br/>
|
||||||
|
**E** - случайная английская буква<br/>
|
||||||
|
**D** - случайное число<br/>
|
||||||
|
Другие символы в строке игнорируются и остаются неизменяемыми
|
||||||
|
Сгенерированные значения хранятся в контексте теста. Их можно подставлять в запросы, тела запросов. Достать их можно используя синтаксис ***${username}***<br/>
|
||||||
|
|
||||||
|
### Запуск тестов через консоль
|
||||||
|
|
||||||
|
```java
|
||||||
|
mvn clean test -Ddataproviderthreadcount=4 -Dscreen_after_step=false -Dtags="@authentication"
|
||||||
|
```
|
||||||
|
* ***-Ddataproviderthreadcount=4*** - кол-во поток выполнения, значение по умолчанию 1 поток (Изменение дефолтного кол-ва поток производится в файле src\test\resources\suite.xml)
|
||||||
|
|
||||||
|
* ***-Dscreen_after_step=false*** - Автоматическое снятие скриншотов после каждого шага. Дефолтное значение =false (Изменение дефолтного значения производится в файле src\test\resources\config\configuration.properties)
|
||||||
|
|
||||||
|
* ***-Dtags="@authentication"*** - Выбор тестов с определенным тегом. Дефолтное значение отсутствует, т.е. если в запуске не указывать данный параметр то, будет запущены все фичи файлы.
|
||||||
|
|
||||||
|
>Тажке в командную строку можно передать любой параметр из файлов *.properties
|
||||||
|
>Для добавление нового параметра, его нужно будет прописать в соответствующем .properties файле и добавить соответствущий полю getter в java классе.
|
||||||
|
|
||||||
|
#### Для запуска с дефолтными параметрами используется команда.
|
||||||
|
```java
|
||||||
|
mvn clean test
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Так же запустить тесты можно через плагин Cucumber (предварительно установив его в Idea), для этого необходимо открыть любой feature-файл, и кликнуть по зеленой стрелке рядом со стройкой **Функционал** или **Сценарий**<br/>
|
||||||
|

|
||||||
|
|
||||||
|
### Генерация отчета
|
||||||
|
По итогу прогонов можно сгенерить _Allure отчет_, для этого необходимо в Intellij Idea кликнуть на строку **Maven** в правом верхнем углу IDE и следовать инструкции по пунктам ниже:<br/>
|
||||||
|
В **п.1** необходимо выбрать тот модуль, в котором запускались тесты.<br/>
|
||||||
|
<br/>
|
||||||
|
**По итогу сформируется Html страница с отчетом.**<br/>
|
||||||
|
<br/>
|
||||||
|
**В отчете можно провалиться в каждый шаг и посмотреть информацию по нему**<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
где
|
||||||
|
* Зеленым цветом отмечены - успешно прошедшие тесты
|
||||||
|
* Желтым цветом отмечены - тесты в которых есть неблокирующие дефекты
|
||||||
|
* Серым цветом отмечены - тесты который были пропущены
|
||||||
|
* Красным цветом отмечены - тесты с блокирующим дефектом
|
||||||
|
|
||||||
|
**Шаги с неблокирующими дефекты** помечаются в отчете желтым цветом. По окончании теста отображается информация о всех подобных дефектах.
|
||||||
8
fragments/DynamicFragmentExample.feature
Normal file
8
fragments/DynamicFragmentExample.feature
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#language:ru
|
||||||
|
Функционал: динамический фрагмент
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
Сценарий: открытие страницы и ввод текста из параметра
|
||||||
|
* открыть url "https://www.google.ru/"
|
||||||
|
* инициализация страницы "Google"
|
||||||
|
* ввести в поле "поле поиска" значение "<текст_для_ввода>"
|
||||||
17
fragments/Example.feature
Normal file
17
fragments/Example.feature
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#language:ru
|
||||||
|
Функционал: фрагмент
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
Сценарий: открытие виджета
|
||||||
|
|
||||||
|
* на странице имеется элемент "кнопка поиска"
|
||||||
|
* кликнуть на элемент "кнопка поиска"
|
||||||
|
* переход на страницу "Google страница результатов"
|
||||||
|
* на странице имеется элемент "виджет погоды"
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
Сценарий: открытие страницы google
|
||||||
|
|
||||||
|
* открыть url "https://www.google.ru/"
|
||||||
|
* инициализация страницы "Google"
|
||||||
|
* ввести в поле "поле поиска" значение "Погода в Москве"
|
||||||
BIN
images/allure.png
Normal file
BIN
images/allure.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
images/allure_report.png
Normal file
BIN
images/allure_report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
images/allure_report_steps.png
Normal file
BIN
images/allure_report_steps.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
images/cucumber_compile.png
Normal file
BIN
images/cucumber_compile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
images/run-feature.png
Normal file
BIN
images/run-feature.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
217
pom.xml
Normal file
217
pom.xml
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>ru.lanit.at</groupId>
|
||||||
|
<artifactId>autotest-template-with-cucumber</artifactId>
|
||||||
|
<version>1.2-SNAPSHOT</version>
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<java.source>8</java.source>
|
||||||
|
<java.target>8</java.target>
|
||||||
|
<owner.version>1.0.12</owner.version>
|
||||||
|
<cucumber.version>7.2.3</cucumber.version>
|
||||||
|
<aspectj.version>1.9.6</aspectj.version>
|
||||||
|
<aspectj-maven-plugin.version>1.14.0</aspectj-maven-plugin.version>
|
||||||
|
<surefire.version>2.22.2</surefire.version>
|
||||||
|
<selenide.version>6.2.0</selenide.version>
|
||||||
|
<log4j.version>2.15.0</log4j.version>
|
||||||
|
<slf4j.version>1.7.30</slf4j.version>
|
||||||
|
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||||
|
<allure.version>2.17.2</allure.version>
|
||||||
|
<allure.maven.version>2.10.0</allure.maven.version>
|
||||||
|
<common-lang3.version>3.12.0</common-lang3.version>
|
||||||
|
<restassured.version>4.4.0</restassured.version>
|
||||||
|
<jackson.version>2.12.3</jackson.version>
|
||||||
|
<tags></tags>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven-compiler-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.source}</source>
|
||||||
|
<target>${java.target}</target>
|
||||||
|
<encoding>${project.build.sourceEncoding}</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${surefire.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<suiteXmlFiles>
|
||||||
|
<suiteXmlFile>src/test/resources/suite.xml</suiteXmlFile>
|
||||||
|
</suiteXmlFiles>
|
||||||
|
<testFailureIgnore>false</testFailureIgnore>
|
||||||
|
<skipTests>false</skipTests>
|
||||||
|
<argLine>
|
||||||
|
-Dcucumber.filter.tags="${tags}"
|
||||||
|
-Dfile.encoding=UTF-8
|
||||||
|
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
|
||||||
|
</argLine>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<base.dir>${project.basedir}/</base.dir>
|
||||||
|
<allure.results.directory>${project.build.directory}/allure-results</allure.results.directory>
|
||||||
|
<selenide.report.folder>${project.build.directory}/report</selenide.report.folder>
|
||||||
|
<selenide.download.folder>${project.build.directory}/download</selenide.download.folder>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjweaver</artifactId>
|
||||||
|
<version>${aspectj.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.qameta.allure</groupId>
|
||||||
|
<artifactId>allure-maven</artifactId>
|
||||||
|
<version>${allure.maven.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<reportDirectory>${project.build.directory}/allure-report</reportDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>aspectj-maven-plugin</artifactId>
|
||||||
|
<version>${aspectj-maven-plugin.version}</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjtools</artifactId>
|
||||||
|
<version>${aspectj.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.source}</source>
|
||||||
|
<target>${java.source}</target>
|
||||||
|
<complianceLevel>${java.source}</complianceLevel>
|
||||||
|
<encoding>${project.build.sourceEncoding}</encoding>
|
||||||
|
<weaveDependencies>
|
||||||
|
<weaveDependency>
|
||||||
|
<groupId>io.cucumber</groupId>
|
||||||
|
<artifactId>cucumber-testng</artifactId>
|
||||||
|
</weaveDependency>
|
||||||
|
<weaveDependency>
|
||||||
|
<groupId>io.cucumber</groupId>
|
||||||
|
<artifactId>cucumber-core</artifactId>
|
||||||
|
</weaveDependency>
|
||||||
|
</weaveDependencies>
|
||||||
|
<XnoInline>true</XnoInline>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!--cucumber-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cucumber</groupId>
|
||||||
|
<artifactId>cucumber-java</artifactId>
|
||||||
|
<version>${cucumber.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cucumber</groupId>
|
||||||
|
<artifactId>cucumber-testng</artifactId>
|
||||||
|
<version>${cucumber.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cucumber</groupId>
|
||||||
|
<artifactId>cucumber-picocontainer</artifactId>
|
||||||
|
<version>${cucumber.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--allure-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.qameta.allure</groupId>
|
||||||
|
<artifactId>allure-cucumber7-jvm</artifactId>
|
||||||
|
<version>2.17.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.qameta.allure</groupId>
|
||||||
|
<artifactId>allure-selenide</artifactId>
|
||||||
|
<version>${allure.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!--allure-->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aspectj</groupId>
|
||||||
|
<artifactId>aspectjrt</artifactId>
|
||||||
|
<version>${aspectj.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-core</artifactId>
|
||||||
|
<version>${log4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jayway.jsonpath</groupId>
|
||||||
|
<artifactId>json-path</artifactId>
|
||||||
|
<version>2.5.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>${common-lang3.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.codeborne</groupId>
|
||||||
|
<artifactId>selenide</artifactId>
|
||||||
|
<version>${selenide.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.reflections</groupId>
|
||||||
|
<artifactId>reflections</artifactId>
|
||||||
|
<version>0.9.10</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.aeonbits.owner</groupId>
|
||||||
|
<artifactId>owner-java8</artifactId>
|
||||||
|
<version>${owner.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--rest-assured-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.rest-assured</groupId>
|
||||||
|
<artifactId>rest-assured</artifactId>
|
||||||
|
<version>${restassured.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.googlecode.json-simple</groupId>
|
||||||
|
<artifactId>json-simple</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
84
src/main/java/ru/lanit/at/actions/WebActions.java
Normal file
84
src/main/java/ru/lanit/at/actions/WebActions.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package ru.lanit.at.actions;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Selenide;
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testng.Assert;
|
||||||
|
import ru.lanit.at.utils.ErrorMessage;
|
||||||
|
import ru.lanit.at.utils.Sleep;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class WebActions {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(WebActions.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Открывает ссылку по переданному url и переводит контекст драйвера на новое окно
|
||||||
|
*/
|
||||||
|
public static void openUrlOnNewTab(String url) {
|
||||||
|
String command = String.format("window.open('%s')", url);
|
||||||
|
Selenide.executeJavaScript(command);
|
||||||
|
List<String> handles = new ArrayList<>(WebDriverRunner.getWebDriver().getWindowHandles());
|
||||||
|
Selenide.switchTo().window(handles.get(handles.size() - 1));
|
||||||
|
LOGGER.info("Отрытие новой вкладки с урл {}", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Переключается на следующую вкладку или вкладку по порядковому номеру (1, 2, ...)
|
||||||
|
*/
|
||||||
|
public static void switchToNextTab(Integer tabNumber) {
|
||||||
|
List<String> handles = new ArrayList<>(WebDriverRunner.getWebDriver().getWindowHandles());
|
||||||
|
if (tabNumber != null) {
|
||||||
|
Assert.assertTrue(handles.size() >= tabNumber, String.format(ErrorMessage.BROWSER_TAB_NUMBER_MORE_THAN_TABS, tabNumber));
|
||||||
|
}
|
||||||
|
tabNumber = tabNumber == null ? handles.size() - 1 : tabNumber - 1;
|
||||||
|
Selenide.switchTo().window(handles.get(tabNumber));
|
||||||
|
LOGGER.info("Переключение на вкладку {}", handles.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Закрывает текущую вкладку и переключается на предыдущую
|
||||||
|
*/
|
||||||
|
public static void closeCurrentTabAndSwitchToPrevious() {
|
||||||
|
Selenide.closeWindow();
|
||||||
|
List<String> handles = new ArrayList<>(WebDriverRunner.getWebDriver().getWindowHandles());
|
||||||
|
Selenide.switchTo().window(handles.get(handles.size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Посимвольное заполнение поля
|
||||||
|
*
|
||||||
|
* @param element - элемент
|
||||||
|
* @param text - значение
|
||||||
|
*/
|
||||||
|
public static void fillInputByCharacter(SelenideElement element, String text) {
|
||||||
|
for (char character : text.toCharArray()) {
|
||||||
|
element.sendKeys(String.valueOf(character));
|
||||||
|
Sleep.pauseSec(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установка чекбокса на элементе
|
||||||
|
*
|
||||||
|
* @param element - элемент
|
||||||
|
* @param flag - значение
|
||||||
|
*/
|
||||||
|
public static void setCheckBox(SelenideElement element, boolean flag) {
|
||||||
|
element.click();
|
||||||
|
if (flag) {
|
||||||
|
if (!element.isSelected()) {
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (element.isSelected()) {
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
216
src/main/java/ru/lanit/at/actions/WebChecks.java
Normal file
216
src/main/java/ru/lanit/at/actions/WebChecks.java
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
package ru.lanit.at.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Condition;
|
||||||
|
import com.codeborne.selenide.Selectors;
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
import com.codeborne.selenide.ex.ElementNotFound;
|
||||||
|
import com.codeborne.selenide.ex.ElementShould;
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testng.Assert;
|
||||||
|
import ru.lanit.at.utils.ErrorMessage;
|
||||||
|
import ru.lanit.at.utils.selenide.command.Commands;
|
||||||
|
import ru.lanit.at.utils.web.properties.WebConfigurations;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$;
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
public class WebChecks {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(WebChecks.class);
|
||||||
|
|
||||||
|
|
||||||
|
private static Integer getTimeoutSeconds(Integer timeout) {
|
||||||
|
WebConfigurations cfg = ConfigFactory.create(WebConfigurations.class);
|
||||||
|
return timeout == null ? cfg.webDriverTimeoutSeconds() : timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет что текущий url равен переданному
|
||||||
|
*
|
||||||
|
* @param url ожидаемый url
|
||||||
|
*/
|
||||||
|
public static void urlEquals(String url) {
|
||||||
|
Assert.assertEquals(WebDriverRunner.getWebDriver().getCurrentUrl(), url,
|
||||||
|
format(ErrorMessage.URL_NOT_EQUAL_ACTUAL, url, WebDriverRunner.getWebDriver().getCurrentUrl()));
|
||||||
|
LOGGER.info("url '{}' равен текущему '{}'", url, WebDriverRunner.getWebDriver().getCurrentUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет что текущий url содержит текст
|
||||||
|
*
|
||||||
|
* @param text ожидаемый текст
|
||||||
|
*/
|
||||||
|
public static void urlContains(String text) {
|
||||||
|
Assert.assertTrue(WebDriverRunner.getWebDriver().getCurrentUrl().contains(text),
|
||||||
|
format(ErrorMessage.URL_NOT_CONTAINS_TEXT, WebDriverRunner.getWebDriver().getCurrentUrl(), text));
|
||||||
|
LOGGER.info("Текущий url '{}' содержит текст '{}'", WebDriverRunner.getWebDriver().getCurrentUrl(), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что атрибут элемента равен ожидаемому
|
||||||
|
*
|
||||||
|
* @param element Элемент
|
||||||
|
* @param attrName Атрибут
|
||||||
|
* @param expectValue Ожидаемое значение атрибута
|
||||||
|
*/
|
||||||
|
public static void checkAttribute(SelenideElement element, String attrName, String expectValue, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
element
|
||||||
|
.shouldBe(Condition.exist, Duration.ofSeconds(timeout))
|
||||||
|
.shouldBe(Condition.attribute(attrName, expectValue), Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что на странице доступен элемент
|
||||||
|
*/
|
||||||
|
public static void elementEnablesOnPage(SelenideElement element, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
element.shouldBe(Condition.enabled, Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что на странице имеется элемент
|
||||||
|
*/
|
||||||
|
public static void elementVisibleOnPage(SelenideElement element, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
element.shouldBe(Condition.visible, Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что элемент имеет текст
|
||||||
|
*/
|
||||||
|
public static void elementContainsText(SelenideElement element, String text) {
|
||||||
|
element.execute(Commands.checkSoft(Condition.text(text), null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что элемент содержит текст
|
||||||
|
*/
|
||||||
|
public static void elementContainsText(SelenideElement element, List<String> texts) {
|
||||||
|
for (String s : texts) {
|
||||||
|
element.execute(Commands.checkSoft(Condition.text(s), Duration.ofSeconds(10)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что на странице имеется текст
|
||||||
|
*/
|
||||||
|
public static void textVisibleOnPage(String text, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
$(Selectors.byText(text))
|
||||||
|
.shouldBe(Condition.visible, Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что на странице отсутствует текст
|
||||||
|
*/
|
||||||
|
public static void textAbsentOnPage(String text, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
$(Selectors.byText(text))
|
||||||
|
.shouldBe(Condition.not(Condition.visible), Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что на странице отсутствует элемент
|
||||||
|
*/
|
||||||
|
public static void elementAbsentOnPage(SelenideElement element, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
element
|
||||||
|
.shouldBe(Condition.not(Condition.visible), Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что текст элемента соответствует ожидаемому тексту
|
||||||
|
*
|
||||||
|
* @param element элемент
|
||||||
|
* @param expectedText ожидаемый текст
|
||||||
|
* @param timeoutSeconds количество секунд, в течении этого времени ожидается текст
|
||||||
|
*/
|
||||||
|
public static void elementTextEqualsExpectedText(SelenideElement element, String expectedText, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
element.shouldBe(Condition.exactTextCaseSensitive(expectedText), Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* проверяет что текст элемента не соответствует ожидаемому тексту
|
||||||
|
*
|
||||||
|
* @param element элемент
|
||||||
|
* @param expectedText текст
|
||||||
|
* @param timeoutSeconds количество секунд
|
||||||
|
*/
|
||||||
|
public static void elementTextNotEqualsExpectedText(SelenideElement element, String expectedText, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
element.shouldNotBe(Condition.exactTextCaseSensitive(expectedText), Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, что текст элемента содержит ожидаемый текст
|
||||||
|
*
|
||||||
|
* @param element элемент
|
||||||
|
* @param expectedText ожидаемый текст или регулярное выражение
|
||||||
|
* @param timeoutSeconds количество секунд, в течении этого времени ожидается текст
|
||||||
|
*/
|
||||||
|
public static void elementTextContainsExpectedText(SelenideElement element, String expectedText, Integer timeoutSeconds) {
|
||||||
|
int timeout = getTimeoutSeconds(timeoutSeconds);
|
||||||
|
element.shouldBe(Condition.matchText(expectedText), Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, появился ли элемент за переданное время
|
||||||
|
*
|
||||||
|
* @param element - веб-элемент
|
||||||
|
* @param timeoutSeconds - таймаут ожидания (может быть null, в таком случае будет использоваться параметр из конфига webdriver.timeoutSeconds )
|
||||||
|
* @return - появился ли элемент
|
||||||
|
*/
|
||||||
|
public static boolean isElementWillAppear(SelenideElement element, Integer timeoutSeconds) {
|
||||||
|
try {
|
||||||
|
elementVisibleOnPage(element, timeoutSeconds);
|
||||||
|
return true;
|
||||||
|
} catch (ElementNotFound elementNotFound) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, исчез ли элемент за переданное время
|
||||||
|
*
|
||||||
|
* @param element веб-элемент
|
||||||
|
* @param timeoutSeconds - таймаут ожидания (может быть null, в таком случае будет использоваться параметр из конфига webdriver.timeoutSeconds )
|
||||||
|
* @return - исчез ли элемент
|
||||||
|
*/
|
||||||
|
public static boolean isElementWillDisappear(SelenideElement element, Integer timeoutSeconds) {
|
||||||
|
try {
|
||||||
|
elementAbsentOnPage(element, timeoutSeconds);
|
||||||
|
return true;
|
||||||
|
} catch (ElementShould elementShould) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверяет, исчез ли элемент за переданное время
|
||||||
|
*
|
||||||
|
* @param xpath - xpath веб-элемента
|
||||||
|
* @param timeoutSeconds - таймаут ожидания (может быть null, в таком случае будет использоваться параметр из конфига webdriver.timeoutSeconds )
|
||||||
|
* @return - исчез ли элемент
|
||||||
|
*/
|
||||||
|
public static boolean isElementWillDisappear(String xpath, Integer timeoutSeconds) {
|
||||||
|
try {
|
||||||
|
SelenideElement element = $(Selectors.byXpath(xpath));
|
||||||
|
elementAbsentOnPage(element, timeoutSeconds);
|
||||||
|
return true;
|
||||||
|
} catch (ElementShould elementShould) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
131
src/main/java/ru/lanit/at/api/ApiRequest.java
Normal file
131
src/main/java/ru/lanit/at/api/ApiRequest.java
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package ru.lanit.at.api;
|
||||||
|
|
||||||
|
import io.qameta.allure.Allure;
|
||||||
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
|
import io.restassured.http.Method;
|
||||||
|
import io.restassured.response.Response;
|
||||||
|
import io.restassured.specification.RequestSpecification;
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import ru.lanit.at.api.listeners.RestAssuredCustomLogger;
|
||||||
|
import ru.lanit.at.api.models.RequestModel;
|
||||||
|
import ru.lanit.at.api.properties.RestConfigurations;
|
||||||
|
import ru.lanit.at.utils.FileUtil;
|
||||||
|
import ru.lanit.at.utils.JsonUtil;
|
||||||
|
import ru.lanit.at.utils.RegexUtil;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static io.restassured.RestAssured.given;
|
||||||
|
import static ru.lanit.at.utils.ContextHolder.replaceVarsIfPresent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Sorokin Boris on 21.01.2022.
|
||||||
|
*/
|
||||||
|
public class ApiRequest {
|
||||||
|
private final static RestConfigurations CONFIGURATIONS = ConfigFactory.create(RestConfigurations.class,
|
||||||
|
System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
|
||||||
|
private String baseUrl;
|
||||||
|
private String path;
|
||||||
|
private Method method;
|
||||||
|
private String body;
|
||||||
|
private String fullUrl;
|
||||||
|
private Response response;
|
||||||
|
|
||||||
|
private RequestSpecBuilder builder;
|
||||||
|
|
||||||
|
public ApiRequest(RequestModel requestModel) {
|
||||||
|
this.builder = new RequestSpecBuilder();
|
||||||
|
|
||||||
|
this.baseUrl = CONFIGURATIONS.getBaseUrl();
|
||||||
|
this.path = replaceVarsIfPresent(requestModel.getPath());
|
||||||
|
this.method = Method.valueOf(requestModel.getMethod());
|
||||||
|
this.body = requestModel.getBody();
|
||||||
|
this.fullUrl = replaceVarsIfPresent(requestModel.getUrl());
|
||||||
|
|
||||||
|
URI uri;
|
||||||
|
|
||||||
|
if (!fullUrl.isEmpty()) {
|
||||||
|
uri = URI.create(fullUrl.replace(" ", "+"));
|
||||||
|
} else {
|
||||||
|
uri = URI.create(baseUrl);
|
||||||
|
builder.setBasePath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.builder.setBaseUri(uri);
|
||||||
|
setBodyFromFile();
|
||||||
|
addLoggingListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сеттит заголовки
|
||||||
|
*/
|
||||||
|
public void setHeaders(Map<String, String> headers) {
|
||||||
|
headers.forEach((k, v) -> {
|
||||||
|
builder.addHeader(k, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сеттит query-параметры
|
||||||
|
*/
|
||||||
|
public void setQuery(Map<String, String> query) {
|
||||||
|
query.forEach((k, v) -> {
|
||||||
|
builder.addQueryParam(k, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправляет сформированный запрос
|
||||||
|
*/
|
||||||
|
public void sendRequest() {
|
||||||
|
RequestSpecification requestSpecification = builder.build();
|
||||||
|
|
||||||
|
Response response = given()
|
||||||
|
.spec(requestSpecification)
|
||||||
|
.request(method);
|
||||||
|
|
||||||
|
attachRequestResponseToAllure(response, body);
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сессит тело запроса из файла
|
||||||
|
*/
|
||||||
|
private void setBodyFromFile() {
|
||||||
|
if (body != null && RegexUtil.getMatch(body, ".*\\.json")) {
|
||||||
|
body = replaceVarsIfPresent(FileUtil.readBodyFromJsonDir(body));
|
||||||
|
builder.setBody(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Аттачит тело запроса и тело ответа в шаг отправки запроса
|
||||||
|
*/
|
||||||
|
private void attachRequestResponseToAllure(Response response, String requestBody) {
|
||||||
|
if (requestBody != null) {
|
||||||
|
Allure.addAttachment(
|
||||||
|
"Request",
|
||||||
|
"application/json",
|
||||||
|
new ByteArrayInputStream(requestBody.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
".txt");
|
||||||
|
}
|
||||||
|
String responseBody = JsonUtil.jsonToUtf(response.body().asPrettyString());
|
||||||
|
Allure.addAttachment("Response", "application/json", responseBody, ".txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Добавляет логгер, печатающий в консоль данные запросов и ответов
|
||||||
|
*/
|
||||||
|
private void addLoggingListener() {
|
||||||
|
builder.addFilter(new RestAssuredCustomLogger());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package ru.lanit.at.api.listeners;
|
||||||
|
|
||||||
|
import io.restassured.filter.Filter;
|
||||||
|
import io.restassured.filter.FilterContext;
|
||||||
|
import io.restassured.filter.log.UrlDecoder;
|
||||||
|
import io.restassured.response.Response;
|
||||||
|
import io.restassured.specification.FilterableRequestSpecification;
|
||||||
|
import io.restassured.specification.FilterableResponseSpecification;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import static ru.lanit.at.utils.ContextHolder.replaceVarsIfPresent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Sorokin Boris on 21.01.2022.
|
||||||
|
*/
|
||||||
|
public class RestAssuredCustomLogger implements Filter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response filter(FilterableRequestSpecification requestSpec
|
||||||
|
, FilterableResponseSpecification responseSpec
|
||||||
|
, FilterContext context) {
|
||||||
|
Response response = context.next(requestSpec, responseSpec);
|
||||||
|
|
||||||
|
String uri = UrlDecoder.urlDecode(requestSpec.getURI(),
|
||||||
|
Charset.forName(requestSpec
|
||||||
|
.getConfig()
|
||||||
|
.getEncoderConfig()
|
||||||
|
.defaultQueryParameterCharset()),
|
||||||
|
true);
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
stringBuilder.append("------------- REQUEST -------------\n");
|
||||||
|
stringBuilder.append("URL: ")
|
||||||
|
.append(uri).append("\n")
|
||||||
|
.append("Method: ").append(requestSpec.getMethod()).append("\n");
|
||||||
|
requestSpec.getHeaders().asList().forEach(header -> stringBuilder
|
||||||
|
.append("Header: ")
|
||||||
|
.append(replaceVarsIfPresent(header.getName()))
|
||||||
|
.append("=")
|
||||||
|
.append(replaceVarsIfPresent(header.getValue()))
|
||||||
|
.append("\n"));
|
||||||
|
requestSpec.getQueryParams().forEach((k, v) -> stringBuilder
|
||||||
|
.append("Query: ")
|
||||||
|
.append(replaceVarsIfPresent(k))
|
||||||
|
.append("=")
|
||||||
|
.append(replaceVarsIfPresent(v))
|
||||||
|
.append("\n"));
|
||||||
|
stringBuilder.append("Request Body: \n")
|
||||||
|
.append(replaceVarsIfPresent(requestSpec.getBody()))
|
||||||
|
.append("\n");
|
||||||
|
stringBuilder.append("------------- RESPONSE -------------\n");
|
||||||
|
stringBuilder.append("Status code: ")
|
||||||
|
.append(response.statusCode())
|
||||||
|
.append("\n");
|
||||||
|
stringBuilder.append("Response Body: \n")
|
||||||
|
.append(response.getBody().asPrettyString());
|
||||||
|
System.out.println(stringBuilder);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/main/java/ru/lanit/at/api/models/RequestModel.java
Normal file
41
src/main/java/ru/lanit/at/api/models/RequestModel.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package ru.lanit.at.api.models;
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
public class RequestModel {
|
||||||
|
private String method;
|
||||||
|
private String body;
|
||||||
|
private String path;
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
public RequestModel(String method, String body, String path, String url) {
|
||||||
|
this.method = method;
|
||||||
|
this.body = body;
|
||||||
|
this.path = path;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new GsonBuilder()
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.create()
|
||||||
|
.toJson(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package ru.lanit.at.api.properties;
|
||||||
|
|
||||||
|
import org.aeonbits.owner.Config;
|
||||||
|
|
||||||
|
@Config.LoadPolicy(Config.LoadType.MERGE)
|
||||||
|
@Config.Sources({
|
||||||
|
"classpath:config/configuration.properties",
|
||||||
|
"system:properties",
|
||||||
|
"system:env"
|
||||||
|
})
|
||||||
|
public interface RestConfigurations extends Config {
|
||||||
|
|
||||||
|
@Key("baseUrl")
|
||||||
|
@DefaultValue("")
|
||||||
|
String getBaseUrl();
|
||||||
|
|
||||||
|
}
|
||||||
54
src/main/java/ru/lanit/at/aspects/FragmentsAspect.java
Normal file
54
src/main/java/ru/lanit/at/aspects/FragmentsAspect.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package ru.lanit.at.aspects;
|
||||||
|
|
||||||
|
import io.cucumber.core.gherkin.Feature;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
|
import org.testng.asserts.Assertion;
|
||||||
|
import ru.lanit.at.corecommonstep.fragment.FragmentReplacer;
|
||||||
|
import ru.lanit.at.corecommonstep.fragment.GherkinSerializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Aspect
|
||||||
|
public class FragmentsAspect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* замена фрагментов и данных в фиче
|
||||||
|
*
|
||||||
|
* @param features список фич
|
||||||
|
* @param assertion assert
|
||||||
|
* @return список фич
|
||||||
|
* @throws IOException
|
||||||
|
* @throws IllegalAccessException
|
||||||
|
*/
|
||||||
|
public static List<Feature> replaceSteps(List<Feature> features, Assertion assertion) throws IOException, IllegalAccessException {
|
||||||
|
features = features.stream()
|
||||||
|
.filter(cucumberFeature -> cucumberFeature.getSource() != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
FragmentReplacer fragmentReplacer = new FragmentReplacer(features, assertion);
|
||||||
|
fragmentReplacer.replace();
|
||||||
|
features = new GherkinSerializer().reserializeFeatures(features, assertion);
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pointcut("execution(* io.cucumber.core.runtime.FeaturePathFeatureSupplier.get(..))")
|
||||||
|
public void cucumberFeatures() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* тут используется строгий assert
|
||||||
|
*
|
||||||
|
* @param joinPoint
|
||||||
|
* @return
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
@Around("cucumberFeatures()")
|
||||||
|
public Object replaceSteps(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
|
return replaceSteps((List<Feature>) joinPoint.proceed(), new Assertion());
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/ru/lanit/at/assertion/AssertErrorType.java
Normal file
17
src/main/java/ru/lanit/at/assertion/AssertErrorType.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package ru.lanit.at.assertion;
|
||||||
|
|
||||||
|
public enum AssertErrorType {
|
||||||
|
|
||||||
|
SOFT_ASSERT("SoftAssert"),
|
||||||
|
CRITICAL_ASSERT("CriticalAssert");
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
AssertErrorType(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/main/java/ru/lanit/at/assertion/AssertsManager.java
Normal file
62
src/main/java/ru/lanit/at/assertion/AssertsManager.java
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package ru.lanit.at.assertion;
|
||||||
|
|
||||||
|
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import ru.lanit.at.utils.web.properties.Configurations;
|
||||||
|
|
||||||
|
public class AssertsManager {
|
||||||
|
private static final ThreadLocal<AssertsManager> assertsManagerThreadLocal = new ThreadLocal<>();
|
||||||
|
private static final Configurations conf = ConfigFactory.create(Configurations.class, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
private ExtendedAssert asserts;
|
||||||
|
|
||||||
|
public synchronized static AssertsManager getAssertsManager() {
|
||||||
|
AssertsManager localInstance = assertsManagerThreadLocal.get();
|
||||||
|
if (localInstance == null) {
|
||||||
|
synchronized (AssertsManager.class) {
|
||||||
|
localInstance = assertsManagerThreadLocal.get();
|
||||||
|
if (localInstance == null) {
|
||||||
|
AssertsManager assertsManager = new AssertsManager();
|
||||||
|
assertsManager.setAsserts(new ExtendedAssert());
|
||||||
|
assertsManagerThreadLocal.set(assertsManager);
|
||||||
|
localInstance = assertsManagerThreadLocal.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localInstance;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get {@link ExtendedAssert} previously marked as critical. So such assert will fail all test.
|
||||||
|
*
|
||||||
|
* @return Critical {@link ExtendedAssert}.
|
||||||
|
*/
|
||||||
|
public ExtendedAssert criticalAssert() {
|
||||||
|
asserts.setCritical();
|
||||||
|
return asserts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to get soft {@link ExtendedAssert} that collects assertions.
|
||||||
|
*
|
||||||
|
* @return Soft {@link ExtendedAssert}.
|
||||||
|
*/
|
||||||
|
public ExtendedAssert softAssert() {
|
||||||
|
return asserts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreates instance of {@link ExtendedAssert}.
|
||||||
|
*/
|
||||||
|
public void flushAsserts() {
|
||||||
|
asserts.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAsserts(ExtendedAssert asserts) {
|
||||||
|
this.asserts = asserts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
69
src/main/java/ru/lanit/at/assertion/ExtendedAssert.java
Normal file
69
src/main/java/ru/lanit/at/assertion/ExtendedAssert.java
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package ru.lanit.at.assertion;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testng.asserts.IAssert;
|
||||||
|
import org.testng.asserts.SoftAssert;
|
||||||
|
import org.testng.collections.Maps;
|
||||||
|
import ru.lanit.at.utils.allure.AllureHelper;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
public class ExtendedAssert extends SoftAssert {
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(ExtendedAssert.class);
|
||||||
|
private final Map<AssertionError, IAssert<?>> m_errors = Maps.newLinkedHashMap();
|
||||||
|
private Boolean isCritical = false;
|
||||||
|
|
||||||
|
|
||||||
|
public void setCritical() {
|
||||||
|
isCritical = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doAssert(IAssert<?> a) {
|
||||||
|
onBeforeAssert(a);
|
||||||
|
try {
|
||||||
|
a.doAssert();
|
||||||
|
onAssertSuccess(a);
|
||||||
|
LOGGER.debug("Успешно проверено: [{}]", a.getActual());
|
||||||
|
} catch (AssertionError ex) {
|
||||||
|
LOGGER.error(ex.getMessage());
|
||||||
|
onAssertFailure(a, ex);
|
||||||
|
m_errors.put(ex, a);
|
||||||
|
AllureHelper.setStepStatusBroken("SoftAssert:" + ex.getMessage());
|
||||||
|
if (isCritical) {
|
||||||
|
this.assertAll();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.isCritical = false;
|
||||||
|
onAfterAssert(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assertAll() {
|
||||||
|
if (!m_errors.isEmpty()) {
|
||||||
|
StringBuilder sb = new StringBuilder("The following asserts failed:");
|
||||||
|
boolean first = true;
|
||||||
|
for (Map.Entry<AssertionError, IAssert<?>> ae : m_errors.entrySet()) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append("\n\t");
|
||||||
|
sb.append(ae.getKey().getMessage());
|
||||||
|
ae.getKey().printStackTrace();
|
||||||
|
}
|
||||||
|
if (isCritical) sb.append(" [BLOCKER]");
|
||||||
|
m_errors.clear();
|
||||||
|
throw new AssertionError(sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush() {
|
||||||
|
m_errors.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/java/ru/lanit/at/corecommonstep/CommonSteps.java
Normal file
23
src/main/java/ru/lanit/at/corecommonstep/CommonSteps.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package ru.lanit.at.corecommonstep;
|
||||||
|
|
||||||
|
import io.cucumber.java.ru.И;
|
||||||
|
import ru.lanit.at.corecommonstep.fragment.FragmentReplacer;
|
||||||
|
|
||||||
|
|
||||||
|
public class CommonSteps {
|
||||||
|
|
||||||
|
@И(FragmentReplacer.REGEX_FRAGMENT)
|
||||||
|
public void userInsertsFragment(String fragmentName) {
|
||||||
|
throw new IllegalStateException("фрагмент не подставился");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Шаг-заглушка для отчета аллюра и группировки шагов фрагмента под спойлер
|
||||||
|
* Если будет использоваться в тесте - будет assert "надо использовать другой шаг"
|
||||||
|
* @param fragmentName - название фрагмента (сценария)
|
||||||
|
*/
|
||||||
|
@И(FragmentReplacer.REGEX_FRAGMENT_SPOILER)
|
||||||
|
public void fragment(String fragmentName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package ru.lanit.at.corecommonstep.fragment;
|
||||||
|
|
||||||
|
import io.cucumber.core.gherkin.DataTableArgument;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/* пришлось написать свою реализацию класса io.cucumber.core.gherkin.messages.GherkinMessagesDataTableArgument */
|
||||||
|
public class CustomGherkinMessagesDataTableArgument implements DataTableArgument {
|
||||||
|
|
||||||
|
private List<List<String>> cells;
|
||||||
|
private int line;
|
||||||
|
|
||||||
|
public CustomGherkinMessagesDataTableArgument(List<List<String>> cells, int line) {
|
||||||
|
this.cells = cells;
|
||||||
|
this.line = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<List<String>> cells() {
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLine() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package ru.lanit.at.corecommonstep.fragment;
|
||||||
|
|
||||||
|
import io.cucumber.core.gherkin.DocStringArgument;
|
||||||
|
|
||||||
|
public class CustomGherkinMessagesDocStringArgument implements DocStringArgument {
|
||||||
|
|
||||||
|
private String content;
|
||||||
|
private String contentType;
|
||||||
|
private String mediaType;
|
||||||
|
private int line;
|
||||||
|
|
||||||
|
public CustomGherkinMessagesDocStringArgument(String content, String contentType, String mediaType, int line) {
|
||||||
|
this.content = content;
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.mediaType = mediaType;
|
||||||
|
this.line = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMediaType() {
|
||||||
|
return mediaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLine() {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
package ru.lanit.at.corecommonstep.fragment;
|
||||||
|
|
||||||
|
import io.cucumber.core.gherkin.Argument;
|
||||||
|
import io.cucumber.core.gherkin.Step;
|
||||||
|
import io.cucumber.core.gherkin.StepType;
|
||||||
|
import io.cucumber.gherkin.GherkinDialect;
|
||||||
|
import io.cucumber.plugin.event.DataTableArgument;
|
||||||
|
import io.cucumber.plugin.event.DocStringArgument;
|
||||||
|
import io.cucumber.plugin.event.Location;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class CustomStep implements Step {
|
||||||
|
|
||||||
|
private final Argument argument;
|
||||||
|
private final String keyWord;
|
||||||
|
private final StepType stepType;
|
||||||
|
private final String previousGwtKeyWord;
|
||||||
|
private final Location location;
|
||||||
|
private final String id;
|
||||||
|
private final String text;
|
||||||
|
|
||||||
|
public CustomStep(
|
||||||
|
String id,
|
||||||
|
String text,
|
||||||
|
List<List<String>> argument,
|
||||||
|
Class argumentType,
|
||||||
|
GherkinDialect dialect,
|
||||||
|
String previousGwtKeyWord,
|
||||||
|
Location location,
|
||||||
|
String keyword
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.text = text;
|
||||||
|
this.argument = extractArgument(argument, argumentType, location);
|
||||||
|
this.keyWord = keyword;
|
||||||
|
this.stepType = extractKeyWordType(keyWord, dialect);
|
||||||
|
this.previousGwtKeyWord = previousGwtKeyWord;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Argument extractArgument(List<List<String>> argument, Class argumentType, Location location) {
|
||||||
|
if (!argument.isEmpty()) {
|
||||||
|
if (DataTableArgument.class.equals(argumentType)) {
|
||||||
|
return new CustomGherkinMessagesDataTableArgument(argument, location.getLine() + 1);
|
||||||
|
} else if (DocStringArgument.class.equals(argumentType)) {
|
||||||
|
return new CustomGherkinMessagesDocStringArgument(argument.get(0).get(0), "", "", location.getLine() + 1);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException(String.format("Неожиданный тип значения: %s.\n", argumentType.getName()));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StepType extractKeyWordType(String keyWord, GherkinDialect dialect) {
|
||||||
|
if (StepType.isAstrix(keyWord)) {
|
||||||
|
return StepType.OTHER;
|
||||||
|
}
|
||||||
|
if (dialect.getGivenKeywords().contains(keyWord)) {
|
||||||
|
return StepType.GIVEN;
|
||||||
|
}
|
||||||
|
if (dialect.getWhenKeywords().contains(keyWord)) {
|
||||||
|
return StepType.WHEN;
|
||||||
|
}
|
||||||
|
if (dialect.getThenKeywords().contains(keyWord)) {
|
||||||
|
return StepType.THEN;
|
||||||
|
}
|
||||||
|
if (dialect.getAndKeywords().contains(keyWord)) {
|
||||||
|
return StepType.AND;
|
||||||
|
}
|
||||||
|
if (dialect.getButKeywords().contains(keyWord)) {
|
||||||
|
return StepType.BUT;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Keyword " + keyWord + " was neither given, when, then, and, but nor *");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StepType getType() {
|
||||||
|
return stepType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPreviousGivenWhenThenKeyword() {
|
||||||
|
return previousGwtKeyWord;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Argument getArgument() {
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKeyword() {
|
||||||
|
return keyWord;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLine() {
|
||||||
|
return location.getLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Location getLocation() {
|
||||||
|
return new Location(location.getLine(), location.getColumn());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,425 @@
|
|||||||
|
package ru.lanit.at.corecommonstep.fragment;
|
||||||
|
|
||||||
|
import com.google.common.graph.EndpointPair;
|
||||||
|
import com.google.common.graph.MutableValueGraph;
|
||||||
|
import com.google.common.graph.ValueGraphBuilder;
|
||||||
|
import io.cucumber.core.feature.FeatureParser;
|
||||||
|
import io.cucumber.core.feature.FeatureWithLines;
|
||||||
|
import io.cucumber.core.gherkin.Feature;
|
||||||
|
import io.cucumber.core.gherkin.Pickle;
|
||||||
|
import io.cucumber.core.gherkin.Step;
|
||||||
|
import io.cucumber.gherkin.GherkinDialect;
|
||||||
|
import io.cucumber.gherkin.GherkinDialectProvider;
|
||||||
|
import io.cucumber.gherkin.IGherkinDialectProvider;
|
||||||
|
import io.cucumber.plugin.event.DataTableArgument;
|
||||||
|
import io.cucumber.plugin.event.DocStringArgument;
|
||||||
|
import io.cucumber.plugin.event.Location;
|
||||||
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||||
|
import org.testng.asserts.Assertion;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
||||||
|
public class FragmentReplacer {
|
||||||
|
|
||||||
|
public static final String NOT_FOUND_VARS_FOR_DYNAMIC_FRAGMENTS = "Следующие переменные не были переданы в динамический фрагмент из сценария '%s' для шага '%s': %s\n";
|
||||||
|
public static final String FOUND_MORE_ONE_FRAGMENT = "Найдено более одного фрагмента с наименованием и путем:\n%s";
|
||||||
|
public static final String FRAGMENT_NOT_EXIST = "Нет такого фрагмента с названием - '%s'.";
|
||||||
|
public static final String LANGUAGE_IS_NOT_SAME = "Язык фрагмента не совпадает с языком сценария! Сценарий: '%s', Фрагмент: '%s'";
|
||||||
|
|
||||||
|
public static final String REGEX_FRAGMENT = "^вызвать фрагмент \"(.+)\"$";
|
||||||
|
public static final String REGEX_FRAGMENT_SPOILER = "^ФРАГМЕНТ \"(.+)\"$";
|
||||||
|
public static final String FEATURE_SUFFIX = ".feature";
|
||||||
|
public static final String FRAGMENT_TAG = "@fragment";
|
||||||
|
private static final String REGEX_VALUE = "<%s>";
|
||||||
|
private static final String REGEX_EXAMPLE = "<(.*)>";
|
||||||
|
|
||||||
|
private List<Feature> features;
|
||||||
|
private Map<Pickle, String> scenarioLanguageMap;
|
||||||
|
private MutableValueGraph<Object, String> fragmentsGraph;
|
||||||
|
private Assertion assertion;
|
||||||
|
|
||||||
|
public FragmentReplacer(List<Feature> features, Assertion assertion) throws IOException {
|
||||||
|
this.assertion = assertion;
|
||||||
|
this.features = cacheFragmentsToFeatures(features);
|
||||||
|
this.scenarioLanguageMap = cacheScenarioLanguage(this.features);
|
||||||
|
Map<String, Pickle> fragmentsMap = cacheFragmentsAsMap(this.features);
|
||||||
|
this.fragmentsGraph = cacheFragmentsAsGraph(this.features, fragmentsMap, this.scenarioLanguageMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean getMatch(String sentence, String regex) {
|
||||||
|
if (sentence != null && regex != null) {
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(sentence);
|
||||||
|
return matcher.find();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMatchValueByGroupNumber(String sentence, String regex, int groupNumber) {
|
||||||
|
if (sentence != null && regex != null) {
|
||||||
|
sentence = sentence.trim();
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(sentence);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(groupNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replace() throws IllegalAccessException {
|
||||||
|
while (!fragmentsGraph.edges().isEmpty()) {
|
||||||
|
int fragmentsGraphSize = fragmentsGraph.edges().size();
|
||||||
|
|
||||||
|
for (EndpointPair edge : new ArrayList<>(fragmentsGraph.edges())) {
|
||||||
|
Pickle fragment = (Pickle) edge.nodeV();
|
||||||
|
Pickle scenario = (Pickle) edge.nodeU();
|
||||||
|
|
||||||
|
if (isTerminal(fragment)) {
|
||||||
|
replaceFragmentInScenario(scenario, fragment);
|
||||||
|
fragmentsGraph.removeEdge(scenario, fragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fragmentsGraphSize == fragmentsGraph.edges().size()) {
|
||||||
|
throw new AssertionError("Fragments replacing is no longer performed, it will lead to an infinite loop. Interrupting...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* загрузка фича файлов из папки с фрагментами
|
||||||
|
*
|
||||||
|
* @return лист Feature файлов
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private List<Feature> cacheFragmentsToFeatures(List<Feature> features) throws IOException {
|
||||||
|
List<FeatureWithLines> featurePaths = new ArrayList();
|
||||||
|
StringBuilder pathBuilder = getParentPath("fragments");
|
||||||
|
|
||||||
|
List<Path> collect = Files.walk(Paths.get(pathBuilder.toString()))
|
||||||
|
.filter(p -> p.toAbsolutePath().toString().endsWith(FEATURE_SUFFIX))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
collect.forEach(p -> {
|
||||||
|
featurePaths.add(FeatureWithLines.create(p.toUri(), Collections.emptyList()));
|
||||||
|
});
|
||||||
|
|
||||||
|
List<URI> uriList = featurePaths.stream().map(p -> p.uri()).collect(Collectors.toList());
|
||||||
|
List<Feature> loadedFeaturesList = new ArrayList<>();
|
||||||
|
FeatureParser featureParser = new FeatureParser(() -> UUID.randomUUID());
|
||||||
|
|
||||||
|
Iterator<URI> iterator = uriList.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
URI uri = iterator.next();
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
Files.readAllLines(Paths.get(uri)).forEach(p -> stringBuilder.append(p).append("\n"));
|
||||||
|
GherkinResource gherkinResource = new GherkinResource(stringBuilder.toString(), uri);
|
||||||
|
Optional<Feature> feature = featureParser.parseResource(gherkinResource);
|
||||||
|
feature.ifPresent(loadedFeaturesList::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stream.concat(features.stream(), loadedFeaturesList.stream()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Pickle, String> cacheScenarioLanguage(List<Feature> features) {
|
||||||
|
Map<Pickle, String> scenarioLanguageMap = new HashMap<>();
|
||||||
|
for (Feature feature : features) {
|
||||||
|
List<Pickle> pickles = feature.getPickles();
|
||||||
|
for (Pickle pickle : pickles) {
|
||||||
|
scenarioLanguageMap.put(pickle, pickle.getLanguage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scenarioLanguageMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Pickle> cacheFragmentsAsMap(List<Feature> features) {
|
||||||
|
Map<String, Pickle> fragments = new HashMap<>();
|
||||||
|
|
||||||
|
List<Pickle> collect = features.stream().map(Feature::getPickles).flatMap(List::stream).collect(Collectors.toList());
|
||||||
|
|
||||||
|
for (Pickle pickle : collect) {
|
||||||
|
if (isFragmentTagContains(pickle.getTags())) {
|
||||||
|
List<Pickle> pickles = collect.stream()
|
||||||
|
.filter(p -> isFragmentTagContains(p.getTags()))
|
||||||
|
.filter(p -> p.getName().equalsIgnoreCase(pickle.getName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (pickles.size() > 1) {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
pickles.forEach(p -> stringBuilder.append(p.getName()).append(", ").append(p.getUri().getPath()).append(";\n"));
|
||||||
|
assertion.fail(String.format(FOUND_MORE_ONE_FRAGMENT, stringBuilder.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Feature feature : features) {
|
||||||
|
List<Pickle> pickles = feature.getPickles();
|
||||||
|
for (Pickle pickle : pickles) {
|
||||||
|
List<String> tags = pickle.getTags();
|
||||||
|
if (isFragmentTagContains(tags)) {
|
||||||
|
fragments.put(pickle.getName(), pickle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MutableValueGraph<Object, String> cacheFragmentsAsGraph(List<Feature> features,
|
||||||
|
Map<String, Pickle> fragmentsMap,
|
||||||
|
Map<Pickle, String> scenarioLanguageMap) {
|
||||||
|
MutableValueGraph<Object, String> graph = ValueGraphBuilder.directed().allowsSelfLoops(false).build();
|
||||||
|
|
||||||
|
for (Feature feature : features) {
|
||||||
|
List<Pickle> pickleList = feature.getPickles().stream().filter(pickle1 -> isScenario(pickle1)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
for (Pickle pickle : pickleList) {
|
||||||
|
addGraphNode(graph, pickle, fragmentsMap, scenarioLanguageMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addGraphNode(MutableValueGraph graph,
|
||||||
|
Pickle scenario,
|
||||||
|
Map<String, Pickle> fragmentsMap,
|
||||||
|
Map<Pickle, String> scenarioLanguageMap) {
|
||||||
|
graph.addNode(scenario);
|
||||||
|
String scenarioLanguage = scenarioLanguageMap.get(scenario);
|
||||||
|
List<Step> steps = scenario.getSteps();
|
||||||
|
|
||||||
|
for (Step step : steps) {
|
||||||
|
if (getMatch(step.getText(), REGEX_FRAGMENT_SPOILER)) {
|
||||||
|
assertion.fail(String.format("Для использования фрагментов используйте другой шаг: %s\n Путь к фича-файлу: %s", REGEX_FRAGMENT, scenario.getUri().getPath()));
|
||||||
|
}
|
||||||
|
String fragmentName = getFragmentName(step);
|
||||||
|
if (fragmentName != null) {
|
||||||
|
String fragmentNameFromMap = fragmentsMap.keySet().stream()
|
||||||
|
.filter(key -> key.equalsIgnoreCase(fragmentName))
|
||||||
|
.findAny().orElse(null);
|
||||||
|
Pickle pickle = fragmentsMap.get(fragmentNameFromMap);
|
||||||
|
|
||||||
|
if (pickle == null) {
|
||||||
|
// пофиг на данные @data
|
||||||
|
assertion.fail(String.format(FRAGMENT_NOT_EXIST, fragmentName));
|
||||||
|
} else {
|
||||||
|
String fragmentLanguage = pickle.getLanguage();
|
||||||
|
assertion.assertEquals(scenarioLanguage, fragmentLanguage, String.format(LANGUAGE_IS_NOT_SAME, scenarioLanguage, fragmentLanguage));
|
||||||
|
|
||||||
|
graph.putEdgeValue(scenario, pickle, "");
|
||||||
|
|
||||||
|
addGraphNode(graph, pickle, fragmentsMap, scenarioLanguageMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFragmentTagContains(List<String> tags) {
|
||||||
|
return tags.stream().anyMatch(tag -> tag.equals(FRAGMENT_TAG));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isScenario(Pickle pickle) {
|
||||||
|
List<String> tags = pickle.getTags();
|
||||||
|
return tags.stream().noneMatch(tag -> tag.equals(FRAGMENT_TAG));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTerminal(Object node) {
|
||||||
|
for (EndpointPair edge : fragmentsGraph.edges()) {
|
||||||
|
if (edge.nodeU().equals(node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replaceFragmentInScenario(Pickle scenario, Pickle fragment) throws IllegalAccessException {
|
||||||
|
List<Step> replacementSteps = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Step step : scenario.getSteps()) {
|
||||||
|
String fragmentName = getFragmentName(step);
|
||||||
|
if (fragmentName != null && !fragmentName.isEmpty() && fragmentName.equalsIgnoreCase(fragment.getName())) {
|
||||||
|
Step mockStep = getMockStep(step, fragment);
|
||||||
|
replacementSteps.add(mockStep);
|
||||||
|
replacementSteps.addAll(replaceSteps(step, fragment.getSteps(), fragment.getLanguage(), scenario));
|
||||||
|
replacementSteps.add(mockStep);
|
||||||
|
} else {
|
||||||
|
replacementSteps.add(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FieldUtils.writeField(scenario, "steps", replacementSteps, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерирует шаг-заглушки, который в аллюр отчете подменяется на спойлер
|
||||||
|
* @param step - шаг "вызвать фрагмент"
|
||||||
|
* @param fragment - сценарий-фрагмент
|
||||||
|
* @return - шаг ФРАГМЕНТ "название фрагмента"
|
||||||
|
*/
|
||||||
|
private Step getMockStep(Step step, Pickle fragment) {
|
||||||
|
IGherkinDialectProvider dialectProvider = new GherkinDialectProvider();
|
||||||
|
GherkinDialect dialect = dialectProvider.getDialect(fragment.getLanguage(), null);
|
||||||
|
Location location = step.getLocation();
|
||||||
|
return new CustomStep(
|
||||||
|
step.getId(),
|
||||||
|
String.format("ФРАГМЕНТ \"%s\"", fragment.getName()),
|
||||||
|
new ArrayList<>(),
|
||||||
|
Object.class,
|
||||||
|
dialect,
|
||||||
|
step.getPreviousGivenWhenThenKeyword(),
|
||||||
|
new Location(location.getLine(), location.getColumn()),
|
||||||
|
step.getKeyword());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFragmentName(Step step) {
|
||||||
|
return getMatchValueByGroupNumber(step.getText(), REGEX_FRAGMENT, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Step> replaceSteps(Step scenarioStep, List<Step> fragmentsSteps, String language, Pickle scenario) {
|
||||||
|
DataTableArgument argument = (DataTableArgument) scenarioStep.getArgument();
|
||||||
|
List<Step> replacementSteps = new ArrayList<>();
|
||||||
|
|
||||||
|
if (argument != null) {
|
||||||
|
Class argumentType = Object.class;
|
||||||
|
List<List<String>> lines = argument.cells();
|
||||||
|
checkArguments(lines, scenario);
|
||||||
|
for (Step fragmentStep : fragmentsSteps) {
|
||||||
|
List<List<String>> replaceFragmentLines = new ArrayList<>();
|
||||||
|
if (fragmentStep.getArgument() instanceof DataTableArgument) {
|
||||||
|
argumentType = DataTableArgument.class;
|
||||||
|
/* замена данных в таблице если она есть у шага */
|
||||||
|
DataTableArgument fragmentStepArgument = (DataTableArgument) fragmentStep.getArgument();
|
||||||
|
if (fragmentStepArgument != null) {
|
||||||
|
for (List<String> fragmentLines : fragmentStepArgument.cells()) {
|
||||||
|
for (List<String> line : lines) {
|
||||||
|
String regex = String.format(REGEX_VALUE, line.get(0));
|
||||||
|
fragmentLines = fragmentLines.stream().map(p -> p.replace(regex, line.get(1))).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
StringBuilder values = new StringBuilder();
|
||||||
|
for (String fragmentLine : fragmentLines) {
|
||||||
|
if (getMatch(fragmentLine, REGEX_EXAMPLE)) {
|
||||||
|
values.append(getMatchValueByGroupNumber(fragmentLine, REGEX_EXAMPLE, 1)).append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (values.length() != 0) {
|
||||||
|
assertion.fail(
|
||||||
|
String.format(NOT_FOUND_VARS_FOR_DYNAMIC_FRAGMENTS,
|
||||||
|
scenario.getName(),
|
||||||
|
fragmentStep.getText(),
|
||||||
|
values.toString()));
|
||||||
|
}
|
||||||
|
replaceFragmentLines.add(fragmentLines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (fragmentStep.getArgument() instanceof DocStringArgument) {
|
||||||
|
argumentType = DocStringArgument.class;
|
||||||
|
/* замена данных в DocStringArgument если есть у шага */
|
||||||
|
DocStringArgument fragmentStepArgument = (DocStringArgument) fragmentStep.getArgument();
|
||||||
|
if (fragmentStepArgument != null) {
|
||||||
|
String content = fragmentStepArgument.getContent();
|
||||||
|
for (List<String> line : lines) {
|
||||||
|
String regex = String.format(REGEX_VALUE, line.get(0));
|
||||||
|
content = content.replace(regex, line.get(1));
|
||||||
|
}
|
||||||
|
checkVarsForDynamicFragments(content, scenario, fragmentStep);
|
||||||
|
replaceFragmentLines.add(Collections.singletonList(content));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* замена в текстовке шага */
|
||||||
|
String replaceValue = fragmentStep.getText();
|
||||||
|
for (List<String> line : lines) {
|
||||||
|
String regex = String.format(REGEX_VALUE, line.get(0));
|
||||||
|
if (getMatch(fragmentStep.getText(), regex)) {
|
||||||
|
replaceValue = replaceValue.replace(regex, line.get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkVarsForDynamicFragments(replaceValue, scenario, fragmentStep);
|
||||||
|
|
||||||
|
IGherkinDialectProvider dialectProvider = new GherkinDialectProvider();
|
||||||
|
GherkinDialect dialect = dialectProvider.getDialect(language, null);
|
||||||
|
Location location = fragmentStep.getLocation();
|
||||||
|
Step replaceStep = new CustomStep(
|
||||||
|
fragmentStep.getId(),
|
||||||
|
replaceValue,
|
||||||
|
replaceFragmentLines,
|
||||||
|
argumentType,
|
||||||
|
dialect,
|
||||||
|
fragmentStep.getPreviousGivenWhenThenKeyword(),
|
||||||
|
new Location(location.getLine(), location.getColumn()),
|
||||||
|
fragmentStep.getKeyword());
|
||||||
|
replacementSteps.add(replaceStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
return replacementSteps;
|
||||||
|
} else {
|
||||||
|
return fragmentsSteps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkArguments(List<List<String>> lines, Pickle scenario) {
|
||||||
|
lines.forEach(line ->
|
||||||
|
assertion.assertEquals(
|
||||||
|
line.size(),
|
||||||
|
2,
|
||||||
|
String.format("Количество значений в таблице не равно двум. Актуальное значение: %s. Путь '%s', наименование сценария '%s'.",
|
||||||
|
line.size(),
|
||||||
|
scenario.getUri().getPath(),
|
||||||
|
scenario.getName()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkVarsForDynamicFragments(String value, Pickle scenario, Step fragmentStep) {
|
||||||
|
if (getMatch(value, REGEX_EXAMPLE)) {
|
||||||
|
assertion.fail(
|
||||||
|
String.format(NOT_FOUND_VARS_FOR_DYNAMIC_FRAGMENTS,
|
||||||
|
scenario.getName(),
|
||||||
|
fragmentStep.getText(),
|
||||||
|
getMatchValueByGroupNumber(value, REGEX_EXAMPLE, 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* метод для составления полного пути до директории или файла
|
||||||
|
*
|
||||||
|
* @param packages массив наименований папок или файла (в конце)
|
||||||
|
* @return полный путь до директории или файла
|
||||||
|
*/
|
||||||
|
private StringBuilder getParentPath(String... packages) {
|
||||||
|
StringBuilder pathBuilder = new StringBuilder();
|
||||||
|
String separator = System.getProperty("file.separator");
|
||||||
|
if (!System.getProperty("base.dir", "").isEmpty()) {
|
||||||
|
pathBuilder.append(System.getProperty("base.dir"));
|
||||||
|
} else {
|
||||||
|
pathBuilder.append(System.getProperty("user.dir"));
|
||||||
|
}
|
||||||
|
for (String packageName : packages) {
|
||||||
|
if (pathBuilder.toString().endsWith(separator)) {
|
||||||
|
pathBuilder.append(packageName);
|
||||||
|
} else {
|
||||||
|
pathBuilder.append(separator).append(packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathBuilder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package ru.lanit.at.corecommonstep.fragment;
|
||||||
|
|
||||||
|
import io.cucumber.core.resource.Resource;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
public class GherkinResource implements Resource {
|
||||||
|
|
||||||
|
private final URI path;
|
||||||
|
private InputStream source;
|
||||||
|
|
||||||
|
public GherkinResource(String source, URI path) {
|
||||||
|
this.source = new ByteArrayInputStream(source.getBytes(Charset.defaultCharset()));
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getUri() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,146 @@
|
|||||||
|
package ru.lanit.at.corecommonstep.fragment;
|
||||||
|
|
||||||
|
import io.cucumber.core.feature.FeatureParser;
|
||||||
|
import io.cucumber.core.gherkin.Feature;
|
||||||
|
import io.cucumber.core.gherkin.Pickle;
|
||||||
|
import io.cucumber.core.gherkin.Step;
|
||||||
|
import io.cucumber.core.resource.Resource;
|
||||||
|
import io.cucumber.plugin.event.DataTableArgument;
|
||||||
|
import io.cucumber.plugin.event.DocStringArgument;
|
||||||
|
import org.testng.asserts.Assertion;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class GherkinSerializer {
|
||||||
|
|
||||||
|
public static final String DATA_TAG = "@data=$";
|
||||||
|
private static final String NL = "\n";
|
||||||
|
private static final String SPACE = " ";
|
||||||
|
private StringBuilder builder;
|
||||||
|
private Assertion assertion;
|
||||||
|
|
||||||
|
public GherkinSerializer() {
|
||||||
|
builder = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Feature> reserializeFeatures(List<Feature> cucumberFeatures, Assertion assertion) {
|
||||||
|
this.assertion = assertion;
|
||||||
|
FeatureParser featureParser = new FeatureParser(() -> UUID.randomUUID());
|
||||||
|
List<Feature> parsedFeatures = new ArrayList<>();
|
||||||
|
cucumberFeatures.forEach(cucumberFeature -> {
|
||||||
|
builder = new StringBuilder();
|
||||||
|
List<Pickle> pickles = cucumberFeature.getPickles();
|
||||||
|
if (pickles.size() > 0) {
|
||||||
|
builder.append("#language: " + pickles.get(0).getLanguage());
|
||||||
|
} else {
|
||||||
|
this.assertion.fail(String.format("\nПроверьте синтаксис фича-файла!:\n%s\n", cucumberFeature.getUri()));
|
||||||
|
}
|
||||||
|
nl(1);
|
||||||
|
Optional<String> featureKeyword = cucumberFeature.getKeyword();
|
||||||
|
Optional<String> featureName = cucumberFeature.getName();
|
||||||
|
if (featureKeyword.isPresent()) {
|
||||||
|
builder.append(featureKeyword.get()).append(":").append(SPACE);
|
||||||
|
} else {
|
||||||
|
this.assertion.fail("\"Нет кейворда Функционал!\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
featureName.ifPresent(name -> builder.append(name));
|
||||||
|
nl(1);
|
||||||
|
pickles.forEach(this::buildScenario);
|
||||||
|
|
||||||
|
Resource gherkinResource = new GherkinResource(builder.toString(), cucumberFeature.getUri());
|
||||||
|
Optional<Feature> feature = featureParser.parseResource(gherkinResource);
|
||||||
|
feature.ifPresent(parsedFeatures::add);
|
||||||
|
});
|
||||||
|
|
||||||
|
return parsedFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildScenario(Pickle pickle) {
|
||||||
|
buildScenarioTags(pickle);
|
||||||
|
|
||||||
|
tab(1);
|
||||||
|
builder.append(pickle.getKeyword()).append(":").append(SPACE).append(pickle.getName());
|
||||||
|
nl(1);
|
||||||
|
|
||||||
|
List<String> dataTags = pickle.getTags().stream().filter(p -> p.startsWith(DATA_TAG)).collect(Collectors.toList());
|
||||||
|
if (!dataTags.isEmpty() && dataTags.size() > 1) {
|
||||||
|
assertion.fail(String.format("Есть два тега '%s' в сценарии '%s'. Необходимо указать только один тег для данных!\n", dataTags.toString(), pickle.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dataTags.isEmpty()) {
|
||||||
|
pickle.getSteps().forEach(step -> buildStep(step, dataTags.get(0), pickle));
|
||||||
|
} else {
|
||||||
|
pickle.getSteps().forEach(step -> buildStep(step, null, pickle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildScenarioTags(Pickle scenarioDefinition) {
|
||||||
|
List<String> tags = scenarioDefinition.getTags();
|
||||||
|
if (!tags.isEmpty()) {
|
||||||
|
tags = tags.stream().filter(p -> !p.startsWith(DATA_TAG)).collect(Collectors.toList());
|
||||||
|
tab(1);
|
||||||
|
tags.forEach(tag -> builder.append(tag).append(SPACE));
|
||||||
|
nl(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildStep(Step step, String dataTag, Pickle scenario) {
|
||||||
|
tab(2);
|
||||||
|
builder.append(step.getKeyword()).append(SPACE).append(step.getText());
|
||||||
|
if (step.getArgument() != null) {
|
||||||
|
nl(1);
|
||||||
|
if (step.getArgument() instanceof DataTableArgument) {
|
||||||
|
DataTableArgument table = (DataTableArgument) step.getArgument();
|
||||||
|
table.cells().forEach(tableRow -> buildTableRow(tableRow, dataTag, scenario));
|
||||||
|
} else if (step.getArgument() instanceof DocStringArgument) {
|
||||||
|
tab(2);
|
||||||
|
builder.append("\"\"\"");
|
||||||
|
nl(1);
|
||||||
|
DocStringArgument docString = (DocStringArgument) step.getArgument();
|
||||||
|
tab(2);
|
||||||
|
builder.append(docString.getContent());
|
||||||
|
nl(1);
|
||||||
|
tab(2);
|
||||||
|
builder.append("\"\"\"");
|
||||||
|
nl(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
builder.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildTableRow(List<String> tableRow, String dataTag, Pickle scenario) {
|
||||||
|
List<String> collect = tableRow.stream()
|
||||||
|
.map(tableCell -> tableCell.replaceAll("\\|", "\\\\|"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
tab(2);
|
||||||
|
space(2);
|
||||||
|
builder.append("|").append(String.join("|", collect)).append("|");
|
||||||
|
nl(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void space(int count) {
|
||||||
|
appendTimes(SPACE, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nl(int count) {
|
||||||
|
appendTimes(NL, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tab(int count) {
|
||||||
|
appendTimes(SPACE, count * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendTimes(String source, int times) {
|
||||||
|
for (int i = 0; i < times; i++) {
|
||||||
|
builder.append(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/main/java/ru/lanit/at/hooks/WebHooks.java
Normal file
50
src/main/java/ru/lanit/at/hooks/WebHooks.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package ru.lanit.at.hooks;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
import io.cucumber.java.After;
|
||||||
|
import io.cucumber.java.Before;
|
||||||
|
import io.cucumber.java.Scenario;
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import org.openqa.selenium.remote.RemoteWebDriver;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import ru.lanit.at.utils.VideoSaveHelper;
|
||||||
|
import ru.lanit.at.utils.web.properties.Configurations;
|
||||||
|
|
||||||
|
import static ru.lanit.at.assertion.AssertsManager.getAssertsManager;
|
||||||
|
|
||||||
|
public class WebHooks {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(WebHooks.class);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup(Scenario scenario) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void close() {
|
||||||
|
if (WebDriverRunner.hasWebDriverStarted()) {
|
||||||
|
String sessionId = ((RemoteWebDriver) WebDriverRunner.getWebDriver()).getSessionId().toString();
|
||||||
|
LOGGER.info("Закрытие сессии драйвера");
|
||||||
|
WebDriverRunner.closeWebDriver();
|
||||||
|
attachVideo(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssertsManager().softAssert().assertAll();
|
||||||
|
getAssertsManager().softAssert().flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Прикрепление видео в Аллюр отчет при условии запускать с параметром remoteUrl. */
|
||||||
|
private void attachVideo(String sessionId) {
|
||||||
|
Configurations cf = ConfigFactory.create(Configurations.class
|
||||||
|
, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
if (!cf.getRemoteURL().isEmpty() && cf.getEnableVideo()) {
|
||||||
|
VideoSaveHelper vs = new VideoSaveHelper(sessionId, cf.getRemoteURL());
|
||||||
|
vs.attachVideoFileRest();
|
||||||
|
vs.deleteSelenoidVideoRest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
17
src/main/java/ru/lanit/at/pages/GooglePage.java
Normal file
17
src/main/java/ru/lanit/at/pages/GooglePage.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package ru.lanit.at.pages;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import ru.lanit.at.utils.web.annotations.Name;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.WebPage;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$x;
|
||||||
|
|
||||||
|
@Name(value = "Google")
|
||||||
|
public class GooglePage extends WebPage {
|
||||||
|
|
||||||
|
@Name("поле поиска")
|
||||||
|
private SelenideElement searchField = $x("//input[@name='q']");
|
||||||
|
|
||||||
|
@Name("кнопка поиска")
|
||||||
|
private SelenideElement searchButton = $x("//input[@value='Поиск в Google']");
|
||||||
|
}
|
||||||
15
src/main/java/ru/lanit/at/pages/GoogleResultPage.java
Normal file
15
src/main/java/ru/lanit/at/pages/GoogleResultPage.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package ru.lanit.at.pages;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
import ru.lanit.at.utils.web.annotations.Name;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.WebPage;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$;
|
||||||
|
|
||||||
|
@Name(value = "Google страница результатов")
|
||||||
|
public class GoogleResultPage extends WebPage {
|
||||||
|
|
||||||
|
@Name("виджет погоды")
|
||||||
|
private SelenideElement searchField = $(By.id("wob_wc"));
|
||||||
|
}
|
||||||
51
src/main/java/ru/lanit/at/steps/VariableSteps.java
Normal file
51
src/main/java/ru/lanit/at/steps/VariableSteps.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package ru.lanit.at.steps;
|
||||||
|
|
||||||
|
|
||||||
|
import io.cucumber.java.ru.И;
|
||||||
|
import io.cucumber.java.ru.Когда;
|
||||||
|
import io.qameta.allure.Allure;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import ru.lanit.at.utils.ContextHolder;
|
||||||
|
import ru.lanit.at.utils.DataGenerator;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static ru.lanit.at.utils.ContextHolder.replaceVarsIfPresent;
|
||||||
|
|
||||||
|
public class VariableSteps {
|
||||||
|
private Logger LOGGER = LogManager.getLogger(this.getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать и Сохранить значение
|
||||||
|
*
|
||||||
|
* @param field - наименование элемента
|
||||||
|
* @param key - значение
|
||||||
|
*/
|
||||||
|
@Когда("сгенерировать значение {string} и сохранить под именем {string}")
|
||||||
|
public void generateAndSaveValue(String field, String key) {
|
||||||
|
String value = DataGenerator.generateValueByMask(field);
|
||||||
|
ContextHolder.asMap().put(key, value);
|
||||||
|
LOGGER.info("значение '{}' сохранено под именем '{}'", value, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@И("сгенерировать переменные")
|
||||||
|
public void generateVariables(Map<String, String> table) {
|
||||||
|
table.forEach((k, v) -> {
|
||||||
|
String value = DataGenerator.generateValueByMask(replaceVarsIfPresent(v));
|
||||||
|
ContextHolder.put(k, value);
|
||||||
|
Allure.addAttachment(k, "application/json", k + ": " + value, ".txt");
|
||||||
|
LOGGER.info("Сгенерирована переменная: {}={}", k, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@И("создать контекстные переменные")
|
||||||
|
public void createContextVariables(Map<String, String> table) {
|
||||||
|
table.forEach((k, v) -> {
|
||||||
|
ContextHolder.put(k, v);
|
||||||
|
LOGGER.info("Сохранена переменная: {}={}", k, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
80
src/main/java/ru/lanit/at/steps/api/ApiSteps.java
Normal file
80
src/main/java/ru/lanit/at/steps/api/ApiSteps.java
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package ru.lanit.at.steps.api;
|
||||||
|
|
||||||
|
|
||||||
|
import io.cucumber.datatable.DataTable;
|
||||||
|
import io.cucumber.java.ru.И;
|
||||||
|
import io.qameta.allure.Allure;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.testng.Assert;
|
||||||
|
import ru.lanit.at.api.ApiRequest;
|
||||||
|
import ru.lanit.at.api.models.RequestModel;
|
||||||
|
import ru.lanit.at.utils.CompareUtil;
|
||||||
|
import ru.lanit.at.utils.ContextHolder;
|
||||||
|
import ru.lanit.at.utils.VariableUtil;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static ru.lanit.at.utils.ContextHolder.replaceVarsIfPresent;
|
||||||
|
import static ru.lanit.at.utils.JsonUtil.getFieldFromJson;
|
||||||
|
|
||||||
|
public class ApiSteps {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ApiSteps.class);
|
||||||
|
private ApiRequest apiRequest;
|
||||||
|
|
||||||
|
@И("создать запрос")
|
||||||
|
public void createRequest(RequestModel requestModel) {
|
||||||
|
apiRequest = new ApiRequest(requestModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@И("добавить header")
|
||||||
|
public void addHeaders(DataTable dataTable) {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
dataTable.asLists().forEach(it -> headers.put(it.get(0), replaceVarsIfPresent(it.get(1))));
|
||||||
|
apiRequest.setHeaders(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@И("добавить query параметры")
|
||||||
|
public void addQuery(DataTable dataTable) {
|
||||||
|
Map<String, String> query = new HashMap<>();
|
||||||
|
dataTable.asLists().forEach(it -> query.put(it.get(0), replaceVarsIfPresent(it.get(1))));
|
||||||
|
apiRequest.setQuery(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@И("отправить запрос")
|
||||||
|
public void send() {
|
||||||
|
apiRequest.sendRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@И("статус код {int}")
|
||||||
|
public void expectStatusCode(int code) {
|
||||||
|
int actualStatusCode = apiRequest.getResponse().statusCode();
|
||||||
|
Assert.assertEquals(actualStatusCode, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
@И("извлечь данные")
|
||||||
|
public void extractVariables(Map<String, String> vars) {
|
||||||
|
String responseBody = apiRequest.getResponse().body().asPrettyString();
|
||||||
|
vars.forEach((k, jsonPath) -> {
|
||||||
|
jsonPath = replaceVarsIfPresent(jsonPath);
|
||||||
|
String extractedValue = VariableUtil.extractBrackets(getFieldFromJson(responseBody, jsonPath));
|
||||||
|
ContextHolder.put(k, extractedValue);
|
||||||
|
Allure.addAttachment(k, "application/json", extractedValue, ".txt");
|
||||||
|
LOG.info("Извлечены данные: {}={}", k, extractedValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@И("сравнить значения")
|
||||||
|
public void compareVars(DataTable table) {
|
||||||
|
table.asLists().forEach(it -> {
|
||||||
|
String expect = replaceVarsIfPresent(it.get(0));
|
||||||
|
String actual = replaceVarsIfPresent(it.get(2));
|
||||||
|
boolean compareResult = CompareUtil.compare(expect, actual, it.get(1));
|
||||||
|
Assert.assertTrue(compareResult, String.format("Ожидаемое: '%s'\nФактическое: '%s'\nОператор сравнения: '%s'\n", expect, actual, it.get(1)));
|
||||||
|
Allure.addAttachment(expect, "application/json", expect + it.get(1) + actual, ".txt");
|
||||||
|
LOG.info("Сравнение значений: {} {} {}", expect, it.get(1), actual);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package ru.lanit.at.steps.api.type_params;
|
||||||
|
|
||||||
|
|
||||||
|
import io.cucumber.java.DataTableType;
|
||||||
|
import ru.lanit.at.api.models.RequestModel;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DataTableTypeContainer {
|
||||||
|
|
||||||
|
@DataTableType
|
||||||
|
public RequestModel requestModel(Map<String, String> entry) {
|
||||||
|
return new RequestModel(
|
||||||
|
entry.get("method"),
|
||||||
|
entry.get("body"),
|
||||||
|
entry.get("path"),
|
||||||
|
entry.get("url")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
46
src/main/java/ru/lanit/at/steps/web/AbstractWebSteps.java
Normal file
46
src/main/java/ru/lanit/at/steps/web/AbstractWebSteps.java
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package ru.lanit.at.steps.web;
|
||||||
|
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import ru.lanit.at.utils.ContextHolder;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.Environment;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.PageManager;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.WebPage;
|
||||||
|
import ru.lanit.at.utils.web.properties.Configurations;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class AbstractWebSteps {
|
||||||
|
protected final Configurations configurations;
|
||||||
|
protected Logger LOGGER = LogManager.getLogger(this.getClass());
|
||||||
|
protected PageManager pageManager;
|
||||||
|
|
||||||
|
|
||||||
|
public AbstractWebSteps(PageManager pageManager) {
|
||||||
|
this.pageManager = pageManager;
|
||||||
|
configurations = ConfigFactory.create(Configurations.class, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, Object> getStorage() {
|
||||||
|
return ContextHolder.asMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void saveValueInStorage(String key, Object value) {
|
||||||
|
getStorage().put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WebPage getPage(String name) {
|
||||||
|
WebPage page = Environment.getPage(name);
|
||||||
|
pageManager.setCurrentPage(page);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T extends WebPage> T getPage(Class<T> c) {
|
||||||
|
WebPage page = Environment.getPage(c);
|
||||||
|
pageManager.setCurrentPage(page);
|
||||||
|
return (T) page;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/ru/lanit/at/steps/web/DebugWebSteps.java
Normal file
17
src/main/java/ru/lanit/at/steps/web/DebugWebSteps.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package ru.lanit.at.steps.web;
|
||||||
|
|
||||||
|
|
||||||
|
import io.cucumber.java.ru.Если;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.PageManager;
|
||||||
|
|
||||||
|
public class DebugWebSteps extends AbstractWebSteps {
|
||||||
|
|
||||||
|
public DebugWebSteps(PageManager pageManager) {
|
||||||
|
super(pageManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Если("шаг № {string}")
|
||||||
|
public void stepNumber(String stepNum) {
|
||||||
|
LOGGER.info("Шаг номер " + stepNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
154
src/main/java/ru/lanit/at/steps/web/WebActionWebSteps.java
Normal file
154
src/main/java/ru/lanit/at/steps/web/WebActionWebSteps.java
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package ru.lanit.at.steps.web;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Condition;
|
||||||
|
import com.codeborne.selenide.Selectors;
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import io.cucumber.java.ru.Если;
|
||||||
|
import io.cucumber.java.ru.И;
|
||||||
|
import io.cucumber.java.ru.Когда;
|
||||||
|
import ru.lanit.at.actions.WebActions;
|
||||||
|
import ru.lanit.at.utils.Sleep;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.PageManager;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import static com.codeborne.selenide.Selenide.$;
|
||||||
|
import static ru.lanit.at.utils.VariableUtil.replaceVars;
|
||||||
|
|
||||||
|
|
||||||
|
public class WebActionWebSteps extends AbstractWebSteps {
|
||||||
|
|
||||||
|
public WebActionWebSteps(PageManager pageManager) {
|
||||||
|
super(pageManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* нажимает на элемент по тексту
|
||||||
|
*
|
||||||
|
* @param text текст элемента
|
||||||
|
*/
|
||||||
|
@Когда("кликнуть на элемент по тексту {string}")
|
||||||
|
public void clickElementWithText(String text) {
|
||||||
|
$(Selectors.byText(text))
|
||||||
|
.shouldBe(Condition.visible)
|
||||||
|
.click();
|
||||||
|
LOGGER.info("клик на элемент по тексту '{}'", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Если("кликнуть на элемент {string}")
|
||||||
|
public void clickOnElement(String elementName) {
|
||||||
|
SelenideElement element = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName);
|
||||||
|
element
|
||||||
|
.shouldBe(Condition.visible, Duration.ofSeconds(10))
|
||||||
|
.click();
|
||||||
|
LOGGER.info("клик на элемент '{}'", elementName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Если("установить чекбокс на элементе {string}")
|
||||||
|
public void selectCheckboxOnElement(String elementName) {
|
||||||
|
SelenideElement element = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName);
|
||||||
|
element
|
||||||
|
.shouldBe(Condition.enabled, Duration.ofSeconds(60));
|
||||||
|
WebActions.setCheckBox(element, true);
|
||||||
|
LOGGER.info("чекбокс установлен на элементе '{}'", elementName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Если("убрать чекбокс на элементе {string}")
|
||||||
|
public void unselectCheckboxOnElement(String elementName) {
|
||||||
|
SelenideElement element = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName);
|
||||||
|
element
|
||||||
|
.shouldBe(Condition.enabled, Duration.ofSeconds(60));
|
||||||
|
WebActions.setCheckBox(element, false);
|
||||||
|
LOGGER.info("чекбокс снят на элементе '{}'", elementName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* скролл до элемента
|
||||||
|
*
|
||||||
|
* @param elementName наименование элемента
|
||||||
|
*/
|
||||||
|
@Когда("проскроллить страницу до элемента {string}")
|
||||||
|
public void scrollToElement(String elementName) {
|
||||||
|
SelenideElement element = pageManager.getCurrentPage().getElement(elementName);
|
||||||
|
element.shouldBe(Condition.visible)
|
||||||
|
.scrollIntoView("{block: 'center'}");
|
||||||
|
LOGGER.info("скролл страницы до элемента '{}'", elementName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* скролл до текста
|
||||||
|
*
|
||||||
|
* @param text текст
|
||||||
|
*/
|
||||||
|
@Когда("проскроллить страницу до текста {string}")
|
||||||
|
public void scrollToText(String text) {
|
||||||
|
SelenideElement element = $(Selectors.byText(text));
|
||||||
|
element.shouldBe(Condition.visible)
|
||||||
|
.scrollIntoView("{block: 'center'}");
|
||||||
|
LOGGER.info("скролл страницы до текста '{}'", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@И("подождать {int} сек")
|
||||||
|
public void waitSeconds(int timeout) {
|
||||||
|
Sleep.pauseSec(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ввод значения в элемент
|
||||||
|
*
|
||||||
|
* @param field - наименование элемента
|
||||||
|
* @param value - значение
|
||||||
|
*/
|
||||||
|
@Когда("ввести в поле {string} значение {string}")
|
||||||
|
public void fillTheField(String field, String value) {
|
||||||
|
value = replaceVars(value, getStorage());
|
||||||
|
SelenideElement fieldElement = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(field);
|
||||||
|
fieldElement
|
||||||
|
.shouldBe(Condition.visible, Duration.ofSeconds(60))
|
||||||
|
.setValue(value);
|
||||||
|
LOGGER.info("в поле '{}' введено значение '{}'", field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохранить значения из элемент
|
||||||
|
*
|
||||||
|
* @param field - наименование элемента
|
||||||
|
* @param key - значение
|
||||||
|
*/
|
||||||
|
@Когда("сохранить значение из поля {string} под именем {string}")
|
||||||
|
public void saveTextField(String field, String key) {
|
||||||
|
SelenideElement fieldElement = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(field);
|
||||||
|
String elementValue = fieldElement
|
||||||
|
.shouldBe(Condition.visible, Duration.ofSeconds(60))
|
||||||
|
.getValue();
|
||||||
|
saveValueInStorage(key, elementValue);
|
||||||
|
LOGGER.info("значение '{}' сохранено под именем '{}'", elementValue, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистка поля
|
||||||
|
*
|
||||||
|
* @param elementName наименование элемента
|
||||||
|
*/
|
||||||
|
@Если("очистить поле {string}")
|
||||||
|
public void clearFiled(String elementName) {
|
||||||
|
pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName)
|
||||||
|
.shouldBe(Condition.visible)
|
||||||
|
.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
143
src/main/java/ru/lanit/at/steps/web/WebCheckWebSteps.java
Normal file
143
src/main/java/ru/lanit/at/steps/web/WebCheckWebSteps.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package ru.lanit.at.steps.web;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import io.cucumber.java.ru.Когда;
|
||||||
|
import io.cucumber.java.ru.Тогда;
|
||||||
|
import ru.lanit.at.actions.WebChecks;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.PageManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class WebCheckWebSteps extends AbstractWebSteps {
|
||||||
|
|
||||||
|
public WebCheckWebSteps(PageManager pageManager) {
|
||||||
|
super(pageManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* проверка присутствия текста на странице
|
||||||
|
*
|
||||||
|
* @param text текст
|
||||||
|
*/
|
||||||
|
@Когда("проверить что элемент {string} содержит текст:")
|
||||||
|
public void textAppearOnThePage(String elementName, List<String> text) {
|
||||||
|
SelenideElement element = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName);
|
||||||
|
WebChecks.elementContainsText(element, text);
|
||||||
|
LOGGER.info("элемент '{}' содержит текст '{}'", elementName, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* проверка присутствия текста на странице
|
||||||
|
*
|
||||||
|
* @param text текст
|
||||||
|
*/
|
||||||
|
@Когда("на странице присутствует текст {string}")
|
||||||
|
public void textAppearOnThePage(String text) {
|
||||||
|
WebChecks.textVisibleOnPage(text, null);
|
||||||
|
LOGGER.info("на странице '{}' имеется текст '{}'", pageManager.getCurrentPage().name(), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* проверка отсутствия текста на странице
|
||||||
|
*
|
||||||
|
* @param text текст
|
||||||
|
*/
|
||||||
|
@Когда("на странице отсутствует текст {string}")
|
||||||
|
public void textVisibleOnPage(String text) {
|
||||||
|
WebChecks.textAbsentOnPage(text, null);
|
||||||
|
LOGGER.info("на странице '{}' отсутствует текст '{}'", pageManager.getCurrentPage().name(), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ожидание появления текста на странице в течении некоторого времени
|
||||||
|
*
|
||||||
|
* @param text текст
|
||||||
|
* @param timeoutSeconds количество секунд
|
||||||
|
*/
|
||||||
|
@Когда("подождать появления текста {string} в течение {int} секунд")
|
||||||
|
public void waitUntilTextAppearOnPage(String text, int timeoutSeconds) {
|
||||||
|
WebChecks.textVisibleOnPage(text, timeoutSeconds);
|
||||||
|
LOGGER.info("на странице '{}' имеется текст '{}'", pageManager.getCurrentPage().name(), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ожидание исчезновения текста на странице в течении некоторого времени
|
||||||
|
*
|
||||||
|
* @param text текст
|
||||||
|
* @param timeoutSeconds количество секунд
|
||||||
|
*/
|
||||||
|
@Когда("подождать исчезновения текста {string} в течение {int} секунд")
|
||||||
|
public void waitUntilTextAbsentOnPage(String text, int timeoutSeconds) {
|
||||||
|
WebChecks.textAbsentOnPage(text, timeoutSeconds);
|
||||||
|
LOGGER.info("на странице '{}' отсутствует текст '{}'", pageManager.getCurrentPage().name(), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ожидание элемента на странице в течении некоторого времени
|
||||||
|
*
|
||||||
|
* @param elementName наименование элемента
|
||||||
|
* @param timeoutSeconds количество секунд
|
||||||
|
*/
|
||||||
|
@Когда("подождать появления элемента {string} в течение {int} секунд")
|
||||||
|
public void waitUntilElementIsVisibleOnPage(String elementName, int timeoutSeconds) {
|
||||||
|
SelenideElement element = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName);
|
||||||
|
WebChecks.elementVisibleOnPage(element, timeoutSeconds);
|
||||||
|
LOGGER.info("на странице '{}' имеется элемент '{}'", pageManager.getCurrentPage().name(), elementName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* проверка что на странице отображен элемент
|
||||||
|
*
|
||||||
|
* @param elementName наименование элемента
|
||||||
|
*/
|
||||||
|
@Когда("на странице имеется элемент {string}")
|
||||||
|
public void elementAppearOnThePage(String elementName) {
|
||||||
|
SelenideElement element = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName);
|
||||||
|
WebChecks.elementVisibleOnPage(element, null);
|
||||||
|
LOGGER.info("на странице '{}' имеется элемент '{}'", pageManager.getCurrentPage().name(), elementName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* проверка что на странице отсуствует элемент
|
||||||
|
*
|
||||||
|
* @param elementName наименование элемента
|
||||||
|
*/
|
||||||
|
@Когда("на странице отсутствует элемент {string}")
|
||||||
|
public void elementAbsentOnPage(String elementName) {
|
||||||
|
SelenideElement element = pageManager
|
||||||
|
.getCurrentPage()
|
||||||
|
.getElement(elementName);
|
||||||
|
WebChecks.elementAbsentOnPage(element, null);
|
||||||
|
LOGGER.info("на странице '{}' отсутствует элемент '{}'", pageManager.getCurrentPage().name(), elementName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* проверка текущего url
|
||||||
|
* <br/>можно начать написание url с переменной %{apiUrl}% или %{webUrl}%
|
||||||
|
*
|
||||||
|
* @param url часть или полный url (также может содержать переменные)
|
||||||
|
*/
|
||||||
|
@Тогда("проверить что текущий url соответствует {string}")
|
||||||
|
public void currentUrlEqualsExpected(String url) {
|
||||||
|
WebChecks.urlEquals(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* проверка текущего url
|
||||||
|
* <br/>можно начать написание url с переменной %{apiUrl}% или %{webUrl}%
|
||||||
|
*
|
||||||
|
* @param url часть url (также может содержать переменные)
|
||||||
|
*/
|
||||||
|
@Тогда("проверить что текущий url содержит текст {string}")
|
||||||
|
public void currentUrlContainsExpected(String url) {
|
||||||
|
WebChecks.urlContains(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/main/java/ru/lanit/at/steps/web/WindowWebSteps.java
Normal file
103
src/main/java/ru/lanit/at/steps/web/WindowWebSteps.java
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package ru.lanit.at.steps.web;
|
||||||
|
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Selenide;
|
||||||
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
import io.cucumber.java.ru.Если;
|
||||||
|
import io.cucumber.java.ru.И;
|
||||||
|
import io.cucumber.java.ru.Когда;
|
||||||
|
import ru.lanit.at.actions.WebActions;
|
||||||
|
import ru.lanit.at.utils.selenide.DriverManager;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.PageManager;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.WebPage;
|
||||||
|
|
||||||
|
public class WindowWebSteps extends AbstractWebSteps {
|
||||||
|
|
||||||
|
public WindowWebSteps(PageManager pageManager) {
|
||||||
|
super(pageManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* открывает браузер
|
||||||
|
*/
|
||||||
|
@Если("открыть браузер")
|
||||||
|
public void openDriver() {
|
||||||
|
DriverManager.startDriver();
|
||||||
|
DriverManager.startApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* открывает страницу по ссылке
|
||||||
|
*
|
||||||
|
* @param url url
|
||||||
|
*/
|
||||||
|
@Если("открыть url {string}")
|
||||||
|
public void open(String url) {
|
||||||
|
DriverManager.startDriver();
|
||||||
|
Selenide.open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* открывает новую вкладку в браузере с url и переключается на нее
|
||||||
|
*
|
||||||
|
* @param url url
|
||||||
|
*/
|
||||||
|
@И("открыть новую вкладку с url {string}")
|
||||||
|
public void openNewTab(String url) {
|
||||||
|
WebActions.openUrlOnNewTab(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* если вкладок 2, то переключится на следующую вкладку.
|
||||||
|
* <br/>по факту переключается на последнюю вкладку
|
||||||
|
*/
|
||||||
|
@И("переключиться на следующую вкладку")
|
||||||
|
public void switchNextTab() {
|
||||||
|
WebActions.switchToNextTab(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* переключается на вкладку по порядковому номеру
|
||||||
|
*
|
||||||
|
* @param number порядковый номер вкладки в браузере
|
||||||
|
*/
|
||||||
|
@И("пеерключиться на вкладку по порядковому номеру {int}")
|
||||||
|
public void switchNextTabByNumber(int number) {
|
||||||
|
WebActions.switchToNextTab(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* закрывает текущую вкладку и переходит на предыдущую (последняя вкладка в наборе)
|
||||||
|
*/
|
||||||
|
@И("закрыть текущую вкладку и перейти на предыдущую")
|
||||||
|
public void closeTabAndSwitchTab() {
|
||||||
|
WebActions.closeCurrentTabAndSwitchToPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* закрывает страницу
|
||||||
|
*/
|
||||||
|
@Если("закрыть страницу")
|
||||||
|
public void closeDriver() {
|
||||||
|
WebDriverRunner.getWebDriver().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* действие обозначает что мы находимся на определенной странице
|
||||||
|
* для работы с описанными элементами в пейдже
|
||||||
|
*
|
||||||
|
* @param pageName наименование страницы
|
||||||
|
*/
|
||||||
|
@Если("пользователь на странице {string}")
|
||||||
|
@Когда("инициализация страницы {string}")
|
||||||
|
@И("переход на страницу {string}")
|
||||||
|
|
||||||
|
public void setPage(String pageName) {
|
||||||
|
WebPage page = getPage(pageName);
|
||||||
|
pageManager.setCurrentPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package ru.lanit.at.steps.web.pages;
|
||||||
|
|
||||||
|
import ru.lanit.at.steps.web.AbstractWebSteps;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.PageManager;
|
||||||
|
|
||||||
|
|
||||||
|
public class GooglePageWebSteps extends AbstractWebSteps {
|
||||||
|
|
||||||
|
public GooglePageWebSteps(PageManager pageManager) {
|
||||||
|
super(pageManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// реализация шагов специфичных для страницы GooglePage
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
62
src/main/java/ru/lanit/at/utils/CompareUtil.java
Normal file
62
src/main/java/ru/lanit/at/utils/CompareUtil.java
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Sorokin Boris on 21.01.2022.
|
||||||
|
*/
|
||||||
|
public class CompareUtil {
|
||||||
|
public static boolean compare(String firstValue, String secondValue, String operator) {
|
||||||
|
if (isNumeric(firstValue) && isNumeric(secondValue)) {
|
||||||
|
return compareNumbers(firstValue, secondValue, operator);
|
||||||
|
} else {
|
||||||
|
return compareStrings(firstValue, secondValue, operator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean compareStrings(String firstValue, String secondValue, String operator) {
|
||||||
|
switch (operator.toLowerCase()) {
|
||||||
|
case "равно":
|
||||||
|
case "==":
|
||||||
|
return firstValue.equals(secondValue);
|
||||||
|
case "не равно":
|
||||||
|
case "!=":
|
||||||
|
return !firstValue.equals(secondValue);
|
||||||
|
case "содержит":
|
||||||
|
return firstValue.contains(secondValue);
|
||||||
|
case "не содержит":
|
||||||
|
return !firstValue.contains(secondValue);
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(String
|
||||||
|
.format("Параметр '%s' является неверным для метода '%s'.", operator, "compareStrings"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean compareNumbers(String firstValue, String secondValue, String operator) {
|
||||||
|
BigDecimal number1 = new BigDecimal(firstValue);
|
||||||
|
BigDecimal number2 = new BigDecimal(secondValue);
|
||||||
|
switch (operator.toLowerCase()) {
|
||||||
|
case "равно":
|
||||||
|
case "==":
|
||||||
|
return number1.equals(number2);
|
||||||
|
case "не равно":
|
||||||
|
case "!=":
|
||||||
|
return !number1.equals(number2);
|
||||||
|
case "больше":
|
||||||
|
case ">":
|
||||||
|
return number1.compareTo(number2) > 0;
|
||||||
|
case "меньше":
|
||||||
|
case "<":
|
||||||
|
return number1.compareTo(number2) < 0;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(String
|
||||||
|
.format("Параметр '%s' является неверным для метода '%s'.", operator, "compareNumbers"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNumeric(String strNum) {
|
||||||
|
return NumberUtils.isParsable(strNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/main/java/ru/lanit/at/utils/ContextHolder.java
Normal file
48
src/main/java/ru/lanit/at/utils/ContextHolder.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс для хранения переменных теста
|
||||||
|
* Синтаксис %{var_name}%
|
||||||
|
*/
|
||||||
|
public class ContextHolder {
|
||||||
|
|
||||||
|
private static final ThreadLocal<Map<String, Object>> THREAD = new ThreadLocal<>();
|
||||||
|
|
||||||
|
private static Map<String, Object> getThread() {
|
||||||
|
Map<String, Object> vault = THREAD.get();
|
||||||
|
if (vault == null) {
|
||||||
|
vault = new HashMap<>();
|
||||||
|
THREAD.set(vault);
|
||||||
|
}
|
||||||
|
return vault;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Map<String, Object> asMap() {
|
||||||
|
return getThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void put(String key, Object value) {
|
||||||
|
getThread().put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String replaceVarsIfPresent(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return VariableUtil.replaceVars(text, asMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T getValue(String key) {
|
||||||
|
return (T) getThread().get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T remove(String key) {
|
||||||
|
return (T) getThread().remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/main/java/ru/lanit/at/utils/DataGenerator.java
Normal file
56
src/main/java/ru/lanit/at/utils/DataGenerator.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
||||||
|
import static org.apache.commons.lang3.RandomStringUtils.randomNumeric;
|
||||||
|
|
||||||
|
public class DataGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерирует набор русских, английских букв и цифр по маске
|
||||||
|
* <p>
|
||||||
|
* <br/> R - русская буква
|
||||||
|
* <br/> D - цифра
|
||||||
|
* <br/> E - английская буква
|
||||||
|
*
|
||||||
|
* @return - рандомная строка
|
||||||
|
*/
|
||||||
|
public static String generateValueByMask(String mask) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
char[] chars = mask.toCharArray();
|
||||||
|
for (char aChar : chars) {
|
||||||
|
switch (String.valueOf(aChar)) {
|
||||||
|
case "R":
|
||||||
|
result.append(getRussianLetter());
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
result.append(randomNumeric(1));
|
||||||
|
break;
|
||||||
|
case "E":
|
||||||
|
result.append(randomAlphabetic(1).toLowerCase());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result.append(aChar);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.trimToSize();
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* рандомная русская буква
|
||||||
|
*
|
||||||
|
* @return рандомная русская буква
|
||||||
|
*/
|
||||||
|
private static String getRussianLetter() {
|
||||||
|
int leftLimit = 1040;
|
||||||
|
int rightLimit = leftLimit + 33;
|
||||||
|
String res = "";
|
||||||
|
int a = ThreadLocalRandom.current().nextInt(leftLimit, rightLimit + 1);
|
||||||
|
char symbol = (char) a;
|
||||||
|
res += symbol;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/main/java/ru/lanit/at/utils/ErrorMessage.java
Normal file
10
src/main/java/ru/lanit/at/utils/ErrorMessage.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
public class ErrorMessage {
|
||||||
|
|
||||||
|
public static final String BROWSER_NOT_SUPPORTED = "Браузер [%s] не поддерживается";
|
||||||
|
public static final String BROWSER_TAB_NUMBER_MORE_THAN_TABS = "Номер [%d] переданной вкладки больше, чем количество вкладок";
|
||||||
|
public static final String URL_NOT_EQUAL_ACTUAL = "Ожидаемый url [%s] не равен текущему [%s]";
|
||||||
|
public static final String URL_NOT_CONTAINS_TEXT = "Текущий url [%s] не содержит в себе [%s]";
|
||||||
|
|
||||||
|
}
|
||||||
95
src/main/java/ru/lanit/at/utils/FileUtil.java
Normal file
95
src/main/java/ru/lanit/at/utils/FileUtil.java
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileSystemNotFoundException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class FileUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* поиск файла по заданному пути и имени файла
|
||||||
|
* <br/>если нет ни одного, то ошибка. если более одного, то тоже ошибка
|
||||||
|
*
|
||||||
|
* @param path путь
|
||||||
|
* @param fileName наименование файла
|
||||||
|
* @return возвращает один файл
|
||||||
|
*/
|
||||||
|
public static File searchFileInDirectory(String path, String fileName) {
|
||||||
|
List<Path> paths;
|
||||||
|
try {
|
||||||
|
paths = Files.find(Paths.get(path),
|
||||||
|
Integer.MAX_VALUE,
|
||||||
|
(path1, basicFileAttributes) -> path1.toFile().getName().equals(fileName))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new FileSystemNotFoundException("Файл не найден");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paths.size() > 1) {
|
||||||
|
throw new AssertionError("В каталоге более 1 файла с именем " + fileName);
|
||||||
|
} else if (paths.size() == 0) {
|
||||||
|
throw new FileSystemNotFoundException("Файл не найден");
|
||||||
|
} else {
|
||||||
|
return paths.get(0).toFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* метод для составления полного пути до директории или файла
|
||||||
|
*
|
||||||
|
* @param packages массив наименований папок или файла (в конце)
|
||||||
|
* @return полный путь до директории или файла
|
||||||
|
*/
|
||||||
|
public static StringBuilder getParentPath(String... packages) {
|
||||||
|
StringBuilder pathBuilder = new StringBuilder();
|
||||||
|
String separator = System.getProperty("file.separator");
|
||||||
|
if (!System.getProperty("project.dir", "").isEmpty()) {
|
||||||
|
pathBuilder.append(System.getProperty("project.dir"));
|
||||||
|
} else {
|
||||||
|
pathBuilder.append(System.getProperty("user.dir"));
|
||||||
|
}
|
||||||
|
for (String packageName : packages) {
|
||||||
|
if (pathBuilder.toString().endsWith(separator)) {
|
||||||
|
pathBuilder.append(packageName);
|
||||||
|
} else {
|
||||||
|
pathBuilder.append(separator).append(packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Считывает и возвращает содержимый текст файла
|
||||||
|
*
|
||||||
|
* @param fileName - название файла
|
||||||
|
* @param packages - директории, в которых лежит файл
|
||||||
|
* @return - содержимый текст файла
|
||||||
|
*/
|
||||||
|
private static String readBodyFromFile(String fileName, String... packages) {
|
||||||
|
File file = searchFileInDirectory(getParentPath(packages).toString(), fileName);
|
||||||
|
try {
|
||||||
|
return new String(Files.readAllBytes(Paths.get(file.getAbsolutePath())));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Считывает json-файл из пакеты по пути: "resources", "json"
|
||||||
|
* Путь часто используется, поэтому вынесен в отдельным метод
|
||||||
|
*
|
||||||
|
* @param fileName - название файла
|
||||||
|
* @return - содержание файла
|
||||||
|
*/
|
||||||
|
public static String readBodyFromJsonDir(String fileName) {
|
||||||
|
return readBodyFromFile(fileName, "src", "test", "resources", "json");
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/main/java/ru/lanit/at/utils/JsonUtil.java
Normal file
58
src/main/java/ru/lanit/at/utils/JsonUtil.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.jayway.jsonpath.Configuration;
|
||||||
|
import com.jayway.jsonpath.InvalidJsonException;
|
||||||
|
import com.jayway.jsonpath.JsonPath;
|
||||||
|
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
|
||||||
|
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
|
||||||
|
import io.qameta.allure.Allure;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
|
||||||
|
public class JsonUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* парсит к utf-8
|
||||||
|
*
|
||||||
|
* @param text текст
|
||||||
|
* @return текст в utf-8 (насколько это возможно)
|
||||||
|
*/
|
||||||
|
public static String jsonToUtf(String text) {
|
||||||
|
return new String(text.getBytes(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* извлекает данные по json path из json
|
||||||
|
*
|
||||||
|
* @param body json
|
||||||
|
* @param jsonPath json path
|
||||||
|
* @return значение из json
|
||||||
|
*/
|
||||||
|
public static String getFieldFromJson(String body, String jsonPath) {
|
||||||
|
Configuration jacksonConfig = Configuration.builder()
|
||||||
|
.mappingProvider(new JacksonMappingProvider())
|
||||||
|
.jsonProvider(new JacksonJsonProvider())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String val;
|
||||||
|
JsonNode node;
|
||||||
|
try {
|
||||||
|
node = JsonPath.using(jacksonConfig).parse(body).read(jsonPath, JsonNode.class);
|
||||||
|
} catch (InvalidJsonException e) {
|
||||||
|
Allure.addAttachment("INVALID JSON", "application/json", body, ".txt");
|
||||||
|
throw new InvalidJsonException("Невалидный json.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null || node.isNull()) {
|
||||||
|
val = "null";
|
||||||
|
} else {
|
||||||
|
val = node.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String matchValue = RegexUtil.getMatchValueByGroupNumber(val, "^\"(.*)\"$", 1);
|
||||||
|
val = matchValue == null ? val : matchValue;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/main/java/ru/lanit/at/utils/RegexUtil.java
Normal file
44
src/main/java/ru/lanit/at/utils/RegexUtil.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class RegexUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* получение значения из текста по регулярному выражению
|
||||||
|
*
|
||||||
|
* @param sentence текст
|
||||||
|
* @param regex регулярное выражение
|
||||||
|
* @param groupNumber номер группы
|
||||||
|
* @return значения из текста по регулярному выражению; если не нашел, то null
|
||||||
|
*/
|
||||||
|
public static String getMatchValueByGroupNumber(String sentence, String regex, int groupNumber) {
|
||||||
|
if (sentence != null && regex != null) {
|
||||||
|
sentence = sentence.trim();
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(sentence);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(groupNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* есть совпадение в тексте по регулярному выражению
|
||||||
|
*
|
||||||
|
* @param sentence текст
|
||||||
|
* @param regex регулярное выражение
|
||||||
|
* @return true - есть совпадение; false - нет совпадения
|
||||||
|
*/
|
||||||
|
public static Boolean getMatch(String sentence, String regex) {
|
||||||
|
if (sentence != null && regex != null) {
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(sentence);
|
||||||
|
return matcher.find();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
26
src/main/java/ru/lanit/at/utils/Sleep.java
Normal file
26
src/main/java/ru/lanit/at/utils/Sleep.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class Sleep {
|
||||||
|
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(Sleep.class);
|
||||||
|
|
||||||
|
public static void pauseSec(int sec) {
|
||||||
|
LOGGER.info("Ожидание {} секунд", sec);
|
||||||
|
sleep(sec * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sleep(long ms) {
|
||||||
|
try {
|
||||||
|
LOGGER.info("Ожидание {} миллисекунд", ms);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(ms);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.warn("Interrupted!", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/ru/lanit/at/utils/Stand.java
Normal file
27
src/main/java/ru/lanit/at/utils/Stand.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Адреса стендов
|
||||||
|
*/
|
||||||
|
public enum Stand {
|
||||||
|
GOOGLE("www.google.ru"),
|
||||||
|
YANDEX("ya.ru");
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
Stand(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stand getByName(String value) {
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(stand -> stand.name().equalsIgnoreCase(value)).findFirst().orElseThrow(() -> new RuntimeException("Не определен подходящий стенд с именем " + value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrlPath() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
76
src/main/java/ru/lanit/at/utils/VariableUtil.java
Normal file
76
src/main/java/ru/lanit/at/utils/VariableUtil.java
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class VariableUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* замена контекстных переменных на значения
|
||||||
|
*
|
||||||
|
* @param preBody текст
|
||||||
|
* @param vars контекстные переменные
|
||||||
|
* @return значение
|
||||||
|
*/
|
||||||
|
public static String replaceVars(String preBody, Map<String, Object> vars) {
|
||||||
|
StringBuilder replacedText = new StringBuilder(preBody);
|
||||||
|
|
||||||
|
final String patternStartVar = "${";
|
||||||
|
final String patternEndVar = "}";
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int fi = replacedText.indexOf(patternStartVar);
|
||||||
|
if (fi == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int li = replacedText.indexOf(patternEndVar, fi);
|
||||||
|
String var = replacedText.substring(fi + patternStartVar.length(), li);
|
||||||
|
if (vars.containsKey(var)) {
|
||||||
|
replacedText.replace(fi, li + 1, vars.get(var).toString());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return replacedText.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* убирает лишние символы (обычно для json)
|
||||||
|
*
|
||||||
|
* @param in текст
|
||||||
|
* @return текст
|
||||||
|
*/
|
||||||
|
public static String extractBrackets(Object in) {
|
||||||
|
if (in == null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
String op = in.toString();
|
||||||
|
String replace;
|
||||||
|
String replace2;
|
||||||
|
if (op.startsWith("[") && op.endsWith("]") && !op.contains(",")) {
|
||||||
|
replace = StringUtils.replace(StringUtils.replace(op, "[", ""), "]", "");
|
||||||
|
if (StringUtils.startsWith(replace, "\"") && StringUtils.endsWith(replace, "\"")) {
|
||||||
|
replace2 = StringUtils.replace(replace, "\"", "");
|
||||||
|
try {
|
||||||
|
if (replace2.matches("^[0-9]+$")) {
|
||||||
|
new BigDecimal(replace2);
|
||||||
|
}
|
||||||
|
return replace2;
|
||||||
|
} catch (NumberFormatException n) {
|
||||||
|
return replace;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return replace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (op.startsWith("[[") && op.endsWith("]]") || op.startsWith("[") && op.endsWith("]")) {
|
||||||
|
op = op.replaceFirst("\\[", "");
|
||||||
|
int lastIndex = op.lastIndexOf("]");
|
||||||
|
return op.substring(0, lastIndex);
|
||||||
|
}
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/main/java/ru/lanit/at/utils/VideoSaveHelper.java
Normal file
78
src/main/java/ru/lanit/at/utils/VideoSaveHelper.java
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package ru.lanit.at.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Selenide;
|
||||||
|
import io.restassured.RestAssured;
|
||||||
|
import io.restassured.response.Response;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import ru.lanit.at.utils.allure.AllureHelper;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static io.restassured.RestAssured.given;
|
||||||
|
|
||||||
|
public class VideoSaveHelper {
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(VideoSaveHelper.class);
|
||||||
|
|
||||||
|
private final String sessionId;
|
||||||
|
private final String hubUrl;
|
||||||
|
|
||||||
|
public VideoSaveHelper(String sessionId, String hubUrl) {
|
||||||
|
this.sessionId = sessionId;
|
||||||
|
this.hubUrl = hubUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод для прикрепления видео к Аллюр отчету
|
||||||
|
*/
|
||||||
|
public void attachVideoFileRest() {
|
||||||
|
waitVideoFileDone();
|
||||||
|
Response response = RestAssured.given().when()
|
||||||
|
.get(buildVideoURL(sessionId));
|
||||||
|
AllureHelper.attachSelenoidVideo("Video", response
|
||||||
|
.then().extract().asInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод для удаления видео из селенойда.
|
||||||
|
*/
|
||||||
|
public void deleteSelenoidVideoRest() {
|
||||||
|
try {
|
||||||
|
given().delete(buildVideoURL(sessionId)).then().statusCode(200);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.warn("Произошла ошибка при удалении видео: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод для построения урл с видео файлом
|
||||||
|
*/
|
||||||
|
private URL buildVideoURL(String sessionId) {
|
||||||
|
try {
|
||||||
|
URI uri = new URI("http", hubUrl, "/video/" + sessionId + ".mp4", null, null);
|
||||||
|
URL url = uri.toURL();
|
||||||
|
LOGGER.info("URL для скачивания файла с видео = " + url);
|
||||||
|
return url;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Не удалось создать url для скачивания видео", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ожидание формирования видео в селенойде
|
||||||
|
*/
|
||||||
|
private void waitVideoFileDone() {
|
||||||
|
for (int i = 0; i < 60; i++) {
|
||||||
|
if (RestAssured.given().when()
|
||||||
|
.get(buildVideoURL(sessionId)).statusCode() == 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOGGER.info("Ожидание подготовки видео файла для скачивания");
|
||||||
|
Selenide.sleep(5000);
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Ошибка ожидания подготовки видео файла для скачивания");
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/main/java/ru/lanit/at/utils/allure/AllureHelper.java
Normal file
92
src/main/java/ru/lanit/at/utils/allure/AllureHelper.java
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package ru.lanit.at.utils.allure;
|
||||||
|
|
||||||
|
|
||||||
|
import io.qameta.allure.Allure;
|
||||||
|
import io.qameta.allure.AllureLifecycle;
|
||||||
|
import io.qameta.allure.model.Status;
|
||||||
|
import io.qameta.allure.model.StepResult;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import ru.lanit.at.utils.Sleep;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static io.qameta.allure.Allure.getLifecycle;
|
||||||
|
|
||||||
|
public class AllureHelper {
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(Sleep.class);
|
||||||
|
private static ThreadLocal<AllureLifecycle> threadLocal = new ThreadLocal<>();
|
||||||
|
|
||||||
|
/* Установка AllureLifecycle для потока выполнения, используется при запуске фреймворка, как веб сервиса **/
|
||||||
|
public static void setLifecycle(AllureLifecycle allureLifecycle) {
|
||||||
|
threadLocal.set(allureLifecycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void attachScreenShot(String name, byte[] bytes) {
|
||||||
|
get().addAttachment(name, "image/png", "png", bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void attachTxt(String name, String text) {
|
||||||
|
get().addAttachment(name, "text/plain", "txt", text.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void attachPageSource(byte[] bytes) {
|
||||||
|
get().addAttachment("Page source", "text/html", "html", bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setStepStatusBroken(String description) {
|
||||||
|
get().updateStep(stepResult -> stepResult.setDescription(description));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addInfoAllureStep(String stepName) {
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
createAllureStep(uuid, stepName, Status.PASSED);
|
||||||
|
LOGGER.info("выполняется шаг : \"" + stepName + "\"");
|
||||||
|
stopAllureStep(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addInfoAllureStep(String uuid, String stepName) {
|
||||||
|
createAllureStep(uuid, stepName, Status.PASSED);
|
||||||
|
LOGGER.info("выполняется шаг : \"" + stepName + "\"");
|
||||||
|
stopAllureStep(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addBrokenAllureStep(String stepName, String textMessage) {
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
stepName = stepName + " [" + textMessage + "]";
|
||||||
|
createAllureStep(uuid, stepName, Status.BROKEN);
|
||||||
|
stopAllureStep(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void createAllureStep(String parentUuid, String uuid, String stepName, Status status) {
|
||||||
|
LOGGER.info("Создание аллюр шага с именем :" + stepName);
|
||||||
|
StepResult stepResult = new StepResult();
|
||||||
|
stepResult.setName(stepName)
|
||||||
|
.setStatus(status);
|
||||||
|
get().startStep(parentUuid, uuid, stepResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createAllureStep(String uuid, String stepName, Status status) {
|
||||||
|
LOGGER.info("Создание аллюр шага с именем :" + stepName);
|
||||||
|
StepResult stepResult = new StepResult();
|
||||||
|
stepResult.setName(stepName)
|
||||||
|
.setStatus(status);
|
||||||
|
get().startStep(uuid, stepResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopAllureStep(String uuid) {
|
||||||
|
get().stopStep(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void attachSelenoidVideo(String name, InputStream is) {
|
||||||
|
Allure.getLifecycle().addAttachment(name, "video/mp4", "mp4", is);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static AllureLifecycle get() {
|
||||||
|
return getLifecycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/main/java/ru/lanit/at/utils/allure/AllureLogger.java
Normal file
125
src/main/java/ru/lanit/at/utils/allure/AllureLogger.java
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package ru.lanit.at.utils.allure;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
import io.qameta.allure.Allure;
|
||||||
|
import io.qameta.allure.listener.StepLifecycleListener;
|
||||||
|
import io.qameta.allure.listener.TestLifecycleListener;
|
||||||
|
import io.qameta.allure.model.Status;
|
||||||
|
import io.qameta.allure.model.StepResult;
|
||||||
|
import io.qameta.allure.model.TestResult;
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import org.openqa.selenium.OutputType;
|
||||||
|
import org.openqa.selenium.TakesScreenshot;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import ru.lanit.at.utils.web.properties.Configurations;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static ru.lanit.at.assertion.AssertErrorType.CRITICAL_ASSERT;
|
||||||
|
import static ru.lanit.at.assertion.AssertErrorType.SOFT_ASSERT;
|
||||||
|
|
||||||
|
//import ru.lanit.at.utils.web.properties.Configurations;
|
||||||
|
|
||||||
|
public class AllureLogger implements StepLifecycleListener, TestLifecycleListener {
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(AllureLogger.class);
|
||||||
|
|
||||||
|
private final Configurations conf = ConfigFactory.create(Configurations.class, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeStepStop(StepResult result) {
|
||||||
|
boolean screenAfterStep = conf.screenAfterStep();
|
||||||
|
if (screenAfterStep && !result.getStatus().equals(Status.SKIPPED)) {
|
||||||
|
Allure.addAttachment(result.getName(),
|
||||||
|
new ByteArrayInputStream(((TakesScreenshot)
|
||||||
|
WebDriverRunner.getWebDriver())
|
||||||
|
.getScreenshotAs(OutputType.BYTES)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterStepStop(StepResult result) {
|
||||||
|
if (!result.getStatus().equals(Status.SKIPPED)) {
|
||||||
|
if (isHasInnerBrokenStep(result)) {
|
||||||
|
LOGGER.info("Устанавливаем для шага: '" + result.getName() + "', статус=BROKEN");
|
||||||
|
result.setStatus(Status.BROKEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (result.getDescription() != null && (result.getDescription().contains(SOFT_ASSERT.getName()) || result.getDescription().contains(CRITICAL_ASSERT.getName()))) {
|
||||||
|
LOGGER.info("Устанавливаем для шага: '" + result.getName() + "', статус=BROKEN");
|
||||||
|
result.setStatus(Status.BROKEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHasInnerBrokenStep(StepResult result) {
|
||||||
|
for (StepResult r : result.getSteps()) {
|
||||||
|
if (r.getStatus().equals(Status.BROKEN)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ищет шаги-заглушки ФРАГМЕНТ "название фрагмента" и прячет шаги фрагмента под спойлер
|
||||||
|
* @param testResult - результаты тестов
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void beforeTestWrite(TestResult testResult) {
|
||||||
|
List<StepResult> originSteps = testResult.getSteps();
|
||||||
|
List<StepResult> newSteps = wrapFragment(originSteps);
|
||||||
|
testResult.setSteps(newSteps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод прячет все шаги фрагмента под спойлер
|
||||||
|
* @param originSteps - оригинальный набор шагов для отчета
|
||||||
|
* @return - переопределенные шаги отчета
|
||||||
|
*/
|
||||||
|
private List<StepResult> wrapFragment(List<StepResult> originSteps) {
|
||||||
|
List<StepResult> newSteps = new ArrayList<>();
|
||||||
|
for (int i=0; i < originSteps.size(); i++) {
|
||||||
|
StepResult step = originSteps.get(i);
|
||||||
|
if (step.getName().contains("ФРАГМЕНТ") && i+1 < originSteps.size()) {
|
||||||
|
i++;
|
||||||
|
long timeStop = step.getStop();
|
||||||
|
List<StepResult> subSteps = new ArrayList<>();
|
||||||
|
List<Status> statusList = new ArrayList<>();
|
||||||
|
//идем дальше по тесту и собираем все шаги фрагмента в отдельный список
|
||||||
|
do {
|
||||||
|
StepResult subStep = originSteps.get(i);
|
||||||
|
if (subStep.getName().contains(step.getName())) {
|
||||||
|
timeStop = subStep.getStop();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
subSteps.add(subStep);
|
||||||
|
statusList.add(subStep.getStatus());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} while (i < originSteps.size());
|
||||||
|
//если есть фрагменты внутри фрагмента
|
||||||
|
subSteps = wrapFragment(subSteps);
|
||||||
|
//таки все шаги фрагмента уйдут под спойлер (как саб-шаги)
|
||||||
|
step.setSteps(subSteps);
|
||||||
|
//ставим изменяем время конца шага, чтобы была сумма всех саб-шагов
|
||||||
|
step.setStop(timeStop);
|
||||||
|
//меняем статус шагу-фрагменту, если статус у одного из шагов не passed или не skipped
|
||||||
|
if (statusList.contains(Status.FAILED)) {
|
||||||
|
step.setStatus(Status.FAILED);
|
||||||
|
} else if (statusList.contains(Status.BROKEN)) {
|
||||||
|
step.setStatus(Status.BROKEN);
|
||||||
|
}
|
||||||
|
newSteps.add(step);
|
||||||
|
} else {
|
||||||
|
newSteps.add(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package ru.lanit.at.utils.reflections;
|
||||||
|
|
||||||
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class ReflectionUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение списка классов по аннотации
|
||||||
|
*/
|
||||||
|
public static Set<Class<?>> getPagesAnnotatedWith(String packageName, Class<? extends Annotation> annotation) {
|
||||||
|
return new Reflections(packageName).getTypesAnnotatedWith(annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение поля класса с помощью механизма рефлексии
|
||||||
|
*/
|
||||||
|
public static Object extractFieldValue(Field field, Object owner) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
return field.get(owner);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
field.setAccessible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/main/java/ru/lanit/at/utils/selenide/DriverManager.java
Normal file
94
src/main/java/ru/lanit/at/utils/selenide/DriverManager.java
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package ru.lanit.at.utils.selenide;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Configuration;
|
||||||
|
import com.codeborne.selenide.FileDownloadMode;
|
||||||
|
import com.codeborne.selenide.Selenide;
|
||||||
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
|
import com.codeborne.selenide.logevents.SelenideLogger;
|
||||||
|
import io.github.bonigarcia.wdm.WebDriverManager;
|
||||||
|
import io.qameta.allure.selenide.AllureSelenide;
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import ru.lanit.at.utils.ErrorMessage;
|
||||||
|
import ru.lanit.at.utils.Stand;
|
||||||
|
import ru.lanit.at.utils.web.pagecontext.Environment;
|
||||||
|
import ru.lanit.at.utils.web.properties.Configurations;
|
||||||
|
import ru.lanit.at.utils.web.properties.WebConfigurations;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
||||||
|
public class DriverManager {
|
||||||
|
|
||||||
|
public static void startDriver() {
|
||||||
|
if (!WebDriverRunner.hasWebDriverStarted()) {
|
||||||
|
createDriver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void createDriver() {
|
||||||
|
Configurations cf = ConfigFactory.create(Configurations.class
|
||||||
|
, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
|
||||||
|
|
||||||
|
WebConfigurations cfg = ConfigFactory.create(WebConfigurations.class
|
||||||
|
, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
|
||||||
|
SelenideLogger.addListener("AllureSelenide", new AllureSelenide()
|
||||||
|
.screenshots(true)
|
||||||
|
.savePageSource(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cf.getRemoteURL().isEmpty()) {
|
||||||
|
Configuration.remote = "http://" + cf.getRemoteURL() + "/wd/hub";
|
||||||
|
Map<String, Boolean> options = new HashMap<>();
|
||||||
|
options.put("enableVNC", cf.getEnableVNC());
|
||||||
|
options.put("enableVideo", cf.getEnableVideo());
|
||||||
|
options.put("enableLog", cf.getEnableLog());
|
||||||
|
Configuration.browserCapabilities.setCapability("selenoid:options", options);
|
||||||
|
Configuration.browserCapabilities.setCapability("sessionTimeout", "30m");
|
||||||
|
Configuration.fileDownload = FileDownloadMode.FOLDER;
|
||||||
|
} else {
|
||||||
|
switch (cfg.webDriverBrowserName()) {
|
||||||
|
case "chrome":
|
||||||
|
WebDriverManager.chromedriver().setup();
|
||||||
|
break;
|
||||||
|
case "firefox":
|
||||||
|
WebDriverManager.firefoxdriver().setup();
|
||||||
|
break;
|
||||||
|
case "edge":
|
||||||
|
WebDriverManager.edgedriver().setup();
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException(String.format(ErrorMessage.BROWSER_NOT_SUPPORTED, cfg.webDriverBrowserName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration.browser = cfg.webDriverBrowserName();
|
||||||
|
Configuration.browserSize = cfg.webDriverBrowserSize();
|
||||||
|
Configuration.browserVersion = cfg.webDriverVersion();
|
||||||
|
Configuration.savePageSource = true;
|
||||||
|
Configuration.screenshots = true;
|
||||||
|
Configuration.webdriverLogsEnabled = false;
|
||||||
|
Configuration.timeout = TimeUnit.SECONDS.toMillis(cfg.webDriverTimeoutSeconds());
|
||||||
|
Configuration.pageLoadTimeout = TimeUnit.SECONDS.toMillis(cfg.pageLoadTimeoutSeconds());
|
||||||
|
Configuration.pollingInterval = cfg.pollingTimeoutMs();
|
||||||
|
Configuration.reportsFolder = System.getProperty("selenide.report.folder");
|
||||||
|
Configuration.downloadsFolder = System.getProperty("selenide.download.folder");
|
||||||
|
Environment.initPages(cfg.pagesPackage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void startApp() {
|
||||||
|
Configurations conf = ConfigFactory.create(Configurations.class, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
String standParam = conf.getStand();
|
||||||
|
Stand stand = Stand.getByName(standParam);
|
||||||
|
Selenide.open("https://" + stand.getUrlPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package ru.lanit.at.utils.selenide.command;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Command;
|
||||||
|
import com.codeborne.selenide.Condition;
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import com.codeborne.selenide.ex.ElementShould;
|
||||||
|
import com.codeborne.selenide.ex.ElementShouldNot;
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import ru.lanit.at.assertion.AssertsManager;
|
||||||
|
import ru.lanit.at.utils.web.properties.Configurations;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Кастомные команды selenide элемента
|
||||||
|
*/
|
||||||
|
public class Commands {
|
||||||
|
|
||||||
|
public static final Configurations conf = ConfigFactory.create(Configurations.class, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
|
||||||
|
private Commands() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Мягкая проверка Conditions element
|
||||||
|
*
|
||||||
|
* @param condition - тип проверки
|
||||||
|
* @param duration - таймаут ожидания (может быть null, в таком случае будет использоваться параметр из конфига webdriver.timeoutSeconds )
|
||||||
|
* @return - proxy элемент
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static Command<SelenideElement> checkSoft(Condition condition, Duration duration) {
|
||||||
|
return (proxy, locator, args) -> {
|
||||||
|
try {
|
||||||
|
return proxy.should(condition, duration);
|
||||||
|
} catch (ElementShould | ElementShouldNot ex) {
|
||||||
|
AssertsManager.getAssertsManager().softAssert().fail(ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
return proxy;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package ru.lanit.at.utils.selenide.extensions;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.commands.Commands;
|
||||||
|
import com.codeborne.selenide.impl.WebElementSource;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** Класс для переопределения и добавление методов Selenide элемента */
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
public class CustomCommands
|
||||||
|
extends Commands {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public <T> T execute(Object proxy, WebElementSource webElementSource, String methodName, @Nullable Object[] args) throws IOException {
|
||||||
|
return super.execute(proxy, webElementSource, methodName, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package ru.lanit.at.utils.selenide.extensions;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.impl.SelenidePageFactory;
|
||||||
|
|
||||||
|
/** Класс для переопределения стандартной фабрики иницализации Page объектов */
|
||||||
|
public class CustomSelenidePageFactory extends SelenidePageFactory {
|
||||||
|
}
|
||||||
15
src/main/java/ru/lanit/at/utils/web/annotations/Name.java
Normal file
15
src/main/java/ru/lanit/at/utils/web/annotations/Name.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package ru.lanit.at.utils.web.annotations;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Аннотация для элементов страницы, служащая для их индентификации в cucumber-сценариях
|
||||||
|
*/
|
||||||
|
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Name {
|
||||||
|
String value();
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package ru.lanit.at.utils.web.pagecontext;
|
||||||
|
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Используется для хранения кеша страниц и драйвера
|
||||||
|
*/
|
||||||
|
public class Environment {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(Environment.class);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Список веб-страниц, заданных пользователем, доступных для использования в сценарии
|
||||||
|
*/
|
||||||
|
private static PageCache pages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод ищет классы, аннотированные "ru.lanit.ru.lanit.at.utils.web.annotations.Name",
|
||||||
|
* добавляя ссылки на эти классы в поле "pages"
|
||||||
|
*
|
||||||
|
* @param packageName наименование пакета где лежат файлы с описанием страниц
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static void initPages(String packageName) {
|
||||||
|
pages = new PageCache();
|
||||||
|
ru.lanit.at.utils.reflections.ReflectionUtil
|
||||||
|
.getPagesAnnotatedWith(packageName, ru.lanit.at.utils.web.annotations.Name.class)
|
||||||
|
.stream()
|
||||||
|
.map(it -> {
|
||||||
|
if (WebPage.class.isAssignableFrom(it)) {
|
||||||
|
return (Class<? extends WebPage>) it;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Класс " + it.getName() + " должен наследоваться от WebPage");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.forEach(clazz -> pages.put(getClassAnnotationValue(clazz), clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вспомогательный метод, получает значение аннотации "ru.absolut.annotations.Name" для класса
|
||||||
|
*/
|
||||||
|
private static String getClassAnnotationValue(Class<?> c) {
|
||||||
|
return Arrays
|
||||||
|
.stream(c.getAnnotationsByType(ru.lanit.at.utils.web.annotations.Name.class))
|
||||||
|
.findFirst()
|
||||||
|
.map(ru.lanit.at.utils.web.annotations.Name::value)
|
||||||
|
.orElseThrow(() -> new AssertionError("Не найдены аннотации Page.Name в классe " + c.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebPage getPage(String name) {
|
||||||
|
return pages.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebPage getPage(Class<?> c) {
|
||||||
|
return getPage(getClassAnnotationValue(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
package ru.lanit.at.utils.web.pagecontext;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.Selenide;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Предназначен для хранения страниц, используемых при прогоне тестов
|
||||||
|
*/
|
||||||
|
public final class PageCache {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Страницы, на которых будет производится тестирование < Имя, Страница >
|
||||||
|
*/
|
||||||
|
private Map<String, Class<? extends WebPage>> pages;
|
||||||
|
|
||||||
|
public PageCache() {
|
||||||
|
pages = Maps.newHashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение страницы из "pages" по имени
|
||||||
|
*/
|
||||||
|
public WebPage get(String pageName) {
|
||||||
|
return Selenide.page(getPageFromPagesByName(pageName)).initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение страницы по классу
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends WebPage> T get(Class<T> clazz, String name) {
|
||||||
|
WebPage webPage = Selenide.page(getPageFromPagesByName(name)).initialize();
|
||||||
|
if (!clazz.isInstance(webPage)) {
|
||||||
|
throw new IllegalStateException(name + " page is not a instance of " + clazz + ". Named page is a " + webPage);
|
||||||
|
}
|
||||||
|
return (T) webPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Class<? extends WebPage>> getPageMapInstanceInternal() {
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<? extends WebPage> getPageFromPagesByName(String pageName) throws IllegalArgumentException {
|
||||||
|
Class<? extends WebPage> page = getPageMapInstanceInternal().get(pageName);
|
||||||
|
if (page == null) {
|
||||||
|
throw new IllegalArgumentException("Страница с именем '" + pageName + "' не задекларирована");
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(String pageName, Class<? extends WebPage> page) {
|
||||||
|
if (page == null) {
|
||||||
|
throw new IllegalArgumentException("Была передана пустая страница");
|
||||||
|
}
|
||||||
|
pages.put(pageName, page);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package ru.lanit.at.utils.web.pagecontext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс, который хранит текущую страницу теста
|
||||||
|
*/
|
||||||
|
public class PageManager {
|
||||||
|
|
||||||
|
private WebPage currentPage;
|
||||||
|
|
||||||
|
public PageManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает текущую страницу, на которой в текущий момент производится тестирование
|
||||||
|
*/
|
||||||
|
public WebPage getCurrentPage() {
|
||||||
|
if (currentPage == null) {
|
||||||
|
throw new IllegalStateException("Текущая страница не задана");
|
||||||
|
}
|
||||||
|
return currentPage.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Задает текущую страницу по ее имени
|
||||||
|
*/
|
||||||
|
public void setCurrentPage(WebPage webPage) {
|
||||||
|
this.currentPage = webPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
126
src/main/java/ru/lanit/at/utils/web/pagecontext/WebPage.java
Normal file
126
src/main/java/ru/lanit/at/utils/web/pagecontext/WebPage.java
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package ru.lanit.at.utils.web.pagecontext;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.ElementsCollection;
|
||||||
|
import com.codeborne.selenide.SelenideElement;
|
||||||
|
import org.aeonbits.owner.ConfigFactory;
|
||||||
|
import ru.lanit.at.utils.reflections.ReflectionUtil;
|
||||||
|
import ru.lanit.at.utils.web.annotations.Name;
|
||||||
|
import ru.lanit.at.utils.web.properties.Configurations;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static java.util.stream.Collectors.toMap;
|
||||||
|
|
||||||
|
public abstract class WebPage {
|
||||||
|
|
||||||
|
protected final Configurations configurations = ConfigFactory.create(Configurations.class, System.getProperties(),
|
||||||
|
System.getenv());
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Список всех элементов страницы
|
||||||
|
*/
|
||||||
|
private Map<String, Object> namedElements;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name Annotations.Name
|
||||||
|
* Возвращает объект SelenideElement по его имени (аннотированного "Annotations.Name")
|
||||||
|
*/
|
||||||
|
public SelenideElement getElement(String name) {
|
||||||
|
Object instance = namedElements.get(name);
|
||||||
|
if (instance != null && !(instance instanceof SelenideElement)) {
|
||||||
|
throw new ClassCastException(String.format("Элемент [%s] должен иметь тип 'SelenideElement'", name));
|
||||||
|
}
|
||||||
|
return (SelenideElement) Optional.ofNullable(namedElements.get(name))
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException(String.format("Элемент [%s] отсутствует в классе [%s]", name, this.getClass().getName())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param name Annotations.Name
|
||||||
|
* @return Возвращает объект ElementsCollection по его имени (аннотированного "Annotations.Name")
|
||||||
|
*/
|
||||||
|
public ElementsCollection getElementsCollection(String name) {
|
||||||
|
Object instance = namedElements.get(name);
|
||||||
|
if (instance != null && !(instance instanceof ElementsCollection)) {
|
||||||
|
throw new ClassCastException(String.format("Элемент [%s] должен иметь тип 'ElementsCollection'", name));
|
||||||
|
}
|
||||||
|
return (ElementsCollection) Optional.ofNullable(namedElements.get(name))
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException(String.format("Элемент [%s] отсутствует в классе [%s]", name, this.getClass().getName())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Возвращает значение аннотации @Name(...) текущей страницы
|
||||||
|
*/
|
||||||
|
public String name() {
|
||||||
|
return this
|
||||||
|
.getClass()
|
||||||
|
.getAnnotation(Name.class)
|
||||||
|
.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebPage initialize() {
|
||||||
|
namedElements = readNamedElements();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поиск и инициализации элементов страницы
|
||||||
|
*/
|
||||||
|
private Map<String, Object> readNamedElements() {
|
||||||
|
checkNamedAnnotations();
|
||||||
|
return Arrays.stream(getClass().getDeclaredFields())
|
||||||
|
.filter(f -> f.getDeclaredAnnotation(Name.class) != null)
|
||||||
|
.peek(this::checkFieldType)
|
||||||
|
.collect(toMap(f -> f.getDeclaredAnnotation(Name.class).value(), this::extractFieldValueViaReflection));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkFieldType(Field f) {
|
||||||
|
if (!SelenideElement.class.isAssignableFrom(f.getType())
|
||||||
|
&& !WebPage.class.isAssignableFrom(f.getType())
|
||||||
|
) {
|
||||||
|
this.checkCollectionFieldType(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCollectionFieldType(Field f) {
|
||||||
|
if (ElementsCollection.class.isAssignableFrom(f.getType())) {
|
||||||
|
return;
|
||||||
|
} else if (List.class.isAssignableFrom(f.getType())) {
|
||||||
|
ParameterizedType listType = (ParameterizedType) f.getGenericType();
|
||||||
|
Class<?> listClass = (Class<?>) listType.getActualTypeArguments()[0];
|
||||||
|
if (SelenideElement.class.isAssignableFrom(listClass) || WebPage.class.isAssignableFrom(listClass)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException(
|
||||||
|
format("Поле с аннотацией '@Name' должно иметь тип SelenideElement, List<SelenideElement> или ElementsCollection.\n" +
|
||||||
|
"Найдено поле с типом %s", f.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поиск по аннотации "Annotations.Name"
|
||||||
|
*/
|
||||||
|
private void checkNamedAnnotations() {
|
||||||
|
List<String> list = Arrays.stream(getClass().getDeclaredFields())
|
||||||
|
.filter(f -> f.getDeclaredAnnotation(Name.class) != null)
|
||||||
|
.map(f -> f.getDeclaredAnnotation(Name.class).value())
|
||||||
|
.collect(toList());
|
||||||
|
if (list.size() != new HashSet<>(list).size()) {
|
||||||
|
throw new IllegalStateException("Найдено несколько аннотаций '@Name' с одинаковым значением в классе " + this.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object extractFieldValueViaReflection(Field field) {
|
||||||
|
return ReflectionUtil.extractFieldValue(field, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package ru.lanit.at.utils.web.properties;
|
||||||
|
|
||||||
|
import org.aeonbits.owner.Config;
|
||||||
|
|
||||||
|
|
||||||
|
@Config.LoadPolicy(Config.LoadType.MERGE)
|
||||||
|
@Config.Sources({
|
||||||
|
"classpath:config/configuration.properties",
|
||||||
|
"system:properties",
|
||||||
|
"system:env"
|
||||||
|
})
|
||||||
|
public interface Configurations extends Config {
|
||||||
|
|
||||||
|
|
||||||
|
@Key("stand")
|
||||||
|
@DefaultValue("GOOGLE")
|
||||||
|
String getStand();
|
||||||
|
|
||||||
|
|
||||||
|
@Key("screen_after_step")
|
||||||
|
@DefaultValue("false")
|
||||||
|
boolean screenAfterStep();
|
||||||
|
|
||||||
|
@Key("remoteUrl")
|
||||||
|
@DefaultValue("")
|
||||||
|
String getRemoteURL();
|
||||||
|
|
||||||
|
@Key("enableVNC")
|
||||||
|
@DefaultValue("false")
|
||||||
|
boolean getEnableVNC();
|
||||||
|
|
||||||
|
@Key("enableVideo")
|
||||||
|
@DefaultValue("false")
|
||||||
|
boolean getEnableVideo();
|
||||||
|
|
||||||
|
@Key("enableLog")
|
||||||
|
@DefaultValue("false")
|
||||||
|
boolean getEnableLog();
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package ru.lanit.at.utils.web.properties;
|
||||||
|
|
||||||
|
import org.aeonbits.owner.Config;
|
||||||
|
|
||||||
|
@Config.LoadPolicy(Config.LoadType.MERGE)
|
||||||
|
@Config.Sources({
|
||||||
|
"classpath:config/${browser}.properties",
|
||||||
|
"classpath:config/chrome.properties",
|
||||||
|
"system:properties",
|
||||||
|
"system:env"
|
||||||
|
})
|
||||||
|
public interface WebConfigurations extends Config {
|
||||||
|
|
||||||
|
@Key("webdriver.browser.size")
|
||||||
|
@DefaultValue("1920x1080")
|
||||||
|
String webDriverBrowserSize();
|
||||||
|
|
||||||
|
@Key("webdriver.version")
|
||||||
|
@DefaultValue("")
|
||||||
|
String webDriverVersion();
|
||||||
|
|
||||||
|
@Key("webdriver.browser.name")
|
||||||
|
@DefaultValue("")
|
||||||
|
String webDriverBrowserName();
|
||||||
|
|
||||||
|
@Key("webdriver.timeoutSeconds")
|
||||||
|
@DefaultValue("10")
|
||||||
|
int webDriverTimeoutSeconds();
|
||||||
|
|
||||||
|
|
||||||
|
@Key("webdriver.pageLoadTimeoutSeconds")
|
||||||
|
@DefaultValue("120")
|
||||||
|
int pageLoadTimeoutSeconds();
|
||||||
|
|
||||||
|
@Key("selenoid.enableVNC")
|
||||||
|
@DefaultValue("false")
|
||||||
|
boolean enableVnc();
|
||||||
|
|
||||||
|
@Key("polling.timeoutMs")
|
||||||
|
@DefaultValue("200")
|
||||||
|
int pollingTimeoutMs();
|
||||||
|
|
||||||
|
@Key("pages.package")
|
||||||
|
@DefaultValue("ru.lanit.at.pages")
|
||||||
|
String pagesPackage();
|
||||||
|
|
||||||
|
@Key("site_url")
|
||||||
|
@DefaultValue("")
|
||||||
|
String site_url();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
6
src/main/resources/META-INF/aop.xml
Normal file
6
src/main/resources/META-INF/aop.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<aspectj>
|
||||||
|
<aspects>
|
||||||
|
<aspect name="ru.lanit.at.aspects.FragmentsAspect"/>
|
||||||
|
</aspects>
|
||||||
|
</aspectj>
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ru.lanit.at.utils.selenide.extensions.CustomCommands
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ru.lanit.at.utils.selenide.extensions.CustomSelenidePageFactory
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ru.lanit.at.utils.allure.AllureLogger
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ru.lanit.at.utils.allure.AllureLogger
|
||||||
5
src/main/resources/log4j.properties
Normal file
5
src/main/resources/log4j.properties
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
log4j.rootLogger=INFO, stdout
|
||||||
|
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||||
|
log4j.appender.stdout.Target=System.out
|
||||||
|
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||||
|
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
||||||
22
src/test/java/Runner.java
Normal file
22
src/test/java/Runner.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import io.cucumber.testng.AbstractTestNGCucumberTests;
|
||||||
|
import io.cucumber.testng.CucumberOptions;
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
|
|
||||||
|
|
||||||
|
@CucumberOptions(
|
||||||
|
plugin = {
|
||||||
|
"pretty",
|
||||||
|
"io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm"
|
||||||
|
},
|
||||||
|
features = "classpath:features",
|
||||||
|
glue = {"ru.lanit.at.steps", "ru.lanit.at.hooks", "ru.lanit.at.corecommonstep"}
|
||||||
|
)
|
||||||
|
public class Runner extends AbstractTestNGCucumberTests {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@DataProvider(parallel = true)
|
||||||
|
|
||||||
|
public Object[][] scenarios() {
|
||||||
|
return super.scenarios();
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/test/resources/config/chrome.properties
Normal file
5
src/test/resources/config/chrome.properties
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
webdriver.browser.size=1920x1080
|
||||||
|
webdriver.browser.name=chrome
|
||||||
|
webdriver.timeoutSeconds=60
|
||||||
|
polling.timeoutMs=200
|
||||||
|
webdriver.version=99.0
|
||||||
10
src/test/resources/config/configuration.properties
Normal file
10
src/test/resources/config/configuration.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
stand=google
|
||||||
|
screen_after_step=false
|
||||||
|
baseUrl=https://petstore.swagger.io/v2/
|
||||||
|
|
||||||
|
## selenoid options
|
||||||
|
#remoteUrl=127.0.0.1:4444
|
||||||
|
remoteUrl=
|
||||||
|
enableVNC=true
|
||||||
|
enableVideo=true
|
||||||
|
enableLog=true
|
||||||
6
src/test/resources/config/edge.properties
Normal file
6
src/test/resources/config/edge.properties
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
webdriver.browser.size=1920x1080
|
||||||
|
webdriver.browser.name=edge
|
||||||
|
webdriver.timeoutSeconds=10
|
||||||
|
polling.timeoutMs=200
|
||||||
|
webdriver.version=91.0
|
||||||
|
|
||||||
6
src/test/resources/config/firefox.properties
Normal file
6
src/test/resources/config/firefox.properties
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
webdriver.browser.size=1920x1080
|
||||||
|
webdriver.browser.name=firefox
|
||||||
|
webdriver.timeoutSeconds=10
|
||||||
|
polling.timeoutMs=200
|
||||||
|
webdriver.version=98.0
|
||||||
|
|
||||||
1
src/test/resources/cucumber.properties
Normal file
1
src/test/resources/cucumber.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
cucumber.publish.quiet=true
|
||||||
25
src/test/resources/features/api/Books.feature
Normal file
25
src/test/resources/features/api/Books.feature
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#language:ru
|
||||||
|
@test
|
||||||
|
|
||||||
|
Функционал: Образец GET запроса
|
||||||
|
|
||||||
|
Сценарий: Запрос в bookstore к книге Just Spring Integration
|
||||||
|
|
||||||
|
Когда создать контекстные переменные
|
||||||
|
| book | Just Spring Integration |
|
||||||
|
|
||||||
|
И создать запрос
|
||||||
|
| method | url |
|
||||||
|
| GET | https://api.itbook.store/1.0/search/${book} |
|
||||||
|
Тогда отправить запрос
|
||||||
|
И статус код 200
|
||||||
|
|
||||||
|
Когда извлечь данные
|
||||||
|
| title | $.books[?(@.title=='${book}')].title |
|
||||||
|
| subtitle | $.books[?(@.title=='${book}')].subtitle |
|
||||||
|
| price | $.books[?(@.title=='${book}')].price |
|
||||||
|
|
||||||
|
И сравнить значения
|
||||||
|
| ${title} | == | Just Spring Integration |
|
||||||
|
| ${subtitle} | == | A Lightweight Introduction to Spring Integration |
|
||||||
|
| ${price} | содержит | 16.99 |
|
||||||
31
src/test/resources/features/api/Github.feature
Normal file
31
src/test/resources/features/api/Github.feature
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#language:ru
|
||||||
|
@test
|
||||||
|
|
||||||
|
Функционал: Selenide github test
|
||||||
|
- Выполнение запроса в репозиторий selenide
|
||||||
|
- Проверка что статус-код = 200
|
||||||
|
- Извлечение данных из тела ответа по jsonpath
|
||||||
|
- Проверка извлеченных данных
|
||||||
|
|
||||||
|
Сценарий: Выполнение GET запроса в репозиторий selenide
|
||||||
|
|
||||||
|
* создать запрос
|
||||||
|
| method | url |
|
||||||
|
| GET | https://api.github.com/orgs/selenide/repos |
|
||||||
|
* добавить header
|
||||||
|
| Accept | application/vnd.github.v3+json |
|
||||||
|
* отправить запрос
|
||||||
|
* статус код 200
|
||||||
|
* извлечь данные
|
||||||
|
| name | $[?(@.full_name=='selenide/selenide')].name |
|
||||||
|
| id | $[?(@.full_name=='selenide/selenide')].id |
|
||||||
|
| language | $[?(@.full_name=='selenide/selenide')].language |
|
||||||
|
| homepage | $[?(@.full_name=='selenide/selenide')].homepage |
|
||||||
|
| size | $[?(@.full_name=='selenide/selenide')].size |
|
||||||
|
|
||||||
|
* сравнить значения
|
||||||
|
| ${name} | == | selenide |
|
||||||
|
| ${id} | != | null |
|
||||||
|
| ${language} | == | Java |
|
||||||
|
| ${homepage} | == | http://selenide.org |
|
||||||
|
| ${size} | > | 0 |
|
||||||
56
src/test/resources/features/api/PetStore.feature
Normal file
56
src/test/resources/features/api/PetStore.feature
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#language:ru
|
||||||
|
@test
|
||||||
|
|
||||||
|
Функционал: Тестирование сервиса PetStore
|
||||||
|
- Создание нового юзера POST запросом с телом из json файла, значения которого заполняем сгенерированным значениями
|
||||||
|
- После создания нового юзера, GET запросом запрашиваем данного юзера и проверяем, что его данные соответствует данными из тела запроса
|
||||||
|
|
||||||
|
Сценарий: Создание юзера
|
||||||
|
|
||||||
|
|
||||||
|
# Первая часть теста - Создание юзера. Эти данные подставятся в тело запроса в шаблон тела файла createUser.json
|
||||||
|
# Генерится дандомная страка по маске
|
||||||
|
# E - Английская буква,
|
||||||
|
# R - русская буква,
|
||||||
|
# D - цифра. Остальные символы игнорятся
|
||||||
|
# Условна дана строка TEST_EEE_DDD_RRR - снегерится примерно такая - TEST_QRG_904_ЙЦУ
|
||||||
|
* сгенерировать переменные
|
||||||
|
| id | 0 |
|
||||||
|
| username | EEEEEEEE |
|
||||||
|
| firstName | EEEEEEEE |
|
||||||
|
| lastName | EEEEEEEE |
|
||||||
|
| email | EEEEEEE@EEEDDD.EE |
|
||||||
|
| password | DDDEEEDDDEEE |
|
||||||
|
| phone | +7DDDDDDDDDD |
|
||||||
|
| userStatus | 1 |
|
||||||
|
|
||||||
|
# Создаем юзера
|
||||||
|
* создать запрос
|
||||||
|
| method | path | body |
|
||||||
|
| POST | /user | createUser.json |
|
||||||
|
* добавить header
|
||||||
|
| Content-Type | application/json |
|
||||||
|
* отправить запрос
|
||||||
|
* статус код 200
|
||||||
|
* извлечь данные
|
||||||
|
| user_id | $.message |
|
||||||
|
* сравнить значения
|
||||||
|
| ${user_id} | != | null |
|
||||||
|
|
||||||
|
# Вторая часть теста - запрос юзера и проверка его данных
|
||||||
|
* создать запрос
|
||||||
|
| method | path |
|
||||||
|
| GET | /user/${username} |
|
||||||
|
* добавить header
|
||||||
|
| accept | application/json |
|
||||||
|
|
||||||
|
# FLAKY - Из-за особенностей сервиса PetStore может возвращать 404
|
||||||
|
* отправить запрос
|
||||||
|
* статус код 200
|
||||||
|
* извлечь данные
|
||||||
|
| resp_firstname | $.firstName |
|
||||||
|
| resp_user_id | $.id |
|
||||||
|
|
||||||
|
* сравнить значения
|
||||||
|
| ${user_id} | == | ${resp_user_id} |
|
||||||
|
| ${firstName} | == | ${resp_firstname} |
|
||||||
18
src/test/resources/features/web/Example.feature
Normal file
18
src/test/resources/features/web/Example.feature
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#language:ru
|
||||||
|
@google
|
||||||
|
Функционал: Поиск гугл
|
||||||
|
|
||||||
|
Сценарий: Открытие страницы google.com, ввод значения в поиск
|
||||||
|
|
||||||
|
* шаг № "1"
|
||||||
|
* открыть url "https://www.google.ru/"
|
||||||
|
* инициализация страницы "Google"
|
||||||
|
* ввести в поле "поле поиска" значение "Погода в Москве"
|
||||||
|
|
||||||
|
* на странице отсутствует текст "погода в ижевске"
|
||||||
|
|
||||||
|
* шаг № "2"
|
||||||
|
* на странице имеется элемент "кнопка поиска"
|
||||||
|
* кликнуть на элемент "кнопка поиска"
|
||||||
|
* переход на страницу "Google страница результатов"
|
||||||
|
* на странице имеется элемент "виджет погоды"
|
||||||
24
src/test/resources/features/web/FragmentExample.feature
Normal file
24
src/test/resources/features/web/FragmentExample.feature
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#language:ru
|
||||||
|
@googleFragment
|
||||||
|
Функционал: Поиск гугл
|
||||||
|
|
||||||
|
Сценарий: Открытие двух фрагментов - страницы google.com и виджета погоды
|
||||||
|
|
||||||
|
* шаг № "1"
|
||||||
|
* вызвать фрагмент "открытие страницы google"
|
||||||
|
|
||||||
|
* на странице отсутствует текст "погода в ижевске"
|
||||||
|
|
||||||
|
* шаг № "2"
|
||||||
|
* вызвать фрагмент "открытие виджета"
|
||||||
|
|
||||||
|
@googleDynamicFragment
|
||||||
|
Сценарий: Открытие двух фрагментов - страницы google.com и виджета погоды с использованным динамического фрагмента
|
||||||
|
* шаг № "1"
|
||||||
|
* вызвать фрагмент "открытие страницы и ввод текста из параметра"
|
||||||
|
| текст_для_ввода | погода в москве |
|
||||||
|
|
||||||
|
* на странице отсутствует текст "погода в ижевске"
|
||||||
|
|
||||||
|
* шаг № "2"
|
||||||
|
* вызвать фрагмент "открытие виджета"
|
||||||
10
src/test/resources/json/createUser.json
Normal file
10
src/test/resources/json/createUser.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"id": ${id},
|
||||||
|
"username": "${username}",
|
||||||
|
"firstName": "${firstName}",
|
||||||
|
"lastName": "${lastName}",
|
||||||
|
"email": "${email}",
|
||||||
|
"password": "${password}",
|
||||||
|
"phone": "${phone}",
|
||||||
|
"userStatus": ${userStatus}
|
||||||
|
}
|
||||||
5
src/test/resources/log4j.properties
Normal file
5
src/test/resources/log4j.properties
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
log4j.rootLogger=INFO, stdout
|
||||||
|
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||||
|
log4j.appender.stdout.Target=System.out
|
||||||
|
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||||
|
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
||||||
9
src/test/resources/suite.xml
Normal file
9
src/test/resources/suite.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >
|
||||||
|
|
||||||
|
<suite name="suite" allow-return-values="true" verbose="1" data-provider-thread-count="1">
|
||||||
|
<test name="test">
|
||||||
|
<classes>
|
||||||
|
<class name="Runner"/>
|
||||||
|
</classes>
|
||||||
|
</test>
|
||||||
|
</suite>
|
||||||
Loading…
Reference in New Issue
Block a user