Compare commits
No commits in common. "main" and "cucumber_master" have entirely different histories.
main
...
cucumber_m
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/
|
||||
276
README.md
276
README.md
@ -1 +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 |
223
pom.xml
Normal file
223
pom.xml
Normal file
@ -0,0 +1,223 @@
|
||||
<?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.19.1</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>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.bonigarcia</groupId>
|
||||
<artifactId>webdrivermanager</artifactId>
|
||||
<version>5.5.3</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"));
|
||||
}
|
||||
53
src/main/java/ru/lanit/at/pages/LoginPage.java
Normal file
53
src/main/java/ru/lanit/at/pages/LoginPage.java
Normal file
@ -0,0 +1,53 @@
|
||||
package ru.lanit.at.pages;
|
||||
|
||||
import com.codeborne.selenide.SelenideElement;
|
||||
import io.qameta.allure.Step;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
import org.openqa.selenium.support.PageFactory;
|
||||
import ru.lanit.at.utils.web.pagecontext.WebPage;
|
||||
import ru.lanit.at.utils.web.annotations.Name;
|
||||
|
||||
import static com.codeborne.selenide.Selenide.$x;
|
||||
import static com.codeborne.selenide.Selenide.$;
|
||||
/** Страница логина */
|
||||
@Name(value = "Авторизация")
|
||||
public class LoginPage extends WebPage {
|
||||
|
||||
@Name("поле логина")
|
||||
private SelenideElement username = $x("//*[@id='username']");
|
||||
|
||||
@Name("поле пароля")
|
||||
private SelenideElement password = $("#password");
|
||||
|
||||
@Name("кнопка входа")
|
||||
private SelenideElement loginButton = $("[type='submit']");
|
||||
|
||||
/**
|
||||
* Авторизация пользователя
|
||||
*
|
||||
* @param user логин пользователя
|
||||
* @param pass пароль пользователя
|
||||
*/
|
||||
public LoginPage login(String user, String pass) {
|
||||
setUser(user);
|
||||
setPassword(pass);
|
||||
clickOnLoginButton();
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoginPage setUser(String user) {
|
||||
getElement("поле логина").setValue(user);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoginPage setPassword(String pass) {
|
||||
getElement("поле пароля").setValue(pass);
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoginPage clickOnLoginButton() {
|
||||
getElement("кнопка входа").click();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
43
src/main/java/ru/lanit/at/pages/TicketPage.java
Normal file
43
src/main/java/ru/lanit/at/pages/TicketPage.java
Normal file
@ -0,0 +1,43 @@
|
||||
package ru.lanit.at.pages;
|
||||
|
||||
import com.codeborne.selenide.SelenideElement;
|
||||
import io.qameta.allure.Step;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.testng.Assert;
|
||||
import ru.lanit.at.utils.web.pagecontext.WebPage;
|
||||
import ru.lanit.at.utils.web.annotations.Name;
|
||||
|
||||
import static com.codeborne.selenide.Selenide.$x;
|
||||
import static com.codeborne.selenide.Selenide.$;
|
||||
|
||||
/** Страница отдельного тикета (авторизированный пользователь) */
|
||||
@Name(value = "Страница тикета")
|
||||
public class TicketPage extends WebPage {
|
||||
|
||||
@Name("заголовок тикета")
|
||||
private SelenideElement title = $x("//th[@colspan='4']/h3");
|
||||
|
||||
@Name("очередь")
|
||||
private SelenideElement queue = $x("//th[contains(text(), 'Queue:')]");
|
||||
|
||||
@Name("дата выполнения")
|
||||
private SelenideElement dueDate = $x("//th[text()='Due Date']/following-sibling::td[1]");
|
||||
|
||||
@Name("email")
|
||||
private SelenideElement email = $x("//th[text()='Submitter E-Mail']/following-sibling::td[1]");
|
||||
|
||||
@Name("приоритет")
|
||||
private SelenideElement priority = $x("//th[text()='Priority']/following-sibling::td[1]");
|
||||
|
||||
@Name("описание")
|
||||
private SelenideElement description = $x("//td[@id='ticket-description']/p");
|
||||
|
||||
@Name("добавить файлы")
|
||||
private SelenideElement buttonAddFiles = $x("//button[@id='ShowFileUpload']");
|
||||
|
||||
@Name("выбрать файл")
|
||||
private SelenideElement selectFile = $(".btn-file");
|
||||
|
||||
}
|
||||
|
||||
70
src/main/java/ru/lanit/at/pages/TicketsPage.java
Normal file
70
src/main/java/ru/lanit/at/pages/TicketsPage.java
Normal file
@ -0,0 +1,70 @@
|
||||
package ru.lanit.at.pages;
|
||||
|
||||
import com.codeborne.selenide.ElementsCollection;
|
||||
import com.codeborne.selenide.SelenideElement;
|
||||
import io.qameta.allure.Step;
|
||||
import ru.lanit.at.utils.web.pagecontext.WebPage;
|
||||
import ru.lanit.at.utils.web.annotations.Name;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.codeborne.selenide.Selenide.*;
|
||||
|
||||
/** Страница с таблицей тикетов и фильтрами */
|
||||
@Name(value = "Список тикетов")
|
||||
public class TicketsPage extends WebPage {
|
||||
|
||||
@Name("ссылки на тикеты")
|
||||
private ElementsCollection ticketsHref = $$x("//div[@class='tickettitle']/a");
|
||||
|
||||
@Name("поле поиска")
|
||||
private SelenideElement search = $("#search_query");
|
||||
|
||||
@Name("кнопка поиска")
|
||||
private SelenideElement searchButton = $x("//i[@class='fas fa-search']/parent::button");
|
||||
|
||||
public TicketsPage clickOnSearch(String title) {
|
||||
getElement("поле поиска").clear();
|
||||
getElement("поле поиска").setValue(title);
|
||||
getElement("кнопка поиска").click();
|
||||
return this;
|
||||
}
|
||||
@Name("сортировка")
|
||||
private SelenideElement sortingBy = $x("//select[@id='id_sortx']");
|
||||
|
||||
@Name("сортировка по названию")
|
||||
private SelenideElement sortingByField = $x("//select[@id='id_sortx']/option[@value='title']");
|
||||
|
||||
@Name("чекбокс обратного порядка")
|
||||
private SelenideElement inputReverse = $x("//input[@id='id_sortreverse']");
|
||||
|
||||
@Name("выпадающий список добавления фильтра")
|
||||
private SelenideElement addFilter = $x("//select[@id='filterBuilderSelect']");
|
||||
|
||||
@Name("фильтр на очередь")
|
||||
private SelenideElement addFilterQueue = $x("//select[@id='filterBuilderSelect']/option[@id='filterBuilderSelect-Queue']");
|
||||
|
||||
@Name("очередь тикета")
|
||||
private SelenideElement addFilterQueueValue = $x("//select[@id='id_queues']/option[@value='1']");
|
||||
|
||||
@Name("статус тикета")
|
||||
private SelenideElement statusTicket = $x("//select[@id='id_statuses']/option[@value='1']");
|
||||
|
||||
@Name("кнопка для применения фильтра")
|
||||
private SelenideElement buttonApply = $x("//input[@value='Apply Filters']");
|
||||
|
||||
@Name("таба для сохранения поискового запроса")
|
||||
private SelenideElement queryTab = $x("//button[@data-target='#collapseTwo']");
|
||||
|
||||
@Name("название сохраненного поискового запроса")
|
||||
private SelenideElement queryTitle = $x("//input[@id='id_title']");
|
||||
|
||||
@Name("кнопка сохранить поисковый запрос")
|
||||
private SelenideElement querySaveButton = $x("//input[@value='Save Query']");
|
||||
|
||||
@Name("кнопка для отображения сохраненных запросов")
|
||||
private SelenideElement savedQueries = $x("//a[@id='ticketsDropdown']");
|
||||
/**
|
||||
* @param ticket
|
||||
*/
|
||||
}
|
||||
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
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package ru.lanit.at.steps.web.pages;
|
||||
|
||||
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 org.openqa.selenium.By;
|
||||
import ru.lanit.at.steps.web.AbstractWebSteps;
|
||||
import ru.lanit.at.utils.web.pagecontext.PageManager;
|
||||
|
||||
import static com.codeborne.selenide.Condition.*;
|
||||
import static com.codeborne.selenide.Selenide.$;
|
||||
import static com.codeborne.selenide.Selenide.$x;
|
||||
|
||||
public class TicketPageWebSteps extends AbstractWebSteps {
|
||||
|
||||
public TicketPageWebSteps(PageManager pageManager) {
|
||||
super(pageManager);
|
||||
}
|
||||
|
||||
@Если("загрузить файл {string}")
|
||||
public void uploadFile(String fileName) {
|
||||
SelenideElement fileInput = $("#file0");
|
||||
fileInput.uploadFromClasspath(fileName);
|
||||
LOGGER.info("загружен файл '{}'", fileName);
|
||||
}
|
||||
|
||||
@Если("проверить что на странице прикреплен файл {string}")
|
||||
public void checkFileAttached(String fileName) {
|
||||
$("#selectedfilename0").shouldBe(text(fileName));
|
||||
}
|
||||
|
||||
@Когда("на странице присутствует часть текста {string}")
|
||||
public void textContainsOnPage(String text) {
|
||||
$x(".//*[contains(text(), '" + text + "')]").shouldBe(Condition.visible);
|
||||
LOGGER.info("на странице присутствует часть текста '{}'", text);
|
||||
}
|
||||
|
||||
@Когда("кликнуть на элемент где присутствует часть текста {string}")
|
||||
public void clickOnElementByContainsText(String text) {
|
||||
$x(".//*[contains(text(), '" + text + "')]")
|
||||
.shouldBe(Condition.visible)
|
||||
.click();
|
||||
LOGGER.info("клик на элемент, содержащий текст '{}'", text);
|
||||
}
|
||||
|
||||
@Если("на странице есть название поискового запроса {string}")
|
||||
public void checkTitle(String title) {
|
||||
$x("//a[@class='dropdown-item small' and normalize-space(.)='" + title + "']")
|
||||
.shouldBe(Condition.visible);
|
||||
}
|
||||
|
||||
@Если("кликнуть на название поискового запроса {string}")
|
||||
public void clickSearchQuery(String title) {
|
||||
$x("//a[@class='dropdown-item small' and normalize-space(.)='" + title + "']")
|
||||
.shouldBe(Condition.visible)
|
||||
.click();
|
||||
}
|
||||
}
|
||||
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
|
||||
23
src/test/java/Runner.java
Normal file
23
src/test/java/Runner.java
Normal file
@ -0,0 +1,23 @@
|
||||
import io.cucumber.testng.AbstractTestNGCucumberTests;
|
||||
import io.cucumber.testng.CucumberOptions;
|
||||
import org.testng.annotations.DataProvider;
|
||||
|
||||
|
||||
@CucumberOptions(
|
||||
plugin = {
|
||||
"pretty",
|
||||
"io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm"
|
||||
},
|
||||
tags = "@helpdesk",
|
||||
features = {"classpath:features/"},
|
||||
glue = {"ru.lanit.at.steps", "ru.lanit.at.hooks", "ru.lanit.at.corecommonstep"}
|
||||
)
|
||||
public class Runner extends AbstractTestNGCucumberTests {
|
||||
|
||||
@Override
|
||||
@DataProvider(parallel = false)
|
||||
|
||||
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"
|
||||
* вызвать фрагмент "открытие виджета"
|
||||
4
src/test/resources/json/authToken.json
Normal file
4
src/test/resources/json/authToken.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"username": "${username}",
|
||||
"password": "${password}"
|
||||
}
|
||||
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}
|
||||
}
|
||||
8
src/test/resources/json/ticket.json
Normal file
8
src/test/resources/json/ticket.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": "${title}",
|
||||
"queue": "${queue}",
|
||||
"description": "${description}",
|
||||
"priority": "${priority}",
|
||||
"submitter_email": "${submitter_email}",
|
||||
"status": "${status}"
|
||||
}
|
||||
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