diff --git a/java-reporter-testng-selenide/README.md b/java-reporter-testng-selenide/README.md
new file mode 100644
index 0000000..0276e71
--- /dev/null
+++ b/java-reporter-testng-selenide/README.md
@@ -0,0 +1,62 @@
+# Java reporter integration with TestNG
+
+## Overview
+
+This simple demo shows how Testomat.io Java reporter works in your project.
+
+## Installation
+
+1. Clone the repository
+
+```sh
+ git clone https://github.com/testomatio/examples.git
+ ```
+2. Change the directory
+
+```sh
+ cd java-reporter-testng
+```
+3. Install dependencies with test skip
+
+```sh
+ mvn clean install -DskipTests
+```
+
+
+## Configurations
+
+**By default, the library runs with properties default values except `testomatio.api.key` and `testomatio.listening`**
+
+
+
+Add your project API key to the `testomatio.properties` file ad `testomatio.api.key`
+
+## Configure TestNG Before Running
+
+Before running tests, make sure your TestNG suites are configured in the testng.xml file.
+Runs test methods in parallel using 7 threads simultaneously.
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Run
+
+Run tests with
+
+```bash
+ mvn test -Dtestomatio.api.key=tstmt_key #if you did not provide it in the `testomatio.properties` file
+```
+
+where `tstmt_key` is your Testomat.io key from a particular project.
diff --git a/java-reporter-testng-selenide/pom.xml b/java-reporter-testng-selenide/pom.xml
new file mode 100644
index 0000000..840e385
--- /dev/null
+++ b/java-reporter-testng-selenide/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ io.testomat
+ java-reporter-testng-selenide
+ 1.0-SNAPSHOT
+
+
+ 17
+ 17
+ UTF-8
+ 7.16.1
+ 7.11.0
+ 2.29.1
+
+
+
+
+ com.codeborne
+ selenide
+ ${selenide.version}
+
+
+ org.testng
+ testng
+ ${testng.version}
+ test
+
+
+ io.testomat
+ java-reporter-testng
+ 0.12.0
+
+
+ org.assertj
+ assertj-core
+ 3.27.3
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.3
+
+
+ testng.xml
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/main/java/io/testomat/config/Urls.java b/java-reporter-testng-selenide/src/main/java/io/testomat/config/Urls.java
new file mode 100644
index 0000000..343be51
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/java/io/testomat/config/Urls.java
@@ -0,0 +1,15 @@
+package io.testomat.config;
+
+public final class Urls {
+
+ public static final String BASE_URL = "https://www.saucedemo.com/";
+ public static final String INVENTORY_URL = BASE_URL + "inventory.html";
+ private static final String INVENTORY_ITEM_PATTERN = BASE_URL + INVENTORY_URL + "?id=%s";
+
+ private Urls() {
+ }
+
+ public static String inventoryItemUrl(int id) {
+ return String.format(INVENTORY_ITEM_PATTERN, id);
+ }
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/main/java/io/testomat/data/Users.java b/java-reporter-testng-selenide/src/main/java/io/testomat/data/Users.java
new file mode 100644
index 0000000..8db86f7
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/java/io/testomat/data/Users.java
@@ -0,0 +1,28 @@
+package io.testomat.data;
+
+public final class Users {
+
+ public static final String PASSWORD =
+ "secret_sauce";
+
+ public static final String STANDARD_USER =
+ "standard_user";
+
+ public static final String LOCKED_USER =
+ "locked_out_user";
+
+ public static final String PROBLEM_USER =
+ "problem_user";
+
+ public static final String PERFORMANCE_USER =
+ "performance_glitch_user";
+
+ public static final String ERROR_USER =
+ "error_user";
+
+ public static final String VISUAL_USER =
+ "visual_user";
+
+ private Users() {
+ }
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/main/java/io/testomat/pages/CartPage.java b/java-reporter-testng-selenide/src/main/java/io/testomat/pages/CartPage.java
new file mode 100644
index 0000000..40fc2c5
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/java/io/testomat/pages/CartPage.java
@@ -0,0 +1,32 @@
+package io.testomat.pages;
+
+import com.codeborne.selenide.ElementsCollection;
+import com.codeborne.selenide.SelenideElement;
+
+import static com.codeborne.selenide.CollectionCondition.sizeGreaterThan;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+
+public class CartPage {
+
+ private final ElementsCollection cartItems =
+ $$(".cart_item");
+
+ private final SelenideElement cartLink =
+ $(".shopping_cart_link");
+
+ public CartPage verifyItemsExist() {
+ cartItems.shouldHave(sizeGreaterThan(0));
+
+ return this;
+ }
+
+ public int getItemsCount() {
+ return cartItems.size();
+ }
+
+ public void cartLinkShouldVisible() {
+ cartLink.shouldBe(visible);
+ }
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/main/java/io/testomat/pages/InventoryPage.java b/java-reporter-testng-selenide/src/main/java/io/testomat/pages/InventoryPage.java
new file mode 100644
index 0000000..2e03567
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/java/io/testomat/pages/InventoryPage.java
@@ -0,0 +1,136 @@
+package io.testomat.pages;
+
+import com.codeborne.selenide.ElementsCollection;
+import com.codeborne.selenide.SelenideElement;
+
+import java.util.List;
+
+import static com.codeborne.selenide.CollectionCondition.sizeGreaterThan;
+import static com.codeborne.selenide.Condition.*;
+import static com.codeborne.selenide.Selenide.*;
+
+public class InventoryPage {
+
+ private final SelenideElement title =
+ $(".title");
+
+ private final ElementsCollection inventoryItems =
+ $$(".inventory_item");
+
+ private final ElementsCollection itemNames =
+ $$(".inventory_item_name");
+
+ private final ElementsCollection itemPrices =
+ $$(".inventory_item_price");
+
+ private final SelenideElement cartButton =
+ $(".shopping_cart_link");
+
+ private final SelenideElement sortDropdown =
+ $(".product_sort_container");
+
+ private final ElementsCollection addToCartButtons =
+ $$("button[id^='add-to-cart']");
+
+ private final ElementsCollection removeButtons =
+ $$("button[id^='remove']");
+
+ private final SelenideElement burgerMenu =
+ $("#react-burger-menu-btn");
+
+ private final SelenideElement logoutLink =
+ $("#logout_sidebar_link");
+
+ private final ElementsCollection inventoryItemsImgs =
+ $$(".inventory_item_img img");
+
+ private final SelenideElement inventoryDetailsImgs =
+ $(".inventory_details_img img");
+
+ private final SelenideElement inventoryDetailsName =
+ $(".inventory_details_name");
+
+ private final SelenideElement inventoryList =
+ $(".inventory_list");
+
+ public InventoryPage verifyPageLoaded() {
+ title.shouldHave(text("Products"));
+ inventoryItems.shouldHave(sizeGreaterThan(0));
+
+ return this;
+ }
+
+ public int getItemsCount() {
+
+ return inventoryItems.size();
+ }
+
+ public List getItemNames() {
+
+ return itemNames.texts();
+ }
+
+ public List getItemPrices() {
+ return itemPrices.texts();
+ }
+
+ public InventoryPage sortBy(String value) {
+
+ sortDropdown.selectOptionByValue(value);
+
+ return this;
+ }
+
+ public InventoryPage addFirstItemToCart() {
+ addToCartButtons.first().click();
+
+ return this;
+ }
+
+ public InventoryPage removeFirstItemFromCart() {
+
+ removeButtons.first().click();
+
+ return this;
+ }
+
+ public CartPage openCart() {
+
+ cartButton.click();
+
+ return new CartPage();
+ }
+
+ public LoginPage logout() {
+
+ burgerMenu.click();
+
+ logoutLink.shouldBe(visible).click();
+
+ return new LoginPage();
+ }
+
+ public ElementsCollection getInventoryItemsImgs() {
+ return inventoryItemsImgs;
+ }
+
+ public SelenideElement getInventoryDetailsImgs() {
+ return inventoryDetailsImgs;
+ }
+
+ public SelenideElement getInventoryDetailsName() {
+ return inventoryDetailsName;
+ }
+
+ public void inventoryListShouldVisible() {
+ inventoryList.shouldBe(visible);
+ }
+
+ public ElementsCollection getCartButtons() {
+ return addToCartButtons;
+ }
+
+ public ElementsCollection getItemPriceElements() {
+ return itemPrices;
+ }
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/main/java/io/testomat/pages/LoginPage.java b/java-reporter-testng-selenide/src/main/java/io/testomat/pages/LoginPage.java
new file mode 100644
index 0000000..6d6475c
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/java/io/testomat/pages/LoginPage.java
@@ -0,0 +1,76 @@
+package io.testomat.pages;
+
+import com.codeborne.selenide.SelenideElement;
+import io.testomat.config.Urls;
+
+import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.open;
+
+public class LoginPage {
+
+ private final SelenideElement usernameInput = $("#user-name");
+ private final SelenideElement passwordInput = $("#password");
+ private final SelenideElement loginButton = $("#login-button");
+
+ private final SelenideElement errorMessage = $("h3[data-test='error']");
+
+ public LoginPage openPage() {
+ open(Urls.BASE_URL);
+
+ return this;
+ }
+
+ public LoginPage verifyErrorMessage(String text) {
+ errorMessage.shouldHave(text(text));
+
+ return this;
+ }
+
+ public LoginPage enterUsername(String username) {
+ usernameInput
+ .shouldBe(visible)
+ .sendKeys(username);
+
+ return this;
+ }
+
+ public LoginPage enterPassword(String password) {
+ passwordInput
+ .shouldBe(visible)
+ .sendKeys(password);
+
+ return this;
+ }
+
+ public InventoryPage clickLogin() {
+ loginButton
+ .shouldBe(visible)
+ .click();
+
+ return new InventoryPage();
+ }
+
+ public LoginPage login() {
+ loginButton
+ .shouldBe(visible)
+ .click();
+
+ return this;
+ }
+
+ public LoginPage clickLoginExpectingFailure() {
+ loginButton
+ .shouldBe(visible)
+ .click();
+
+ return this;
+ }
+
+ public LoginPage verifyLoginButtonVisible() {
+ loginButton.shouldBe(visible);
+
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/main/java/io/testomat/pages/ProductPage.java b/java-reporter-testng-selenide/src/main/java/io/testomat/pages/ProductPage.java
new file mode 100644
index 0000000..60136a8
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/java/io/testomat/pages/ProductPage.java
@@ -0,0 +1,102 @@
+package io.testomat.pages;
+
+import com.codeborne.selenide.SelenideElement;
+
+import static com.codeborne.selenide.Condition.*;
+import static com.codeborne.selenide.Selenide.$;
+
+public class ProductPage {
+
+ private final SelenideElement productName =
+ $(".inventory_details_name");
+
+ private final SelenideElement productDescription =
+ $(".inventory_details_desc");
+
+ private final SelenideElement productPrice =
+ $(".inventory_details_price");
+
+ private final SelenideElement productImage =
+ $(".inventory_details_img img");
+
+ private final SelenideElement addToCartButton =
+ $("button[id^='add-to-cart']");
+
+ private final SelenideElement removeButton =
+ $("button[id^='remove']");
+
+ private final SelenideElement backButton =
+ $("#back-to-products");
+
+ private final SelenideElement cartBadge =
+ $(".shopping_cart_badge");
+
+ private final SelenideElement productSortContainer =
+ $(".product_sort_container");
+
+ public ProductPage verifyPageLoaded() {
+
+ productName.shouldBe(visible);
+
+ productDescription.shouldBe(visible);
+
+ productPrice.shouldBe(visible);
+
+ productImage.shouldBe(visible);
+
+ return this;
+ }
+
+ public String getProductName() {
+
+ return productName.getText();
+ }
+
+ public String getProductDescription() {
+
+ return productDescription.getText();
+ }
+
+ public String getProductPrice() {
+
+ return productPrice.getText();
+ }
+
+ public ProductPage addToCart() {
+
+ addToCartButton
+ .shouldBe(enabled)
+ .click();
+
+ return this;
+ }
+
+ public ProductPage removeFromCart() {
+
+ removeButton
+ .shouldBe(enabled)
+ .click();
+
+ return this;
+ }
+
+ public ProductPage verifyCartBadge(String count) {
+ cartBadge.shouldHave(text(count));
+
+ return this;
+ }
+
+ public InventoryPage clickBackButton() {
+ backButton.click();
+
+ return new InventoryPage();
+ }
+
+ public SelenideElement getCartBadge() {
+ return cartBadge;
+ }
+
+ public void productSortContainerShouldVisible() {
+ productSortContainer.shouldBe(visible);
+ }
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/main/java/io/testomat/steps/InventorySteps.java b/java-reporter-testng-selenide/src/main/java/io/testomat/steps/InventorySteps.java
new file mode 100644
index 0000000..83695fd
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/java/io/testomat/steps/InventorySteps.java
@@ -0,0 +1,13 @@
+package io.testomat.steps;
+
+import io.testomat.pages.ProductPage;
+
+public class InventorySteps {
+
+ public String getCartBadgeAmount() {
+ return new ProductPage()
+ .getCartBadge()
+ .getText();
+ }
+
+}
diff --git a/java-reporter-testng-selenide/src/main/java/io/testomat/steps/LoginSteps.java b/java-reporter-testng-selenide/src/main/java/io/testomat/steps/LoginSteps.java
new file mode 100644
index 0000000..923c33d
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/java/io/testomat/steps/LoginSteps.java
@@ -0,0 +1,33 @@
+package io.testomat.steps;
+
+import com.codeborne.selenide.SelenideElement;
+import io.testomat.data.Users;
+import io.testomat.pages.InventoryPage;
+import io.testomat.pages.LoginPage;
+
+public class LoginSteps {
+
+ public static InventoryPage loginAsStandardUser() {
+ return new LoginPage()
+ .openPage()
+ .enterUsername("standard_user")
+ .enterPassword("secret_sauce")
+ .clickLogin()
+ .verifyPageLoaded();
+ }
+
+ public static InventoryPage loginAs(String username) {
+
+ return new LoginPage()
+ .openPage()
+ .enterUsername(username)
+ .enterPassword(Users.PASSWORD)
+ .clickLogin()
+ .verifyPageLoaded();
+ }
+
+ public SelenideElement getInventoryDetailsName() {
+ return new InventoryPage().getInventoryDetailsName();
+ }
+
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/main/resources/testomatio.properties b/java-reporter-testng-selenide/src/main/resources/testomatio.properties
new file mode 100644
index 0000000..23fd259
--- /dev/null
+++ b/java-reporter-testng-selenide/src/main/resources/testomatio.properties
@@ -0,0 +1,11 @@
+#Change to https://beta.testomat.io/ if you use it
+testomatio.url=https://app.testomat.io/
+
+#define the run title, or it will be default_run_title
+testomatio.run.title=testng-example-run
+
+#Particular project api key, starts with "tstmt_"
+testomatio.api.key=
+
+#enables/disables the reporting (remove value to disable)
+testomatio.listening=true
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/test/java/base/BaseTest.java b/java-reporter-testng-selenide/src/test/java/base/BaseTest.java
new file mode 100644
index 0000000..a8f6d10
--- /dev/null
+++ b/java-reporter-testng-selenide/src/test/java/base/BaseTest.java
@@ -0,0 +1,28 @@
+package base;
+
+import com.codeborne.selenide.Configuration;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import static com.codeborne.selenide.Selenide.closeWebDriver;
+
+public class BaseTest {
+
+ @BeforeMethod
+ public void setup() {
+ Configuration.browser = "chrome";
+ Configuration.browserSize = "1920x1080";
+ Configuration.timeout = 10000;
+ Configuration.pageLoadTimeout = 60000;
+ Configuration.headless = false;
+ Configuration.screenshots = true;
+ Configuration.savePageSource = true;
+ Configuration.reopenBrowserOnFail = true;
+ Configuration.fastSetValue = true;
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() {
+ closeWebDriver();
+ }
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/src/test/java/tests/InventoryTest.java b/java-reporter-testng-selenide/src/test/java/tests/InventoryTest.java
new file mode 100644
index 0000000..8886841
--- /dev/null
+++ b/java-reporter-testng-selenide/src/test/java/tests/InventoryTest.java
@@ -0,0 +1,186 @@
+package tests;
+
+import static com.codeborne.selenide.Condition.empty;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.open;
+import static com.codeborne.selenide.Selenide.refresh;
+import static com.codeborne.selenide.Selenide.webdriver;
+import static com.codeborne.selenide.WebDriverConditions.url;
+
+import base.BaseTest;
+import io.testomat.config.Urls;
+import io.testomat.steps.InventorySteps;
+import java.util.Comparator;
+import java.util.List;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import io.testomat.pages.CartPage;
+import io.testomat.pages.InventoryPage;
+import io.testomat.pages.LoginPage;
+import io.testomat.steps.LoginSteps;
+
+public class InventoryTest extends BaseTest {
+
+ @Test
+ public void inventoryShouldBeLoaded() {
+ InventoryPage page = LoginSteps.loginAsStandardUser();
+ Assert.assertTrue(page.getItemsCount() > 0);
+ }
+
+ @Test
+ public void inventoryTitleShouldBeVisible() {
+ LoginSteps.loginAsStandardUser()
+ .verifyPageLoaded();
+ }
+
+ @Test
+ public void itemShouldBeAddedToCart() {
+ CartPage cartPage =
+ LoginSteps.loginAsStandardUser()
+ .addFirstItemToCart()
+ .openCart()
+ .verifyItemsExist();
+
+ Assert.assertEquals(cartPage.getItemsCount(), 1);
+ }
+
+ @Test
+ public void itemShouldBeRemovedFromCart() {
+ InventoryPage page =
+ LoginSteps.loginAsStandardUser()
+ .addFirstItemToCart()
+ .removeFirstItemFromCart();
+
+ CartPage cartPage = page.openCart();
+
+ Assert.assertEquals(cartPage.getItemsCount(), 0);
+ }
+
+ @Test
+ public void userShouldLogoutSuccessfully() {
+ LoginPage loginPage =
+ LoginSteps.loginAsStandardUser()
+ .logout();
+
+ loginPage.verifyLoginButtonVisible();
+ }
+
+ @Test
+ public void productsShouldBeSortedByNameAZ() {
+ InventoryPage page =
+ LoginSteps.loginAsStandardUser()
+ .sortBy("az");
+
+ List names = page.getItemNames();
+
+ List sorted =
+ names.stream()
+ .sorted()
+ .toList();
+
+ Assert.assertEquals(names, sorted);
+ }
+
+ @Test
+ public void productsShouldBeSortedByNameZA() {
+ InventoryPage page =
+ LoginSteps.loginAsStandardUser()
+ .sortBy("za");
+
+ List names = page.getItemNames();
+
+ List sorted =
+ names.stream()
+ .sorted(Comparator.reverseOrder())
+ .toList();
+
+ Assert.assertEquals(names, sorted);
+ }
+
+ @Test
+ public void productsShouldBeSortedByPriceLowToHigh() {
+ InventoryPage page =
+ LoginSteps.loginAsStandardUser()
+ .sortBy("lohi");
+
+ List prices =
+ page.getItemPrices()
+ .stream()
+ .map(p -> Double.parseDouble(
+ p.replace("$", "")
+ ))
+ .toList();
+
+ List sorted =
+ prices.stream()
+ .sorted()
+ .toList();
+
+ Assert.assertEquals(prices, sorted);
+ }
+
+ @Test
+ public void userShouldNotAccessInventoryWithoutLogin() {
+ open(Urls.INVENTORY_URL);
+ webdriver().shouldHave(url(Urls.BASE_URL));
+ }
+
+ @Test
+ public void lockedUserShouldNotLogin() {
+ new LoginPage()
+ .openPage()
+ .enterUsername("locked_out_user")
+ .enterPassword("secret_sauce")
+ .clickLoginExpectingFailure()
+ .verifyErrorMessage(
+ "Sorry, this user has been locked out."
+ );
+ }
+
+ @Test
+ public void cartShouldBeEmptyInitially() {
+ CartPage cartPage =
+ LoginSteps.loginAsStandardUser()
+ .openCart();
+
+ Assert.assertEquals(cartPage.getItemsCount(), 0);
+ }
+
+ @Test
+ public void allProductImagesShouldBeVisible() {
+ new InventoryPage()
+ .getInventoryItemsImgs()
+ .forEach(img ->
+ img.shouldBe(visible));
+ }
+
+ @Test
+ public void addToCartButtonsShouldBeVisible() {
+ LoginSteps.loginAsStandardUser();
+ new InventoryPage()
+ .getCartButtons()
+ .forEach(btn ->
+ btn.shouldBe(visible));
+ }
+
+ @Test
+ public void allPricesShouldBeVisible() {
+ LoginSteps.loginAsStandardUser();
+ new InventoryPage()
+ .getItemPriceElements()
+ .forEach(price ->
+ price.shouldNotBe(empty)
+ );
+ }
+
+ @Test
+ public void cartShouldPersistAfterRefresh() {
+ LoginSteps.loginAsStandardUser()
+ .addFirstItemToCart();
+ refresh();
+
+ Assert.assertEquals(
+ new InventorySteps().getCartBadgeAmount(),
+ "1");
+ }
+}
diff --git a/java-reporter-testng-selenide/src/test/java/tests/LoginTest.java b/java-reporter-testng-selenide/src/test/java/tests/LoginTest.java
new file mode 100644
index 0000000..7811169
--- /dev/null
+++ b/java-reporter-testng-selenide/src/test/java/tests/LoginTest.java
@@ -0,0 +1,129 @@
+package tests;
+
+import static com.codeborne.selenide.Condition.attributeMatching;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$$;
+import static com.codeborne.selenide.Selenide.open;
+
+import base.BaseTest;
+import io.testomat.config.Urls;
+import io.testomat.data.Users;
+import io.testomat.pages.CartPage;
+import io.testomat.pages.InventoryPage;
+import io.testomat.pages.ProductPage;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import io.testomat.pages.LoginPage;
+import io.testomat.steps.LoginSteps;
+
+public class LoginTest extends BaseTest {
+
+ @DataProvider(name = "validUsers")
+ public Object[][] validUsers() {
+
+ return new Object[][]{
+ {Users.STANDARD_USER},
+ {Users.PROBLEM_USER},
+ {Users.PERFORMANCE_USER},
+ {Users.ERROR_USER},
+ {Users.VISUAL_USER}
+ };
+ }
+
+ @Test(dataProvider = "validUsers")
+ public void validUsersShouldLogin(String username) {
+
+ new LoginPage()
+ .openPage()
+ .enterUsername(username)
+ .enterPassword(Users.PASSWORD)
+ .clickLogin()
+ .verifyPageLoaded();
+ }
+
+ @Test
+ public void lockedUserShouldSeeError() {
+ new LoginPage()
+ .openPage()
+ .enterUsername(Users.LOCKED_USER)
+ .enterPassword(Users.PASSWORD)
+ .clickLoginExpectingFailure()
+ .verifyErrorMessage(
+ "Sorry, this user has been locked out."
+ );
+ }
+
+ @Test
+ public void performanceUserShouldLoginSuccessfully() {
+ long start = System.currentTimeMillis();
+
+ new LoginPage()
+ .openPage()
+ .enterUsername(Users.PERFORMANCE_USER)
+ .enterPassword(Users.PASSWORD)
+ .clickLogin()
+ .verifyPageLoaded();
+
+ long elapsed = System.currentTimeMillis() - start;
+
+ Assert.assertTrue(elapsed > 2000);
+ }
+
+ @Test
+ public void performanceUserInventoryShouldLoad() {
+ new LoginPage()
+ .openPage()
+ .enterUsername(Users.PERFORMANCE_USER)
+ .enterPassword(Users.PASSWORD)
+ .clickLogin()
+ .verifyPageLoaded();
+ }
+
+ @Test
+ public void problemUserShouldHaveBrokenImages() {
+ new LoginPage()
+ .openPage()
+ .enterUsername(Users.PROBLEM_USER)
+ .enterPassword(Users.PASSWORD)
+ .clickLogin();
+
+ $$("img")
+ .forEach(img ->
+ img.shouldHave(attributeMatching(
+ "src",
+ ".*sl-404.*"
+ ))
+ );
+ }
+
+ @Test
+ public void problemUserCanStillAddItemsToCart() {
+
+ LoginSteps.loginAs(Users.PROBLEM_USER)
+ .addFirstItemToCart()
+ .openCart()
+ .verifyItemsExist();
+ }
+
+ @Test
+ public void errorUserShouldNavigateToProductPage() {
+ LoginSteps.loginAs(Users.ERROR_USER);
+ open(Urls.inventoryItemUrl(4));
+ new LoginSteps().getInventoryDetailsName().shouldBe(visible);
+ }
+
+ @Test
+ public void visualUserShouldOpenInventoryPage() {
+ LoginSteps.loginAs(Users.VISUAL_USER).verifyPageLoaded();
+ }
+
+ @Test
+ public void visualUserShouldSeeMainElements() {
+ LoginSteps.loginAs(Users.VISUAL_USER);
+
+ new InventoryPage().inventoryListShouldVisible();
+ new CartPage().cartLinkShouldVisible();
+ new ProductPage().productSortContainerShouldVisible();
+ }
+}
diff --git a/java-reporter-testng-selenide/src/test/java/tests/ProductDetailsTest.java b/java-reporter-testng-selenide/src/test/java/tests/ProductDetailsTest.java
new file mode 100644
index 0000000..013ecc9
--- /dev/null
+++ b/java-reporter-testng-selenide/src/test/java/tests/ProductDetailsTest.java
@@ -0,0 +1,163 @@
+package tests;
+
+import base.BaseTest;
+import io.testomat.config.Urls;
+import io.testomat.pages.InventoryPage;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import io.testomat.pages.LoginPage;
+import io.testomat.pages.ProductPage;
+
+import static com.codeborne.selenide.Condition.attributeMatching;
+import static com.codeborne.selenide.Selenide.open;
+import static com.codeborne.selenide.Selenide.refresh;
+import static com.codeborne.selenide.Selenide.webdriver;
+import static com.codeborne.selenide.WebDriverConditions.url;
+
+public class ProductDetailsTest extends BaseTest {
+
+ @BeforeMethod
+ public void login() {
+ new LoginPage()
+ .openPage()
+ .enterUsername("standard_user")
+ .enterPassword("secret_sauce")
+ .clickLogin();
+ }
+
+ private ProductPage openProductPage() {
+ open(Urls.inventoryItemUrl(7));
+
+ return new ProductPage()
+ .verifyPageLoaded();
+ }
+
+ @Test
+ public void productPageShouldBeOpened() {
+ openProductPage();
+ }
+
+ @Test
+ public void productNameShouldBeVisible() {
+ ProductPage page =
+ openProductPage();
+
+ Assert.assertFalse(
+ page.getProductName().isBlank()
+ );
+ }
+
+ @Test
+ public void productDescriptionShouldBeVisible() {
+ ProductPage page =
+ openProductPage();
+
+ Assert.assertFalse(
+ page.getProductDescription().isBlank()
+ );
+ }
+
+ @Test
+ public void productPriceShouldBeVisible() {
+ ProductPage page =
+ openProductPage();
+
+ Assert.assertTrue(
+ page.getProductPrice().contains("$")
+ );
+ }
+
+ @Test
+ public void productImageShouldBeVisible() {
+ openProductPage();
+ }
+
+ @Test
+ public void itemShouldBeAddedToCart() {
+ openProductPage()
+ .addToCart()
+ .verifyCartBadge("1");
+ }
+
+ @Test
+ public void itemShouldBeRemovedFromCart() {
+ openProductPage()
+ .addToCart()
+ .removeFromCart();
+ }
+
+ @Test
+ public void backButtonShouldReturnToInventory() {
+ openProductPage()
+ .clickBackButton()
+ .verifyPageLoaded();
+ }
+
+ @Test
+ public void cartBadgeShouldIncreaseAfterAdd() {
+ openProductPage()
+ .addToCart()
+ .verifyCartBadge("1");
+ }
+
+ @Test
+ public void addToCartButtonShouldChangeToRemove() {
+ openProductPage()
+ .addToCart();
+ }
+
+ @Test
+ public void pageShouldBeRefreshSafe() {
+ ProductPage page =
+ openProductPage();
+
+ page.addToCart();
+ refresh();
+
+ page.verifyCartBadge("1");
+ }
+
+ @Test
+ public void userShouldNotAccessProductPageWithoutLogin() {
+ open(Urls.inventoryItemUrl(3));
+ webdriver().shouldHave(
+ url(Urls.BASE_URL)
+ );
+ }
+
+ @Test
+ public void invalidProductIdShouldNotCrashApplication() {
+ open(Urls.inventoryItemUrl(999));
+ }
+
+ @Test
+ public void correctProductShouldBeDisplayed() {
+ ProductPage page =
+ openProductPage();
+
+ Assert.assertEquals(
+ page.getProductName(),
+ "Sauce Labs Backpack"
+ );
+ }
+
+ @Test
+ public void imageShouldContainSrcAttribute() {
+ new InventoryPage()
+ .getInventoryDetailsImgs()
+ .shouldHave(attributeMatching("src", ".*"));
+ }
+
+ @Test
+ public void addRemoveAddFlowShouldWork() {
+ ProductPage page =
+ openProductPage();
+
+ page.addToCart()
+ .removeFromCart()
+ .addToCart()
+ .verifyCartBadge("1");
+ }
+
+}
\ No newline at end of file
diff --git a/java-reporter-testng-selenide/testng.xml b/java-reporter-testng-selenide/testng.xml
new file mode 100644
index 0000000..f8d15cc
--- /dev/null
+++ b/java-reporter-testng-selenide/testng.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file