Prechádzať zdrojové kódy

Merge pull request #381 from mikementor/checks/e2e-checks

#380 example of  E2E UI screenshot check
Marat Chukmarov 4 rokov pred
rodič
commit
05af366f49

+ 3 - 0
kafka-ui-e2e-checks/.env.example

@@ -0,0 +1,3 @@
+USE_LOCAL_BROWSER=true
+SHOULD_START_SELENOID=false
+TURN_OFF_SCREENSHOTS=true

+ 6 - 0
kafka-ui-e2e-checks/.gitignore

@@ -0,0 +1,6 @@
+.env
+build/
+allure-results/
+selenoid/video/
+target/
+selenoid/logs/

+ 117 - 0
kafka-ui-e2e-checks/README.md

@@ -0,0 +1,117 @@
+### E2E UI automation for Kafka-ui
+
+This repository is for E2E UI automation. 
+
+### Table of Contents
+
+- [Prerequisites](#prerequisites)
+- [How to install](#how-to-install)
+- [Environment variables](#environment-variables)
+- [How to run checks](#how-to-run-checks)
+- [Reporting](#reporting)
+- [Environments setup](#environments-setup)
+- [Test Data](#test-data)
+- [Actions](#actions)
+- [Checks](#checks)
+- [Parallelization](#parallelization)
+- [How to develop](#how-to-develop)
+
+### Prerequisites
+- Docker & Docker-compose
+- Java
+- Maven
+  
+### How to install
+```
+git clone https://github.com/provectus/kafka-ui.git
+cd  kafka-ui-e2e-checks
+docker pull selenoid/vnc:chrome_86.0  
+```
+### Environment variables
+
+|Name               	                |   Default   | Description
+|---------------------------------------|-------------|---------------------
+|`USE_LOCAL_BROWSER`                    |  `true`     | clear reports dir on startup
+|`CLEAR_REPORTS_DIR`                    |  `true`     | clear reports dir on startup
+|`SHOULD_START_SELENOID`                |  `false`    | starts selenoid container on startup
+|`SELENOID_URL`                         |  `http://localhost:4444/wd/hub`    | URL of remote selenoid instance
+|`BASE_URL`                             |  `http://192.168.1.2:8080/`    | base url for selenide configuration
+|`PIXELS_THRESHOLD`                     |  `200`    | Amount of pixels, that should be different to fail screenshot check
+|`SCREENSHOTS_FOLDER`                   |  `screenshots/`    | folder for keeping reference screenshots
+|`DIFF_SCREENSHOTS_FOLDER`              |  `build/__diff__/`    | folder for keeping  screenshots diffs
+|`ACTUAL_SCREENSHOTS_FOLDER`            |  `build/__actual__/`   | folder for keeping  actual screenshots(during checks)
+|`SHOULD_SAVE_SCREENSHOTS_IF_NOT_EXIST` |  `true`    | folder for keeping  actual screenshots(during checks)
+|`TURN_OFF_SCREENSHOTS`                 |  `false`    | If true, `compareScreenshots` will not fail on different screenshots. Useful for functional debugging on local machine, while preserving golden screenshots made in selenoid
+
+### How to run checks
+
+1. Run `kafka-ui` 
+```
+cd docker
+docker-compose -f kafka-ui.yaml up -d
+```
+2. Run `selenoid-ui` 
+```
+cd kafka-ui-e2e-checks/docker
+docker-compose -f selenoid.yaml up -d
+```
+3. Run checks 
+```
+cd kafka-ui-e2e-checks
+mvn test
+```
+
+* There are several ways to run checks
+
+1. If you don't have  selenoid run on your machine
+```
+ mvn test -DSHOULD_START_SELENOID=true
+```
+⚠️ If you want to run checks in IDE with this approach, you'd need to set up
+environment variable(`SHOULD_START_SELENOID=true`) in `Run/Edit Configurations..`
+
+2. For development purposes it is better to just start separate selenoid in docker-compose
+Do it in separate window
+```
+cd docker
+docker-compose -f selenoid.yaml up
+```
+Then you can just `mvn test`. By default, `SELENOID_URL` will resolve to `http://localhost:4444/wd/hub`
+
+It's preferred way to run. 
+
+* If you have remote selenoid instance, set 
+
+`SELENOID_URL` environment variable
+
+Example:
+`mvn test -DSELENOID_URL=http://localhost:4444/wd/hub`
+That's the way to run tests in CI with selenoid set up somewhere in cloud
+
+### Reporting
+
+Reports are in `allure-results` folder.
+If you have installed allure commandline(e.g. like [here](https://docs.qameta.io/allure/#_installing_a_commandline) or [here](https://www.npmjs.com/package/allure-commandline))
+You can see allure report with command:
+```
+allure serve
+```
+### Screenshots
+
+Reference screenshots are in `SCREENSHOTS_FOLDER`  (default,`kafka-ui-e2e-checks/screenshots`)
+
+### How to develop
+> ⚠️ todo 
+### Setting for different environments
+> ⚠️ todo 
+### Test Data
+> ⚠️ todo 
+### Actions
+> ⚠️ todo 
+### Checks
+> ⚠️ todo 
+### Parallelization
+> ⚠️ todo 
+### Tips
+ - install `Selenium UI Testing plugin` in IDEA
+

+ 25 - 0
kafka-ui-e2e-checks/docker/selenoid.yaml

@@ -0,0 +1,25 @@
+version: '3'
+
+services:
+  selenoid:
+    network_mode: bridge
+    image: aerokube/selenoid:1.10.3
+    volumes:
+      - "../selenoid/config:/etc/selenoid"
+      - "/var/run/docker.sock:/var/run/docker.sock"
+      - "../selenoid/video:/video"
+      - "../selenoid/logs:/opt/selenoid/logs"
+    environment:
+      - OVERRIDE_VIDEO_OUTPUT_DIR=video
+    command: [ "-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs" ]
+    ports:
+      - "4444:4444"
+
+  selenoid-ui:
+    network_mode: bridge
+    image: aerokube/selenoid-ui:latest-release
+    links:
+      - selenoid
+    ports:
+      - "8081:8080"
+    command: [ "--selenoid-uri", "http://selenoid:4444" ]

+ 182 - 0
kafka-ui-e2e-checks/pom.xml

@@ -0,0 +1,182 @@
+<?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">
+    <parent>
+        <artifactId>kafka-ui</artifactId>
+        <groupId>com.provectus</groupId>
+        <version>0.0.11-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>kafka-ui-e2e-checks</artifactId>
+    <properties>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <junit.version>5.7.0</junit.version>
+        <aspectj.version>1.9.6</aspectj.version>
+        <allure.version>2.13.7</allure.version>
+        <testcontainers.version>1.15.2</testcontainers.version>
+        <selenide.version>5.16.2</selenide.version>
+        <assertj.version>3.17.1</assertj.version>
+        <google.auto-service.version>1.0-rc7</google.auto-service.version>
+        <hamcrest.version>2.2</hamcrest.version>
+        <slf4j.version>1.7.29</slf4j.version>
+        <testcontainers.junit-jupiter.version>1.15.1</testcontainers.junit-jupiter.version>
+        <allure.java-commons.version>2.13.6</allure.java-commons.version>
+        <dotenv.version>2.2.0</dotenv.version>
+        <junit.platform-launcher.version>1.6.2</junit.platform-launcher.version>
+        <allure.maven-plugin.version>2.6</allure.maven-plugin.version>
+        <ashot.version>1.5.4</ashot.version>
+        <allure.screendiff-plugin.version>2.13.9</allure.screendiff-plugin.version>
+        <maven.surefire-plugin.version>2.22.2</maven.surefire-plugin.version>
+        <allure-maven.version>2.10.0</allure-maven.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka_2.13</artifactId>
+            <version>${kafka.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <version>${testcontainers.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.qameta.allure</groupId>
+            <artifactId>allure-junit5</artifactId>
+            <version>${allure.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.codeborne</groupId>
+            <artifactId>selenide</artifactId>
+            <version>${selenide.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.qameta.allure</groupId>
+            <artifactId>allure-selenide</artifactId>
+            <version>${allure.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest</artifactId>
+            <version>${hamcrest.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>${assertj.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.auto.service</groupId>
+            <artifactId>auto-service</artifactId>
+            <version>${google.auto-service.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>${junit.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>${junit.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>${slf4j.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${org.projectlombok.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjrt</artifactId>
+            <version>${aspectj.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>${testcontainers.junit-jupiter.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.qameta.allure</groupId>
+            <artifactId>allure-java-commons</artifactId>
+            <version>${allure.java-commons.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.github.cdimascio</groupId>
+            <artifactId>dotenv-java</artifactId>
+            <version>${dotenv.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <version>${junit.platform-launcher.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>ru.yandex.qatools.allure</groupId>
+            <artifactId>allure-maven-plugin</artifactId>
+            <version>${allure.maven-plugin.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>ru.yandex.qatools.ashot</groupId>
+            <artifactId>ashot</artifactId>
+            <version>${ashot.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.seleniumhq.selenium</groupId>
+                    <artifactId>selenium-remote-driver</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.qameta.allure.plugins</groupId>
+            <artifactId>screen-diff-plugin</artifactId>
+            <version>${allure.screendiff-plugin.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${maven.surefire-plugin.version}</version>
+                <configuration>
+                    <argLine>
+                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
+                    </argLine>
+                </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>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>15</source>
+                    <target>15</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

BIN
kafka-ui-e2e-checks/screenshots/main.png


+ 11 - 0
kafka-ui-e2e-checks/selenoid/config/browsers.json

@@ -0,0 +1,11 @@
+{
+  "chrome": {
+    "default": "86.0",
+    "versions": {
+      "86.0": {
+        "image": "selenoid/vnc:chrome_86.0",
+        "port": "4444"
+      }
+    }
+  }
+}

+ 103 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/base/BaseTest.java

@@ -0,0 +1,103 @@
+package com.provectus.kafka.ui.base;
+
+import com.codeborne.selenide.Configuration;
+import com.codeborne.selenide.logevents.SelenideLogger;
+import com.provectus.kafka.ui.pages.Pages;
+import com.provectus.kafka.ui.screenshots.Screenshooter;
+import io.github.cdimascio.dotenv.Dotenv;
+import io.qameta.allure.selenide.AllureSelenide;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.testcontainers.containers.BindMode;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+import java.io.File;
+import java.util.Arrays;
+
+import static com.codeborne.selenide.Selenide.closeWebDriver;
+
+@Slf4j
+@DisplayNameGeneration(CamelCaseToSpacedDisplayNameGenerator.class)
+public class BaseTest {
+
+  protected Pages pages = Pages.INSTANCE;
+
+  private Screenshooter screenshooter = new Screenshooter();
+
+  public void compareScreenshots(String name) {
+    screenshooter.compareScreenshots(name);
+  }
+
+  public void compareScreenshots(String name, Boolean shouldUpdateScreenshots) {
+    screenshooter.compareScreenshots(name, shouldUpdateScreenshots);
+  }
+
+  public static GenericContainer selenoid =
+      new GenericContainer(DockerImageName.parse("aerokube/selenoid:latest-release"))
+          .withExposedPorts(4444)
+          .withFileSystemBind("selenoid/config/", "/etc/selenoid", BindMode.READ_WRITE)
+          .withFileSystemBind("/var/run/docker.sock", "/var/run/docker.sock", BindMode.READ_WRITE)
+          .withFileSystemBind("selenoid/video", "/opt/selenoid/video", BindMode.READ_WRITE)
+          .withFileSystemBind("selenoid/logs", "/opt/selenoid/logs", BindMode.READ_WRITE)
+          .withEnv("OVERRIDE_VIDEO_OUTPUT_DIR", "/opt/selenoid/video")
+          .withCommand(
+              "-conf", "/etc/selenoid/browsers.json", "-log-output-dir", "/opt/selenoid/logs");
+
+  static {
+    Dotenv.load().entries().forEach(env -> System.setProperty(env.getKey(), env.getValue()));
+    if (TestConfiguration.CLEAR_REPORTS_DIR) {
+      clearReports();
+    }
+    setupSelenoid();
+  }
+
+  @AfterAll
+  public static void afterAll() {
+    closeWebDriver();
+    selenoid.close();
+  }
+
+  @SneakyThrows
+  private static void setupSelenoid() {
+    String remote = TestConfiguration.SELENOID_URL;
+    if (TestConfiguration.SHOULD_START_SELENOID) {
+      selenoid.start();
+      remote =
+          "http://%s:%s/wd/hub"
+              .formatted(selenoid.getContainerIpAddress(), selenoid.getMappedPort(4444));
+    }
+
+    Configuration.reportsFolder = TestConfiguration.REPORTS_FOLDER;
+    if (!TestConfiguration.USE_LOCAL_BROWSER) {
+      Configuration.remote = remote;
+      TestConfiguration.BASE_URL = TestConfiguration.BASE_URL.replace("localhost", "host.docker.internal");
+    }
+    Configuration.screenshots = TestConfiguration.SCREENSHOTS;
+    Configuration.savePageSource = TestConfiguration.SAVE_PAGE_SOURCE;
+    Configuration.reopenBrowserOnFail = TestConfiguration.REOPEN_BROWSER_ON_FAIL;
+    Configuration.browser = TestConfiguration.BROWSER;
+    Configuration.baseUrl = TestConfiguration.BASE_URL;
+    Configuration.browserSize = TestConfiguration.BROWSER_SIZE;
+    var capabilities = new DesiredCapabilities();
+    capabilities.setCapability("enableVNC", TestConfiguration.ENABLE_VNC);
+    Configuration.browserCapabilities = capabilities;
+
+    SelenideLogger.addListener("allure", new AllureSelenide().savePageSource(false));
+  }
+
+  public static void clearReports() {
+    log.info("Clearing reports dir [%s]...".formatted(TestConfiguration.REPORTS_FOLDER));
+    File allureResults = new File(TestConfiguration.REPORTS_FOLDER);
+    if (allureResults.isDirectory()) {
+      File[] list = allureResults.listFiles();
+      if (list != null)
+        Arrays.stream(list)
+            .sequential()
+            .filter(e -> !e.getName().equals("categories.json"))
+            .forEach(File::delete);
+    }
+  }
+}

+ 34 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/base/CamelCaseToSpacedDisplayNameGenerator.java

@@ -0,0 +1,34 @@
+package com.provectus.kafka.ui.base;
+
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.platform.commons.util.ClassUtils;
+import org.junit.platform.commons.util.Preconditions;
+
+import java.lang.reflect.Method;
+
+public class CamelCaseToSpacedDisplayNameGenerator implements DisplayNameGenerator {
+  @Override
+  public String generateDisplayNameForClass(Class<?> testClass) {
+    String name = testClass.getName();
+    int lastDot = name.lastIndexOf('.');
+    return name.substring(lastDot + 1).replaceAll("([A-Z])", " $1").toLowerCase();
+  }
+
+  @Override
+  public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
+    return nestedClass.getSimpleName();
+  }
+
+  @Override
+  public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
+    return testMethod.getName().replaceAll("([A-Z])", " $1").toLowerCase()
+        + parameterTypesAsString(testMethod);
+  }
+
+  static String parameterTypesAsString(Method method) {
+    Preconditions.notNull(method, "Method must not be null");
+    return method.getParameterTypes().length == 0
+        ? ""
+        : '(' + ClassUtils.nullSafeToString(Class::getSimpleName, method.getParameterTypes()) + ')';
+  }
+}

+ 27 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/base/TestConfiguration.java

@@ -0,0 +1,27 @@
+package com.provectus.kafka.ui.base;
+
+public class TestConfiguration {
+  public static boolean CLEAR_REPORTS_DIR =
+      Boolean.parseBoolean(System.getProperty("CLEAR_REPORTS_DIR", "true"));
+
+  public static boolean SHOULD_START_SELENOID =
+      Boolean.parseBoolean(System.getProperty("SHOULD_START_SELENOID", "false"));
+
+  public static String BASE_URL = System.getProperty("BASE_URL", "http://localhost:8080/");
+
+  public static boolean USE_LOCAL_BROWSER =
+      Boolean.parseBoolean(System.getProperty("USE_LOCAL_BROWSER", "true"));
+
+  public static String SELENOID_URL =
+      System.getProperty("SELENOID_URL", "http://localhost:4444/wd/hub");
+  public static String REPORTS_FOLDER = System.getProperty("REPORTS_FOLDER", "allure-results");
+  public static Boolean SCREENSHOTS =
+      Boolean.parseBoolean(System.getProperty("SCREENSHOTS", "false"));
+  public static Boolean SAVE_PAGE_SOURCE =
+      Boolean.parseBoolean(System.getProperty("SAVE_PAGE_SOURCE", "false"));
+  public static Boolean REOPEN_BROWSER_ON_FAIL =
+      Boolean.parseBoolean(System.getProperty("REOPEN_BROWSER_ON_FAIL", "true"));
+  public static String BROWSER = System.getProperty("BROWSER", "chrome");
+  public static String BROWSER_SIZE = System.getProperty("BROWSER_SIZE", "1920x1080");
+  public static Boolean ENABLE_VNC = Boolean.parseBoolean(System.getProperty("ENABLE_VNC", "true"));
+}

+ 16 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/MainPage.java

@@ -0,0 +1,16 @@
+package com.provectus.kafka.ui.pages;
+
+import com.codeborne.selenide.Condition;
+import io.qameta.allure.Step;
+import org.openqa.selenium.By;
+
+import static com.codeborne.selenide.Selenide.$;
+
+public class MainPage {
+
+    @Step
+    public void shouldBeOnPage(){
+        $(By.xpath("//*[contains(text(),'Loading')]")).shouldBe(Condition.disappear);
+        $(By.xpath("//h5[text()='Clusters']")).shouldBe(Condition.visible);
+    }
+}

+ 16 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/pages/Pages.java

@@ -0,0 +1,16 @@
+package com.provectus.kafka.ui.pages;
+
+import com.codeborne.selenide.Selenide;
+import com.provectus.kafka.ui.base.TestConfiguration;
+
+public class Pages {
+
+    public static Pages INSTANCE = new Pages();
+
+    public MainPage mainPage = new MainPage();
+
+    public Pages goTo(String path) {
+        Selenide.open(TestConfiguration.BASE_URL+path);
+        return this;
+    }
+}

+ 151 - 0
kafka-ui-e2e-checks/src/main/java/com/provectus/kafka/ui/screenshots/Screenshooter.java

@@ -0,0 +1,151 @@
+package com.provectus.kafka.ui.screenshots;
+
+import io.qameta.allure.Allure;
+import io.qameta.allure.Attachment;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.Assertions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ru.yandex.qatools.ashot.AShot;
+import ru.yandex.qatools.ashot.Screenshot;
+import ru.yandex.qatools.ashot.comparison.ImageDiff;
+import ru.yandex.qatools.ashot.comparison.ImageDiffer;
+import ru.yandex.qatools.ashot.coordinates.WebDriverCoordsProvider;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.nio.file.FileSystems;
+import java.util.List;
+
+import static com.codeborne.selenide.WebDriverRunner.getWebDriver;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class Screenshooter {
+
+  public static  Logger log = LoggerFactory.getLogger(Screenshooter.class);
+
+  private static int PIXELS_THRESHOLD =
+      Integer.parseInt(System.getProperty("PIXELS_THRESHOLD", "200"));
+  private static String SCREENSHOTS_FOLDER =
+      System.getProperty("SCREENSHOTS_FOLDER", "screenshots/");
+  private static String DIFF_SCREENSHOTS_FOLDER =
+      System.getProperty("DIFF_SCREENSHOTS_FOLDER", "build/__diff__/");
+  private static String ACTUAL_SCREENSHOTS_FOLDER =
+      System.getProperty("ACTUAL_SCREENSHOTS_FOLDER", "build/__actual__/");
+  private static boolean SHOULD_SAVE_SCREENSHOTS_IF_NOT_EXIST =
+      Boolean.parseBoolean(System.getProperty("SHOULD_SAVE_SCREENSHOTS_IF_NOT_EXIST", "true"));
+  private static boolean TURN_OFF_SCREENSHOTS =
+      Boolean.parseBoolean(System.getProperty("TURN_OFF_SCREENSHOTS", "false"));
+
+  private File newFile(String name) {
+    var file = new File(name);
+    if (!file.exists()) {
+      file.mkdirs();
+    }
+    return file;
+  }
+
+  public Screenshooter() {
+    List.of(SCREENSHOTS_FOLDER, DIFF_SCREENSHOTS_FOLDER, ACTUAL_SCREENSHOTS_FOLDER)
+        .forEach(this::newFile);
+  }
+
+  public void compareScreenshots(String name) {
+    compareScreenshots(name, false);
+  }
+
+  public void compareScreenshots(String name, boolean shouldUpdateScreenshotIfDiffer) {
+    if (TURN_OFF_SCREENSHOTS) {
+      return;
+    }
+    if (!doesScreenshotExist(name)) {
+      if (SHOULD_SAVE_SCREENSHOTS_IF_NOT_EXIST) {
+        updateActualScreenshot(name);
+      } else {
+        fail("no reference screenshot found for %s".formatted(name));
+      }
+    } else {
+      makeImageDiff(name, shouldUpdateScreenshotIfDiffer);
+    }
+  }
+
+  @SneakyThrows
+  private void updateActualScreenshot(String name) {
+    Screenshot actual =
+        new AShot().coordsProvider(new WebDriverCoordsProvider()).takeScreenshot(getWebDriver());
+    File  file=  newFile(SCREENSHOTS_FOLDER + name + ".png");
+    ImageIO.write(actual.getImage(), "png", file);
+    log.debug("created screenshot: %s \n at $s".formatted(name,file.getAbsolutePath()));
+  }
+
+  private static boolean doesScreenshotExist(String name) {
+    return new File(SCREENSHOTS_FOLDER + name + ".png").exists();
+  }
+
+  @SneakyThrows
+  private void makeImageDiff(String expectedName, boolean shouldUpdateScreenshotIfDiffer) {
+    String fullPathNameExpected = SCREENSHOTS_FOLDER + expectedName + ".png";
+    String fullPathNameActual = ACTUAL_SCREENSHOTS_FOLDER + expectedName + ".png";
+    String fullPathNameDiff = DIFF_SCREENSHOTS_FOLDER + expectedName + ".png";
+
+    //  activating allure plugin for showing diffs in report
+    Allure.label("testType", "screenshotDiff");
+
+    Screenshot actual =
+        new AShot().coordsProvider(new WebDriverCoordsProvider()).takeScreenshot(getWebDriver());
+    ImageIO.write(actual.getImage(), "png", newFile(fullPathNameActual));
+
+    Screenshot expected = new Screenshot(ImageIO.read(newFile(fullPathNameExpected)));
+    ImageDiff diff = new ImageDiffer().makeDiff(actual, expected);
+    BufferedImage diffImage = diff.getMarkedImage();
+    ImageIO.write(diffImage, "png", newFile(fullPathNameDiff));
+    // adding to report
+    diff(fullPathNameDiff);
+    // adding to report
+    actual(fullPathNameActual);
+    // adding to report
+    expected(fullPathNameExpected);
+
+    if (shouldUpdateScreenshotIfDiffer) {
+      if (diff.getDiffSize() > PIXELS_THRESHOLD) {
+        updateActualScreenshot(expectedName);
+      }
+    } else {
+      Assertions.assertTrue(
+          PIXELS_THRESHOLD >= diff.getDiffSize(),
+          ("""
+                  Amount of differing pixels should be less or equals than %s, actual %s
+                  diff file: %s
+                  """)
+              .formatted(PIXELS_THRESHOLD, diff.getDiffSize(), FileSystems.getDefault().getPath(fullPathNameDiff).normalize().toAbsolutePath().toString()));
+    }
+  }
+
+  @SneakyThrows
+  private byte[] imgToBytes(String filename) {
+    BufferedImage bImage2 = ImageIO.read(new File(filename));
+    ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
+    ImageIO.write(bImage2, "png", bos2);
+    return bos2.toByteArray();
+  }
+
+  @SneakyThrows
+  @Attachment
+  private byte[] actual(String actualFileName) {
+    return imgToBytes(actualFileName);
+  }
+
+  @SneakyThrows
+  @Attachment
+  private byte[] expected(String expectedFileName) {
+    return imgToBytes(expectedFileName);
+  }
+
+  @SneakyThrows
+  @Attachment
+  private byte[] diff(String diffFileName) {
+    return imgToBytes(diffFileName);
+  }
+}

+ 2 - 0
kafka-ui-e2e-checks/src/main/resources/allure.properties

@@ -0,0 +1,2 @@
+allure.results.directory=allure-results
+allure.link.issue.pattern=https://github.com/provectus/kafka-ui/issues/{}

+ 20 - 0
kafka-ui-e2e-checks/src/test/java/com/provectus/kafka/ui/SmokeTests.java

@@ -0,0 +1,20 @@
+package com.provectus.kafka.ui;
+
+import com.provectus.kafka.ui.base.BaseTest;
+import io.qameta.allure.Issue;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class SmokeTests extends BaseTest {
+
+    @Test
+    @SneakyThrows
+    @DisplayName("main page should load")
+    @Issue("380")
+    void mainPageLoads() {
+        pages.goTo("")
+            .mainPage.shouldBeOnPage();
+        compareScreenshots("main");
+    }
+}

+ 1 - 0
pom.xml

@@ -6,6 +6,7 @@
     <modules>
         <module>kafka-ui-contract</module>
 		<module>kafka-ui-api</module>
+		<module>kafka-ui-e2e-checks</module>
 	</modules>
 
 	<properties>