Compare commits

...

No commits in common. "main" and "cucumber_master" have entirely different histories.

90 changed files with 4675 additions and 1 deletions

19
.gitignore vendored Normal file
View 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
View File

@ -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"
![feature](images/cucumber_compile.png)
Для запуска фрагментов через 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/>
![feature](images/run-feature.png)
### Генерация отчета
По итогу прогонов можно сгенерить _Allure отчет_, для этого необходимо в Intellij Idea кликнуть на строку **Maven** в правом верхнем углу IDE и следовать инструкции по пунктам ниже:<br/>
В **п.1** необходимо выбрать тот модуль, в котором запускались тесты.<br/>
![allure](images/allure.png)<br/>
**По итогу сформируется Html страница с отчетом.**<br/>
![allure-report](images/allure_report.png)<br/>
**В отчете можно провалиться в каждый шаг и посмотреть информацию по нему**<br/>
![allure-step](images/allure_report_steps.png)<br/>
где
* Зеленым цветом отмечены - успешно прошедшие тесты
* Желтым цветом отмечены - тесты в которых есть неблокирующие дефекты
* Серым цветом отмечены - тесты который были пропущены
* Красным цветом отмечены - тесты с блокирующим дефектом
**Шаги с неблокирующими дефекты** помечаются в отчете желтым цветом. По окончании теста отображается информация о всех подобных дефектах.

View File

@ -0,0 +1,8 @@
#language:ru
Функционал: динамический фрагмент
@fragment
Сценарий: открытие страницы и ввод текста из параметра
* открыть url "https://www.google.ru/"
* инициализация страницы "Google"
* ввести в поле "поле поиска" значение "<текст_для_ввода>"

17
fragments/Example.feature Normal file
View File

@ -0,0 +1,17 @@
#language:ru
Функционал: фрагмент
@fragment
Сценарий: открытие виджета
* на странице имеется элемент "кнопка поиска"
* кликнуть на элемент "кнопка поиска"
* переход на страницу "Google страница результатов"
* на странице имеется элемент "виджет погоды"
@fragment
Сценарий: открытие страницы google
* открыть url "https://www.google.ru/"
* инициализация страницы "Google"
* ввести в поле "поле поиска" значение "Погода в Москве"

BIN
images/allure.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
images/allure_report.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

223
pom.xml Normal file
View 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>

View 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();
}
}
}
}

View 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;
}
}
}

View 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());
}
}

View File

@ -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;
}
}

View 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);
}
}

View File

@ -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();
}

View 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());
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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) {
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View 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();
}
}
}

View 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']");
}

View 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"));
}

View 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;
}
}

View 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");
}

View 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
*/
}

View 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);
});
}
}

View 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);
});
}
}

View File

@ -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")
);
}
}

View 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;
}
}

View 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);
}
}

View 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();
}
}

View 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);
}
}

View 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);
}
}

View File

@ -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
}

View File

@ -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();
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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]";
}

View 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");
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}
}

View 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;
}
}

View 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;
}
}

View 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("Ошибка ожидания подготовки видео файла для скачивания");
}
}

View 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();
}
}

View 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;
}
}

View File

@ -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);
}
}
}

View 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());
}
}

View File

@ -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;
};
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,7 @@
package ru.lanit.at.utils.selenide.extensions;
import com.codeborne.selenide.impl.SelenidePageFactory;
/** Класс для переопределения стандартной фабрики иницализации Page объектов */
public class CustomSelenidePageFactory extends SelenidePageFactory {
}

View 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();
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View 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);
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
<aspects>
<aspect name="ru.lanit.at.aspects.FragmentsAspect"/>
</aspects>
</aspectj>

View File

@ -0,0 +1 @@
ru.lanit.at.utils.selenide.extensions.CustomCommands

View File

@ -0,0 +1 @@
ru.lanit.at.utils.selenide.extensions.CustomSelenidePageFactory

View File

@ -0,0 +1 @@
ru.lanit.at.utils.allure.AllureLogger

View File

@ -0,0 +1 @@
ru.lanit.at.utils.allure.AllureLogger

View 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
View 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();
}
}

View File

@ -0,0 +1,5 @@
webdriver.browser.size=1920x1080
webdriver.browser.name=chrome
webdriver.timeoutSeconds=60
polling.timeoutMs=200
webdriver.version=99.0

View 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

View File

@ -0,0 +1,6 @@
webdriver.browser.size=1920x1080
webdriver.browser.name=edge
webdriver.timeoutSeconds=10
polling.timeoutMs=200
webdriver.version=91.0

View File

@ -0,0 +1,6 @@
webdriver.browser.size=1920x1080
webdriver.browser.name=firefox
webdriver.timeoutSeconds=10
polling.timeoutMs=200
webdriver.version=98.0

View File

@ -0,0 +1 @@
cucumber.publish.quiet=true

View 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 |

View 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 |

View 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} |

View File

@ -0,0 +1,18 @@
#language:ru
@google
Функционал: Поиск гугл
Сценарий: Открытие страницы google.com, ввод значения в поиск
* шаг № "1"
* открыть url "https://www.google.ru/"
* инициализация страницы "Google"
* ввести в поле "поле поиска" значение "Погода в Москве"
* на странице отсутствует текст "погода в ижевске"
* шаг № "2"
* на странице имеется элемент "кнопка поиска"
* кликнуть на элемент "кнопка поиска"
* переход на страницу "Google страница результатов"
* на странице имеется элемент "виджет погоды"

View File

@ -0,0 +1,24 @@
#language:ru
@googleFragment
Функционал: Поиск гугл
Сценарий: Открытие двух фрагментов - страницы google.com и виджета погоды
* шаг № "1"
* вызвать фрагмент "открытие страницы google"
* на странице отсутствует текст "погода в ижевске"
* шаг № "2"
* вызвать фрагмент "открытие виджета"
@googleDynamicFragment
Сценарий: Открытие двух фрагментов - страницы google.com и виджета погоды с использованным динамического фрагмента
* шаг № "1"
* вызвать фрагмент "открытие страницы и ввод текста из параметра"
| текст_для_ввода | погода в москве |
* на странице отсутствует текст "погода в ижевске"
* шаг № "2"
* вызвать фрагмент "открытие виджета"

View File

@ -0,0 +1,4 @@
{
"username": "${username}",
"password": "${password}"
}

View File

@ -0,0 +1,10 @@
{
"id": ${id},
"username": "${username}",
"firstName": "${firstName}",
"lastName": "${lastName}",
"email": "${email}",
"password": "${password}",
"phone": "${phone}",
"userStatus": ${userStatus}
}

View File

@ -0,0 +1,8 @@
{
"title": "${title}",
"queue": "${queue}",
"description": "${description}",
"priority": "${priority}",
"submitter_email": "${submitter_email}",
"status": "${status}"
}

View 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

View 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>