ДЗ #1
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
target/
|
||||||
|
build/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.iml
|
||||||
|
.DS_Store
|
||||||
94
README.md
Normal file
94
README.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# OTUS Selenium Homework 1
|
||||||
|
|
||||||
|
## Цель проекта
|
||||||
|
Автоматизировать 3 UI-сценария на `https://otus.ru` с использованием Selenium WebDriver 4+, JUnit 5, Guice DI, listeners, Stream API, Jsoup и обязательных проверок качества (Checkstyle + SpotBugs).
|
||||||
|
|
||||||
|
## Стек технологий
|
||||||
|
- Java 21
|
||||||
|
- Maven
|
||||||
|
- Selenium `4.38.0`
|
||||||
|
- WebDriverManager `6.3.3`
|
||||||
|
- JUnit 5
|
||||||
|
- Guice
|
||||||
|
- Jsoup
|
||||||
|
- Checkstyle
|
||||||
|
- SpotBugs
|
||||||
|
|
||||||
|
## Реализованные сценарии
|
||||||
|
1. Поиск курса по имени.
|
||||||
|
- Открытие каталога `https://otus.ru/catalog/courses`
|
||||||
|
- Поиск курса по имени через Stream API
|
||||||
|
- Клик по плитке курса
|
||||||
|
- Проверка заголовка открытого курса
|
||||||
|
|
||||||
|
2. Самые ранние/поздние курсы по дате старта.
|
||||||
|
- Открытие каталога `https://otus.ru/catalog/courses`
|
||||||
|
- Поиск ранних и поздних курсов через Stream API + `reduce`
|
||||||
|
- При совпадении дат проверяются все курсы с этой датой
|
||||||
|
- Проверка названия и даты старта на странице курса через Jsoup
|
||||||
|
|
||||||
|
3. Случайная категория с главной страницы.
|
||||||
|
- Открытие `https://otus.ru`
|
||||||
|
- Открытие меню «Обучение»
|
||||||
|
- Выбор случайной категории
|
||||||
|
- Проверка, что открыта корректная категория
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
- 2-уровневый тест-дизайн: `tests` + `page objects`
|
||||||
|
- DI через Guice для тестов и страниц
|
||||||
|
- JUnit 5 Extension (`GuiceExtension`), без базового класса-теста
|
||||||
|
- Фабрика драйвера:
|
||||||
|
- `DriverFactory` (интерфейс)
|
||||||
|
- `ChromeDriverFactory` (реализация)
|
||||||
|
- `WebDriverProvider` (жизненный цикл драйвера + декоратор listener)
|
||||||
|
- Подсветка через listener:
|
||||||
|
- Подсветка ставится в `beforeClick`
|
||||||
|
- Снимается в `afterClick`
|
||||||
|
- Стиль элемента возвращается в исходное состояние
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
- `src/main/java/ru/kovbasa/config` — DI-конфигурация
|
||||||
|
- `src/main/java/ru/kovbasa/driver` — фабрика и провайдер WebDriver
|
||||||
|
- `src/main/java/ru/kovbasa/listeners` — listener подсветки
|
||||||
|
- `src/main/java/ru/kovbasa/pages` — Page Object классы
|
||||||
|
- `src/main/java/ru/kovbasa/elements` — типизированные UI-элементы
|
||||||
|
- `src/test/java/ru/kovbasa/config` — JUnit extension для DI
|
||||||
|
- `src/test/java/ru/kovbasa/tests` — автотесты
|
||||||
|
|
||||||
|
## Требования к окружению
|
||||||
|
1. Установлен JDK 21 (доступен в `PATH`)
|
||||||
|
2. Установлен Google Chrome
|
||||||
|
3. Установлен Maven 3.9+
|
||||||
|
4. Есть доступ в интернет и к `otus.ru` (тесты запускаются на живом сайте)
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
### 1. Запуск только тестов
|
||||||
|
```bash
|
||||||
|
mvn test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Полная проверка (тесты + Checkstyle + SpotBugs)
|
||||||
|
```bash
|
||||||
|
mvn verify
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Запуск отдельного тестового класса
|
||||||
|
```bash
|
||||||
|
mvn "-Dtest=ru.kovbasa.tests.CourseSearchTest" test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Параметры запуска
|
||||||
|
Пробрасываются через Maven Surefire:
|
||||||
|
- `base.url` (по умолчанию `https://otus.ru`)
|
||||||
|
- `course.name` (по умолчанию `Python Developer`)
|
||||||
|
|
||||||
|
Пример переопределения:
|
||||||
|
```bash
|
||||||
|
mvn "-Dcourse.name=Python Developer" test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quality Gates
|
||||||
|
- Checkstyle и SpotBugs выполняется в фазе `verify`
|
||||||
|
|
||||||
|
## Примечания
|
||||||
|
- Тесты зависят от текущей верстки/контента `otus.ru`.
|
||||||
48
checkstyle.xml
Normal file
48
checkstyle.xml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||||
|
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||||
|
|
||||||
|
<module name="Checker">
|
||||||
|
|
||||||
|
<!-- File encoding -->
|
||||||
|
<property name="charset" value="UTF-8"/>
|
||||||
|
|
||||||
|
<!-- Fail build on any violation -->
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
|
||||||
|
<!-- Disallow tabs -->
|
||||||
|
<module name="FileTabCharacter"/>
|
||||||
|
|
||||||
|
<!-- Max line length -->
|
||||||
|
<module name="LineLength">
|
||||||
|
<property name="max" value="120"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="TreeWalker">
|
||||||
|
|
||||||
|
<!-- Naming conventions -->
|
||||||
|
<module name="TypeName"/>
|
||||||
|
<module name="MethodName"/>
|
||||||
|
<module name="ParameterName"/>
|
||||||
|
<module name="LocalVariableName"/>
|
||||||
|
<module name="MemberName"/>
|
||||||
|
|
||||||
|
<!-- Imports -->
|
||||||
|
<module name="AvoidStarImport"/>
|
||||||
|
<module name="UnusedImports"/>
|
||||||
|
<module name="RedundantImport"/>
|
||||||
|
|
||||||
|
<!-- Braces -->
|
||||||
|
<module name="NeedBraces"/>
|
||||||
|
|
||||||
|
<!-- Whitespace -->
|
||||||
|
<module name="WhitespaceAround"/>
|
||||||
|
<module name="WhitespaceAfter"/>
|
||||||
|
|
||||||
|
<!-- Empty blocks are forbidden -->
|
||||||
|
<module name="EmptyBlock"/>
|
||||||
|
|
||||||
|
</module>
|
||||||
|
|
||||||
|
</module>
|
||||||
173
pom.xml
Normal file
173
pom.xml
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<?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
|
||||||
|
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>ru.kovbasa</groupId>
|
||||||
|
<artifactId>homework_1</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
|
||||||
|
<!-- Конфиг тестов -->
|
||||||
|
<base.url>https://otus.ru</base.url>
|
||||||
|
<course.name>Python Developer</course.name>
|
||||||
|
|
||||||
|
<!-- Dependencies -->
|
||||||
|
<selenium.version>4.38.0</selenium.version>
|
||||||
|
<junit.version>5.10.0</junit.version>
|
||||||
|
<webdrivermanager.version>6.3.3</webdrivermanager.version>
|
||||||
|
<guice.version>5.1.0</guice.version>
|
||||||
|
<jsoup.version>1.21.2</jsoup.version>
|
||||||
|
<slf4j.version>2.0.11</slf4j.version>
|
||||||
|
<logback.version>1.4.14</logback.version>
|
||||||
|
<guava.version>32.1.3-jre</guava.version>
|
||||||
|
|
||||||
|
<!-- Plugins -->
|
||||||
|
<maven.compiler.version>3.11.0</maven.compiler.version>
|
||||||
|
<surefire.version>3.1.2</surefire.version>
|
||||||
|
<checkstyle.plugin.version>3.6.0</checkstyle.plugin.version>
|
||||||
|
<spotbugs.plugin.version>4.9.8.0</spotbugs.plugin.version>
|
||||||
|
<spotbugs.version>4.9.8</spotbugs.version>
|
||||||
|
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Selenium WebDriver 4+ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-java</artifactId>
|
||||||
|
<version>${selenium.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- WebDriverManager -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.bonigarcia</groupId>
|
||||||
|
<artifactId>webdrivermanager</artifactId>
|
||||||
|
<version>${webdrivermanager.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JUnit 5 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Guice -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.inject</groupId>
|
||||||
|
<artifactId>guice</artifactId>
|
||||||
|
<version>${guice.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jsoup -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>${jsoup.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>${logback.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Guava -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>${guava.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- Compiler -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven.compiler.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Surefire -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${surefire.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<useModulePath>false</useModulePath>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<base.url>${base.url}</base.url>
|
||||||
|
<course.name>${course.name}</course.name>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Checkstyle -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<version>${checkstyle.plugin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>checkstyle-validation</id>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<configLocation>checkstyle.xml</configLocation>
|
||||||
|
<consoleOutput>true</consoleOutput>
|
||||||
|
<failsOnError>true</failsOnError>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- SpotBugs -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.spotbugs</groupId>
|
||||||
|
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||||
|
<version>${spotbugs.plugin.version}</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.spotbugs</groupId>
|
||||||
|
<artifactId>spotbugs</artifactId>
|
||||||
|
<version>${spotbugs.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
9
spotbugs-exclude.xml
Normal file
9
spotbugs-exclude.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<FindBugsFilter>
|
||||||
|
|
||||||
|
<!-- Ignore warnings in test sources -->
|
||||||
|
<Match>
|
||||||
|
<Source name="~.*src/test/java/.*"/>
|
||||||
|
</Match>
|
||||||
|
|
||||||
|
</FindBugsFilter>
|
||||||
16
src/main/java/ru/kovbasa/config/DriverModule.java
Normal file
16
src/main/java/ru/kovbasa/config/DriverModule.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.kovbasa.config;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import ru.kovbasa.driver.ChromeDriverFactory;
|
||||||
|
import ru.kovbasa.driver.DriverFactory;
|
||||||
|
import ru.kovbasa.driver.WebDriverProvider;
|
||||||
|
|
||||||
|
public class DriverModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(DriverFactory.class).to(ChromeDriverFactory.class).in(Singleton.class);
|
||||||
|
bind(WebDriverProvider.class).in(Singleton.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/ru/kovbasa/config/InjectorProvider.java
Normal file
16
src/main/java/ru/kovbasa/config/InjectorProvider.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.kovbasa.config;
|
||||||
|
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
|
public final class InjectorProvider {
|
||||||
|
|
||||||
|
private static final Injector INJECTOR = Guice.createInjector(new DriverModule());
|
||||||
|
|
||||||
|
private InjectorProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Injector getInjector() {
|
||||||
|
return INJECTOR;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/java/ru/kovbasa/config/TestConfig.java
Normal file
21
src/main/java/ru/kovbasa/config/TestConfig.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.kovbasa.config;
|
||||||
|
|
||||||
|
public final class TestConfig {
|
||||||
|
|
||||||
|
private static final String BASE_URL =
|
||||||
|
System.getProperty("base.url", "https://otus.ru");
|
||||||
|
|
||||||
|
private static final String COURSE_NAME =
|
||||||
|
System.getProperty("course.name", "Python Developer");
|
||||||
|
|
||||||
|
private TestConfig() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getBaseUrl() {
|
||||||
|
return BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCourseName() {
|
||||||
|
return COURSE_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/ru/kovbasa/driver/ChromeDriverFactory.java
Normal file
16
src/main/java/ru/kovbasa/driver/ChromeDriverFactory.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.kovbasa.driver;
|
||||||
|
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
|
import org.openqa.selenium.chrome.ChromeOptions;
|
||||||
|
|
||||||
|
public class ChromeDriverFactory implements DriverFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebDriver createDriver() {
|
||||||
|
final ChromeOptions options = new ChromeOptions();
|
||||||
|
options.addArguments("--start-maximized");
|
||||||
|
options.addArguments("--disable-notifications");
|
||||||
|
return new ChromeDriver(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/main/java/ru/kovbasa/driver/DriverFactory.java
Normal file
7
src/main/java/ru/kovbasa/driver/DriverFactory.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.kovbasa.driver;
|
||||||
|
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
public interface DriverFactory {
|
||||||
|
WebDriver createDriver();
|
||||||
|
}
|
||||||
43
src/main/java/ru/kovbasa/driver/WebDriverProvider.java
Normal file
43
src/main/java/ru/kovbasa/driver/WebDriverProvider.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package ru.kovbasa.driver;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.support.events.EventFiringDecorator;
|
||||||
|
|
||||||
|
import io.github.bonigarcia.wdm.WebDriverManager;
|
||||||
|
import ru.kovbasa.listeners.HighlightElementListener;
|
||||||
|
|
||||||
|
public final class WebDriverProvider {
|
||||||
|
|
||||||
|
private WebDriver driver;
|
||||||
|
private final DriverFactory driverFactory;
|
||||||
|
|
||||||
|
static {
|
||||||
|
WebDriverManager.chromedriver().setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WebDriverProvider(DriverFactory driverFactory) {
|
||||||
|
this.driverFactory = driverFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebDriver getDriver() {
|
||||||
|
if (driver == null) {
|
||||||
|
driver = createDecoratedDriver();
|
||||||
|
}
|
||||||
|
return driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebDriver createDecoratedDriver() {
|
||||||
|
final WebDriver raw = driverFactory.createDriver();
|
||||||
|
return new EventFiringDecorator(new HighlightElementListener())
|
||||||
|
.decorate(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void quit() {
|
||||||
|
if (driver != null) {
|
||||||
|
driver.quit();
|
||||||
|
driver = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/ru/kovbasa/elements/BaseElement.java
Normal file
27
src/main/java/ru/kovbasa/elements/BaseElement.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package ru.kovbasa.elements;
|
||||||
|
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
|
||||||
|
public abstract class BaseElement implements UIElement {
|
||||||
|
|
||||||
|
protected final WebElement element;
|
||||||
|
|
||||||
|
protected BaseElement(WebElement element) {
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void click() {
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return element.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDisplayed() {
|
||||||
|
return element.isDisplayed();
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/java/ru/kovbasa/elements/Button.java
Normal file
14
src/main/java/ru/kovbasa/elements/Button.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.kovbasa.elements;
|
||||||
|
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
|
||||||
|
public class Button extends BaseElement {
|
||||||
|
|
||||||
|
public Button(WebElement element) {
|
||||||
|
super(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return element.isEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/main/java/ru/kovbasa/elements/CourseCard.java
Normal file
54
src/main/java/ru/kovbasa/elements/CourseCard.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package ru.kovbasa.elements;
|
||||||
|
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.Year;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class CourseCard extends BaseElement {
|
||||||
|
|
||||||
|
private final By titleLocator = By.cssSelector("h6 .sc-hrqzy3-1");
|
||||||
|
private final By dateLocator = By.cssSelector(".sc-157icee-1 .sc-hrqzy3-1");
|
||||||
|
|
||||||
|
public CourseCard(WebElement element) {
|
||||||
|
super(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String title() {
|
||||||
|
return element.findElement(titleLocator).getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate startDate() {
|
||||||
|
String raw = element.findElement(dateLocator).getText().trim();
|
||||||
|
if (raw.isEmpty()) {
|
||||||
|
throw new RuntimeException("Пустая дата на карточке курса: " + title());
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = raw.split("·");
|
||||||
|
String datePart = parts[0].trim();
|
||||||
|
|
||||||
|
if (datePart.startsWith("С ")) {
|
||||||
|
datePart = datePart.substring(2).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
datePart = datePart.replace(",", "").replace("года", "").trim();
|
||||||
|
|
||||||
|
boolean hasYear = datePart.matches(".*\\d{4}.*");
|
||||||
|
|
||||||
|
String normalized;
|
||||||
|
DateTimeFormatter formatter;
|
||||||
|
|
||||||
|
if (hasYear) {
|
||||||
|
normalized = datePart;
|
||||||
|
formatter = DateTimeFormatter.ofPattern("d MMMM yyyy", Locale.forLanguageTag("ru"));
|
||||||
|
} else {
|
||||||
|
normalized = datePart + " " + Year.now().getValue();
|
||||||
|
formatter = DateTimeFormatter.ofPattern("d MMMM yyyy", Locale.forLanguageTag("ru"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalDate.parse(normalized, formatter);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/java/ru/kovbasa/elements/Link.java
Normal file
14
src/main/java/ru/kovbasa/elements/Link.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package ru.kovbasa.elements;
|
||||||
|
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
|
||||||
|
public class Link extends BaseElement {
|
||||||
|
|
||||||
|
public Link(WebElement element) {
|
||||||
|
super(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHref() {
|
||||||
|
return element.getAttribute("href");
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/main/java/ru/kovbasa/elements/UIElement.java
Normal file
7
src/main/java/ru/kovbasa/elements/UIElement.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.kovbasa.elements;
|
||||||
|
|
||||||
|
public interface UIElement {
|
||||||
|
void click();
|
||||||
|
String getText();
|
||||||
|
boolean isDisplayed();
|
||||||
|
}
|
||||||
151
src/main/java/ru/kovbasa/listeners/HighlightElementListener.java
Normal file
151
src/main/java/ru/kovbasa/listeners/HighlightElementListener.java
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package ru.kovbasa.listeners;
|
||||||
|
|
||||||
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
import org.openqa.selenium.StaleElementReferenceException;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.WrapsDriver;
|
||||||
|
import org.openqa.selenium.support.events.WebDriverListener;
|
||||||
|
|
||||||
|
public class HighlightElementListener implements WebDriverListener {
|
||||||
|
|
||||||
|
private static final String HIGHLIGHT_STYLE =
|
||||||
|
"outline: 4px solid red !important; " +
|
||||||
|
"box-shadow: 0 0 0 4px rgba(255,0,0,0.20) !important; " +
|
||||||
|
"background: rgba(255,0,0,0.06) !important;";
|
||||||
|
private WebElement highlightedElement;
|
||||||
|
private String highlightedOriginalStyle;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeClick(WebElement element) {
|
||||||
|
try {
|
||||||
|
clearHighlight();
|
||||||
|
|
||||||
|
final String tag = safeGetTag(element);
|
||||||
|
final String href = safeGetAttr(element, "href");
|
||||||
|
final String cls = safeGetAttr(element, "class");
|
||||||
|
System.out.println("beforeClick: tag=" + tag + " href=" + href + " class=" + cls);
|
||||||
|
|
||||||
|
final WebElement tile = findTileContainer(element);
|
||||||
|
applyHighlight(tile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("Highlight listener error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterClick(WebElement element) {
|
||||||
|
clearHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyHighlight(WebElement element) {
|
||||||
|
if (element == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final WebDriver raw = ((WrapsDriver) element).getWrappedDriver();
|
||||||
|
if (!(raw instanceof JavascriptExecutor)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final JavascriptExecutor js = (JavascriptExecutor) raw;
|
||||||
|
|
||||||
|
String original = safeGetAttr(element, "style");
|
||||||
|
if (original == null) {
|
||||||
|
original = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.highlightedElement = element;
|
||||||
|
this.highlightedOriginalStyle = original;
|
||||||
|
|
||||||
|
js.executeScript("arguments[0].setAttribute('style', arguments[1] + '; ' + arguments[2]);",
|
||||||
|
element, original, HIGHLIGHT_STYLE);
|
||||||
|
} catch (StaleElementReferenceException ignored) {
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("applyHighlight error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearHighlight() {
|
||||||
|
if (highlightedElement == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final WebDriver raw = ((WrapsDriver) highlightedElement).getWrappedDriver();
|
||||||
|
if (!(raw instanceof JavascriptExecutor)) {
|
||||||
|
highlightedElement = null;
|
||||||
|
highlightedOriginalStyle = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final JavascriptExecutor js = (JavascriptExecutor) raw;
|
||||||
|
final String original = highlightedOriginalStyle == null ? "" : highlightedOriginalStyle;
|
||||||
|
js.executeScript("arguments[0].setAttribute('style', arguments[1]);", highlightedElement, original);
|
||||||
|
} catch (StaleElementReferenceException ignored) {
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("clearHighlight error: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
highlightedElement = null;
|
||||||
|
highlightedOriginalStyle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebElement findTileContainer(WebElement element) {
|
||||||
|
try {
|
||||||
|
final String tag = safeGetTag(element);
|
||||||
|
final String href = safeGetAttr(element, "href");
|
||||||
|
final String cls = safeGetAttr(element, "class");
|
||||||
|
|
||||||
|
if ("a".equalsIgnoreCase(tag) && href != null &&
|
||||||
|
(href.contains("/lessons/") || href.contains("/categories/"))) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
if (cls != null && cls.contains("sc-zzdkm7-0")) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
final WebDriver rawDriver = ((WrapsDriver) element).getWrappedDriver();
|
||||||
|
if (!(rawDriver instanceof JavascriptExecutor)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
final JavascriptExecutor js = (JavascriptExecutor) rawDriver;
|
||||||
|
|
||||||
|
WebElement current = element;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
final Object parentObj = js.executeScript("return arguments[0].parentElement;", current);
|
||||||
|
if (!(parentObj instanceof WebElement)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = (WebElement) parentObj;
|
||||||
|
|
||||||
|
final String t = safeGetTag(current);
|
||||||
|
final String h = safeGetAttr(current, "href");
|
||||||
|
final String c = safeGetAttr(current, "class");
|
||||||
|
|
||||||
|
if ("a".equalsIgnoreCase(t) && h != null &&
|
||||||
|
(h.contains("/lessons/") || h.contains("/categories/"))) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
if (c != null && c.contains("sc-zzdkm7-0")) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safeGetAttr(WebElement el, String name) {
|
||||||
|
try { return el.getAttribute(name); }
|
||||||
|
catch (Exception e) { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safeGetTag(WebElement el) {
|
||||||
|
try { return el.getTagName(); }
|
||||||
|
catch (Exception e) { return null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
138
src/main/java/ru/kovbasa/pages/CatalogPage.java
Normal file
138
src/main/java/ru/kovbasa/pages/CatalogPage.java
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package ru.kovbasa.pages;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
import org.openqa.selenium.NoSuchElementException;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.interactions.Actions;
|
||||||
|
import org.openqa.selenium.support.ui.ExpectedCondition;
|
||||||
|
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||||
|
|
||||||
|
import ru.kovbasa.config.TestConfig;
|
||||||
|
import ru.kovbasa.driver.WebDriverProvider;
|
||||||
|
import ru.kovbasa.elements.CourseCard;
|
||||||
|
import ru.kovbasa.utils.PageUtils;
|
||||||
|
|
||||||
|
public class CatalogPage {
|
||||||
|
|
||||||
|
private final WebDriver driver;
|
||||||
|
|
||||||
|
private final By courseCards = By.cssSelector("a.sc-zzdkm7-0");
|
||||||
|
private final By courseLinks = By.cssSelector("a[href*='/lessons/']");
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CatalogPage(WebDriverProvider provider) {
|
||||||
|
this.driver = provider.getDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CatalogPage open() {
|
||||||
|
driver.get(TestConfig.getBaseUrl() + "/catalog/courses");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebElement findCourseByName(String name) {
|
||||||
|
final WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
|
||||||
|
wait.until((ExpectedCondition<Boolean>) drv -> !drv.findElements(courseLinks).isEmpty());
|
||||||
|
|
||||||
|
final List<WebElement> links = driver.findElements(courseLinks);
|
||||||
|
return links.stream()
|
||||||
|
.filter(e -> e.getText().toLowerCase().contains(name.toLowerCase()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new NoSuchElementException(
|
||||||
|
"Course not found in catalog by name: " + name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoursePage clickCourseByName(String name) {
|
||||||
|
PageUtils.removeBottomBanner(driver);
|
||||||
|
|
||||||
|
final WebElement course = findCourseByName(name);
|
||||||
|
|
||||||
|
((JavascriptExecutor) driver)
|
||||||
|
.executeScript("arguments[0].scrollIntoView({block:'center'});", course);
|
||||||
|
|
||||||
|
new Actions(driver)
|
||||||
|
.moveToElement(course)
|
||||||
|
.perform();
|
||||||
|
course.click();
|
||||||
|
|
||||||
|
return new CoursePage(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CourseCard> getAllCourseCards() {
|
||||||
|
return driver.findElements(courseCards).stream()
|
||||||
|
.map(CourseCard::new)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CourseItem> getAllCourses() {
|
||||||
|
return getAllCourseCards().stream()
|
||||||
|
.map(card -> {
|
||||||
|
try {
|
||||||
|
return new CourseItem(card.title(), card.startDate());
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CourseItem> findEarliestCourses() {
|
||||||
|
final List<CourseItem> all = getAllCourses();
|
||||||
|
|
||||||
|
final LocalDate minDate = all.stream()
|
||||||
|
.map(CourseItem::startDate)
|
||||||
|
.reduce((left, right) -> left.isBefore(right) ? left : right)
|
||||||
|
.orElseThrow();
|
||||||
|
|
||||||
|
return all.stream()
|
||||||
|
.filter(c -> c.startDate().isEqual(minDate))
|
||||||
|
.sorted(Comparator.comparing(CourseItem::title))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CourseItem> findLatestCourses() {
|
||||||
|
final List<CourseItem> all = getAllCourses();
|
||||||
|
|
||||||
|
final LocalDate maxDate = all.stream()
|
||||||
|
.map(CourseItem::startDate)
|
||||||
|
.reduce((left, right) -> left.isAfter(right) ? left : right)
|
||||||
|
.orElseThrow();
|
||||||
|
|
||||||
|
return all.stream()
|
||||||
|
.filter(c -> c.startDate().isEqual(maxDate))
|
||||||
|
.sorted(Comparator.comparing(CourseItem::title))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoursePage openCourse(CourseItem course) {
|
||||||
|
PageUtils.removeBottomBanner(driver);
|
||||||
|
|
||||||
|
final WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
|
||||||
|
|
||||||
|
final WebElement card = wait.until(drv ->
|
||||||
|
drv.findElements(courseCards).stream()
|
||||||
|
.filter(c -> new CourseCard(c).title().equals(course.title()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null)
|
||||||
|
);
|
||||||
|
|
||||||
|
((JavascriptExecutor) driver)
|
||||||
|
.executeScript("arguments[0].scrollIntoView({block:'center'});", card);
|
||||||
|
|
||||||
|
new Actions(driver)
|
||||||
|
.moveToElement(card)
|
||||||
|
.perform();
|
||||||
|
card.click();
|
||||||
|
|
||||||
|
return new CoursePage(driver);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/main/java/ru/kovbasa/pages/CategoryPage.java
Normal file
24
src/main/java/ru/kovbasa/pages/CategoryPage.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package ru.kovbasa.pages;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
public class CategoryPage {
|
||||||
|
|
||||||
|
private final WebDriver driver;
|
||||||
|
|
||||||
|
public CategoryPage(WebDriver driver) {
|
||||||
|
this.driver = driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeader() {
|
||||||
|
Document doc = Jsoup.parse(driver.getPageSource());
|
||||||
|
return doc.select("h1")
|
||||||
|
.stream()
|
||||||
|
.map(e -> e.text().trim())
|
||||||
|
.filter(t -> !t.isEmpty())
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new RuntimeException("Category header not found"));
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/main/java/ru/kovbasa/pages/CourseItem.java
Normal file
6
src/main/java/ru/kovbasa/pages/CourseItem.java
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.kovbasa.pages;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public record CourseItem(String title, LocalDate startDate) {
|
||||||
|
}
|
||||||
62
src/main/java/ru/kovbasa/pages/CoursePage.java
Normal file
62
src/main/java/ru/kovbasa/pages/CoursePage.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package ru.kovbasa.pages;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import ru.kovbasa.elements.Button;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class CoursePage {
|
||||||
|
|
||||||
|
private final WebDriver driver;
|
||||||
|
|
||||||
|
private final By enrollButton = By.cssSelector("button[data-testid='enroll-button']");
|
||||||
|
|
||||||
|
public CoursePage(WebDriver driver) {
|
||||||
|
this.driver = driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCourseTitle() {
|
||||||
|
return getFirstH1();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstH1() {
|
||||||
|
Document doc = Jsoup.parse(driver.getPageSource());
|
||||||
|
return doc.select("h1")
|
||||||
|
.stream()
|
||||||
|
.map(e -> e.text().trim())
|
||||||
|
.filter(t -> !t.isEmpty())
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new RuntimeException("Course title not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getCourseStartDate(LocalDate catalogDate) {
|
||||||
|
Document doc = Jsoup.parse(driver.getPageSource());
|
||||||
|
|
||||||
|
Element dateElement = doc.selectFirst("p.sc-3cb1l3-0");
|
||||||
|
if (dateElement == null) {
|
||||||
|
throw new RuntimeException("Start date <p> not found on course page");
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = dateElement.text().trim();
|
||||||
|
String fullDate = text + ", " + catalogDate.getYear();
|
||||||
|
|
||||||
|
DateTimeFormatter formatter =
|
||||||
|
DateTimeFormatter.ofPattern("d MMMM, yyyy", Locale.forLanguageTag("ru"));
|
||||||
|
|
||||||
|
return LocalDate.parse(fullDate, formatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Button getEnrollButton() {
|
||||||
|
return new Button(driver.findElement(enrollButton));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clickEnroll() {
|
||||||
|
getEnrollButton().click();
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/main/java/ru/kovbasa/pages/MainPage.java
Normal file
71
src/main/java/ru/kovbasa/pages/MainPage.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package ru.kovbasa.pages;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
import org.openqa.selenium.ElementClickInterceptedException;
|
||||||
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.interactions.Actions;
|
||||||
|
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||||
|
import ru.kovbasa.config.TestConfig;
|
||||||
|
import ru.kovbasa.driver.WebDriverProvider;
|
||||||
|
import ru.kovbasa.utils.PageUtils;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
public class MainPage {
|
||||||
|
|
||||||
|
private final WebDriver driver;
|
||||||
|
|
||||||
|
private final By menuLearning = By.cssSelector("span[title='Обучение']");
|
||||||
|
private final By categories = By.cssSelector("a[href*='/categories/']");
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MainPage(WebDriverProvider provider) {
|
||||||
|
this.driver = provider.getDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainPage open() {
|
||||||
|
driver.get(TestConfig.getBaseUrl());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String clickRandomCategory() {
|
||||||
|
PageUtils.removeBottomBanner(driver);
|
||||||
|
|
||||||
|
final WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
|
||||||
|
|
||||||
|
final WebElement menu = wait.until(drv -> drv.findElement(menuLearning));
|
||||||
|
menu.click();
|
||||||
|
|
||||||
|
final List<WebElement> els = wait.until(drv -> {
|
||||||
|
final List<WebElement> found = drv.findElements(categories).stream()
|
||||||
|
.filter(WebElement::isDisplayed)
|
||||||
|
.toList();
|
||||||
|
return found.isEmpty() ? null : found;
|
||||||
|
});
|
||||||
|
|
||||||
|
final WebElement chosen = els.get(ThreadLocalRandom.current().nextInt(els.size()));
|
||||||
|
final String href = chosen.getAttribute("href");
|
||||||
|
System.out.println("Selected category href = " + href);
|
||||||
|
|
||||||
|
((JavascriptExecutor) driver)
|
||||||
|
.executeScript("arguments[0].scrollIntoView({block:'center'});", chosen);
|
||||||
|
|
||||||
|
try {
|
||||||
|
chosen.click();
|
||||||
|
} catch (ElementClickInterceptedException ignored) {
|
||||||
|
new Actions(driver).moveToElement(chosen).click().perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String categorySlug = href.substring(href.lastIndexOf('/') + 1);
|
||||||
|
if (!driver.getCurrentUrl().contains(categorySlug)) {
|
||||||
|
driver.get(TestConfig.getBaseUrl() + "/catalog/courses?categories=" + categorySlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
return href;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/main/java/ru/kovbasa/utils/PageUtils.java
Normal file
22
src/main/java/ru/kovbasa/utils/PageUtils.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package ru.kovbasa.utils;
|
||||||
|
|
||||||
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
public final class PageUtils {
|
||||||
|
|
||||||
|
private PageUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeBottomBanner(WebDriver driver) {
|
||||||
|
try {
|
||||||
|
((JavascriptExecutor) driver)
|
||||||
|
.executeScript(
|
||||||
|
"document.querySelectorAll('.sc-11pdrud-1.cmIXWc')" +
|
||||||
|
".forEach(e => e.remove());"
|
||||||
|
);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
src/test/java/ru/kovbasa/config/GuiceExtension.java
Normal file
29
src/test/java/ru/kovbasa/config/GuiceExtension.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package ru.kovbasa.config;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
|
||||||
|
import ru.kovbasa.driver.WebDriverProvider;
|
||||||
|
|
||||||
|
public class GuiceExtension implements TestInstancePostProcessor, BeforeEachCallback, AfterEachCallback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
|
||||||
|
final Injector injector = InjectorProvider.getInjector();
|
||||||
|
injector.injectMembers(testInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeEach(ExtensionContext context) {
|
||||||
|
final Injector injector = InjectorProvider.getInjector();
|
||||||
|
injector.getInstance(WebDriverProvider.class).getDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterEach(ExtensionContext context) {
|
||||||
|
final Injector injector = InjectorProvider.getInjector();
|
||||||
|
injector.getInstance(WebDriverProvider.class).quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/test/java/ru/kovbasa/tests/CategoryRandomTest.java
Normal file
35
src/test/java/ru/kovbasa/tests/CategoryRandomTest.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package ru.kovbasa.tests;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.kovbasa.config.GuiceExtension;
|
||||||
|
import ru.kovbasa.driver.WebDriverProvider;
|
||||||
|
import ru.kovbasa.pages.MainPage;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(GuiceExtension.class)
|
||||||
|
public class CategoryRandomTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private MainPage main;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private WebDriverProvider webDriverProvider;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void randomCategoryOpensCorrectCatalog() {
|
||||||
|
main.open();
|
||||||
|
|
||||||
|
final String selectedHref = main.clickRandomCategory();
|
||||||
|
final String category = selectedHref.substring(selectedHref.lastIndexOf("/") + 1);
|
||||||
|
final String currentUrl = webDriverProvider.getDriver().getCurrentUrl();
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
currentUrl.contains(category),
|
||||||
|
"Catalog URL should contain selected category. Selected: "
|
||||||
|
+ selectedHref + ", current: " + currentUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/test/java/ru/kovbasa/tests/CourseSearchTest.java
Normal file
33
src/test/java/ru/kovbasa/tests/CourseSearchTest.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package ru.kovbasa.tests;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.kovbasa.config.GuiceExtension;
|
||||||
|
import ru.kovbasa.config.TestConfig;
|
||||||
|
import ru.kovbasa.pages.CatalogPage;
|
||||||
|
import ru.kovbasa.pages.CoursePage;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(GuiceExtension.class)
|
||||||
|
public class CourseSearchTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private CatalogPage catalog;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findCourseByName() {
|
||||||
|
catalog.open();
|
||||||
|
|
||||||
|
final String courseName = TestConfig.getCourseName();
|
||||||
|
|
||||||
|
final CoursePage page = catalog.clickCourseByName(courseName);
|
||||||
|
|
||||||
|
final String title = page.getCourseTitle();
|
||||||
|
assertTrue(
|
||||||
|
title.toLowerCase().contains(courseName.toLowerCase()),
|
||||||
|
"Course page title should contain searched course name"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/test/java/ru/kovbasa/tests/CoursesDatesTest.java
Normal file
74
src/test/java/ru/kovbasa/tests/CoursesDatesTest.java
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package ru.kovbasa.tests;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import ru.kovbasa.config.GuiceExtension;
|
||||||
|
import ru.kovbasa.pages.CatalogPage;
|
||||||
|
import ru.kovbasa.pages.CourseItem;
|
||||||
|
import ru.kovbasa.pages.CoursePage;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(GuiceExtension.class)
|
||||||
|
public class CoursesDatesTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private CatalogPage catalog;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void earliestCourseHasCorrectTitleAndDate() {
|
||||||
|
catalog.open();
|
||||||
|
|
||||||
|
final List<CourseItem> earliestCourses = catalog.findEarliestCourses();
|
||||||
|
|
||||||
|
for (CourseItem course : earliestCourses) {
|
||||||
|
final CoursePage page = catalog.openCourse(course);
|
||||||
|
|
||||||
|
final String pageTitle = page.getCourseTitle();
|
||||||
|
assertTrue(
|
||||||
|
pageTitle.toLowerCase().contains(course.title().toLowerCase()),
|
||||||
|
"Earliest course title on page should contain title from catalog: " + course.title()
|
||||||
|
);
|
||||||
|
|
||||||
|
final LocalDate pageDate = page.getCourseStartDate(course.startDate());
|
||||||
|
assertEquals(
|
||||||
|
course.startDate(),
|
||||||
|
pageDate,
|
||||||
|
"Earliest course start date should match for course: " + course.title()
|
||||||
|
);
|
||||||
|
|
||||||
|
catalog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void latestCourseHasCorrectTitleAndDate() {
|
||||||
|
catalog.open();
|
||||||
|
|
||||||
|
final List<CourseItem> latestCourses = catalog.findLatestCourses();
|
||||||
|
|
||||||
|
for (CourseItem course : latestCourses) {
|
||||||
|
final CoursePage page = catalog.openCourse(course);
|
||||||
|
|
||||||
|
final String pageTitle = page.getCourseTitle();
|
||||||
|
assertTrue(
|
||||||
|
pageTitle.toLowerCase().contains(course.title().toLowerCase()),
|
||||||
|
"Latest course title on page should contain title from catalog: " + course.title()
|
||||||
|
);
|
||||||
|
|
||||||
|
final LocalDate pageDate = page.getCourseStartDate(course.startDate());
|
||||||
|
assertEquals(
|
||||||
|
course.startDate(),
|
||||||
|
pageDate,
|
||||||
|
"Latest course start date should match for course: " + course.title()
|
||||||
|
);
|
||||||
|
|
||||||
|
catalog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user