From 8da913a9be0eff614dc70aae85b9748f74ea08b4 Mon Sep 17 00:00:00 2001 From: Phuoc Nguyen Date: Fri, 13 Sep 2024 10:35:28 +0700 Subject: [PATCH 1/2] #1022 - Apply Automation test for Yas project - Update base form to retrieve web driver from WebDriverFactory. - Update package name. - Add plugin org.apache.maven.plugins:maven-surefire-plugin to make test run as command. - Create test cases for storefront and backoffice flows --- automation-ui/README.md | 87 ++++--- automation-ui/automation-base/pom.xml | 55 +++++ .../main/java/com/yas/AutomationBaseMain.java | 7 + .../com/yas/automation/ui/form/BaseForm.java | 26 +++ .../com/yas/automation/ui/form/InputType.java | 19 ++ .../com/yas/automation/ui/hook/Hooks.java | 1 + .../automation/ui/hook/WebDriverFactory.java | 10 +- .../com/yas/automation/ui/page/BasePage.java | 69 ++++++ .../ui/service/InputDelegateService.java | 26 +++ .../automation/ui/service/InputService.java | 9 + .../ui/service/impl/CheckBoxService.java | 13 ++ .../ui/service/impl/DropdownService.java | 16 ++ .../ui/service/impl/FileService.java | 15 ++ .../ui/service/impl/TextService.java | 14 ++ .../automation/ui/util}/WebElementUtil.java | 21 +- automation-ui/backoffice/pom.xml | 54 +++++ .../backoffice/sampledata/images/category.png | Bin 0 -> 5078 bytes .../backoffice/sampledata/images/dell.jpg | Bin 0 -> 88899 bytes .../ui/AutomationUiApplication.java | 16 ++ .../BackOfficeConfiguration.java | 6 + .../ui/enumerate/ProductAttribute.java | 18 ++ .../src/main/resources/application.properties | 2 + .../automation/ui/JUnitCucumberRunner.java | 22 ++ .../CucumberSpringConfiguration.java | 10 + .../ui/constants/CategoryConstants.java | 9 + .../yas/automation/ui/form/CategoryForm.java | 46 ++++ .../yas/automation/ui/form/ProductForm.java | 148 ++++++++++++ .../yas/automation/ui/pages/CategoryPage.java | 27 +++ .../com/yas/automation/ui/pages/HomePage.java | 33 +++ .../yas/automation/ui}/pages/LoginPage.java | 23 +- .../automation/ui/pages/NewCategoryPage.java | 55 +++++ .../yas/automation/ui/pages/ProductPage.java | 216 ++++++++++++++++++ .../ui/service/AuthenticationService.java | 23 ++ .../ui/steps/CreateCategorySteps.java | 85 +++++++ .../ui/steps/CreateProductSteps.java | 84 +++++++ .../yas/automation/ui/steps/LoginSteps.java | 73 ++++++ .../src/test/resources/application.yml | 4 + .../resources/features/createCategory.feature | 12 + .../resources/features/createProduct.feature | 11 + .../src/test/resources/features/login.feature | 7 + automation-ui/pom.xml | 147 ++++++++---- automation-ui/storefront/pom.xml | 54 ++--- .../automation/ui/JUnitCucumberRunner.java | 15 +- .../automation/ui/form/UserRegisterForm.java | 43 ++++ .../com/yas/automation/ui/pages/CartPage.java | 78 +++++++ .../ui/pages/CategoryItemDetailPage.java | 44 ++++ .../automation/ui/pages/CategoryItemPage.java | 22 ++ .../yas/automation/ui/pages/CategoryPage.java | 23 ++ .../ui/{storefront => }/pages/HomePage.java | 14 +- .../yas/automation/ui/pages/LoginPage.java | 30 +++ .../ui/pages/UserRegistrationPage.java | 59 +++++ .../ui/services/AuthenticationService.java | 52 +++++ .../automation/ui/steps/CartProcessSteps.java | 106 +++++++++ .../ui/{storefront => }/steps/LoginSteps.java | 29 +-- .../ui/steps/UserRegistrationSteps.java | 89 ++++++++ .../resources/features/cart-process.feature | 25 ++ .../{storefront => }/features/login.feature | 0 .../features/user-registration.feature | 28 +++ pom.xml | 53 ++++- 59 files changed, 2113 insertions(+), 170 deletions(-) create mode 100644 automation-ui/automation-base/pom.xml create mode 100644 automation-ui/automation-base/src/main/java/com/yas/AutomationBaseMain.java create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/form/BaseForm.java create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/form/InputType.java rename automation-ui/{storefront/src/test => automation-base/src/main}/java/com/yas/automation/ui/hook/Hooks.java (99%) rename automation-ui/{storefront/src/test => automation-base/src/main}/java/com/yas/automation/ui/hook/WebDriverFactory.java (79%) create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/page/BasePage.java create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/InputDelegateService.java create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/InputService.java create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/CheckBoxService.java create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/DropdownService.java create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/FileService.java create mode 100644 automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/TextService.java rename automation-ui/{storefront/src/test/java/com/yas/automation/ui/ultil => automation-base/src/main/java/com/yas/automation/ui/util}/WebElementUtil.java (55%) create mode 100644 automation-ui/backoffice/pom.xml create mode 100644 automation-ui/backoffice/sampledata/images/category.png create mode 100644 automation-ui/backoffice/sampledata/images/dell.jpg create mode 100644 automation-ui/backoffice/src/main/java/com/yas/automation/ui/AutomationUiApplication.java create mode 100644 automation-ui/backoffice/src/main/java/com/yas/automation/ui/configuration/BackOfficeConfiguration.java create mode 100644 automation-ui/backoffice/src/main/java/com/yas/automation/ui/enumerate/ProductAttribute.java create mode 100644 automation-ui/backoffice/src/main/resources/application.properties create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/JUnitCucumberRunner.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/configuration/CucumberSpringConfiguration.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/constants/CategoryConstants.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/form/CategoryForm.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/form/ProductForm.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/CategoryPage.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/HomePage.java rename automation-ui/{storefront/src/test/java/com/yas/automation/ui/storefront => backoffice/src/test/java/com/yas/automation/ui}/pages/LoginPage.java (81%) create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/NewCategoryPage.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/ProductPage.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/service/AuthenticationService.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/CreateCategorySteps.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/CreateProductSteps.java create mode 100644 automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/LoginSteps.java create mode 100644 automation-ui/backoffice/src/test/resources/application.yml create mode 100644 automation-ui/backoffice/src/test/resources/features/createCategory.feature create mode 100644 automation-ui/backoffice/src/test/resources/features/createProduct.feature create mode 100644 automation-ui/backoffice/src/test/resources/features/login.feature create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/form/UserRegisterForm.java create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CartPage.java create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryItemDetailPage.java create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryItemPage.java create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryPage.java rename automation-ui/storefront/src/test/java/com/yas/automation/ui/{storefront => }/pages/HomePage.java (66%) create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/LoginPage.java create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/UserRegistrationPage.java create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/services/AuthenticationService.java create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/CartProcessSteps.java rename automation-ui/storefront/src/test/java/com/yas/automation/ui/{storefront => }/steps/LoginSteps.java (81%) create mode 100644 automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/UserRegistrationSteps.java create mode 100644 automation-ui/storefront/src/test/resources/features/cart-process.feature rename automation-ui/storefront/src/test/resources/{storefront => }/features/login.feature (100%) create mode 100644 automation-ui/storefront/src/test/resources/features/user-registration.feature diff --git a/automation-ui/README.md b/automation-ui/README.md index 53dd92fe09..e05fa052bc 100644 --- a/automation-ui/README.md +++ b/automation-ui/README.md @@ -1,54 +1,47 @@ -# Automation project for YAS application +# 🛠️ Automation Project for YAS Application -## Tentative technologies and frameworks +This project automates tests for the YAS application using modern Java technologies. -- Java 21 -- Spring boot 3.2 -- Cucumber -- Cucumber-Spring -- Selenium -- Cucumber-Junit -- SonarCloud +## 🧑‍💻 Technologies and Frameworks +- **Java 21** +- **Spring Boot 3.2** +- **Cucumber** +- **Cucumber-Spring** +- **Selenium** +- **Cucumber-JUnit** +- **SonarCloud** +## 🏗️ Local Development Architecture +```mermaid +flowchart TD + TestRunner("Test Runner (JUnit, TestNG)") + FeatureFile("Feature File (.feature)") + StepDefinitions("Step Definitions (using Java)") + Selenium("Selenium WebDriver") + WebApp("Web Application") -## Local development architecture -``` -+----------------------------------------+ -| **Test Runner** (JUnit, TestNG) | -| - Executes the Cucumber scenarios | -| - Initiates WebDriver & test flow | -+----------------------------------------+ - | - v -+----------------------------------------+ -| **Feature File** (.feature) | -| - Written in Gherkin language | -| - Defines scenarios (Given, When, Then)| -+----------------------------------------+ - | - v -+----------------------------------------+ -| **Step Definitions** (using Java) | -| - Maps Gherkin steps to actual code | -| - Uses Selenium WebDriver for actions | -+----------------------------------------+ - | - v -+----------------------------------------+ -| **Selenium WebDriver** | -| - Automates browser interactions | -| - Opens browser, simulates actions | -| - Fetches web elements, etc. | -+----------------------------------------+ - | - v -+----------------------------------------+ -| **Web Application** | -| - The actual application under test | -+----------------------------------------+ + TestRunner --> FeatureFile + FeatureFile --> StepDefinitions + StepDefinitions --> Selenium + Selenium --> WebApp ``` +## 🚀 Getting Started + +To run the tests locally, follow the steps below: + +### 1. Start the YAS Application +Ensure the YAS application is running in your local environment. + +### 2. Running Tests via Maven +You can run the tests in two modes: -## Getting started : +- **Normal Mode**: Run the tests normally with the following command: + ```bash + mvn clean test + ``` -1. Make sure Yas application is up and running. -2. If you want to execute all scenarios in storefront, go to storefront project and execute command: mvn clean test , all test scenarios will be executed. Another way is running JUnitCucumberRunner class using IDE. \ No newline at end of file +- **Headless Mode**: + To run the tests in headless mode, use the following command: + ```bash + mvn clean test -Dheadless + ``` diff --git a/automation-ui/automation-base/pom.xml b/automation-ui/automation-base/pom.xml new file mode 100644 index 0000000000..8bad890efc --- /dev/null +++ b/automation-ui/automation-base/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + com.yas + automation-ui + ${revision} + + automation-base + + + nashtech-garage + https://sonarcloud.io + + nashtech-garage_yas-automation-ui-automation_base + + + + + org.seleniumhq.selenium + selenium-java + compile + + + io.github.bonigarcia + webdrivermanager + compile + + + io.cucumber + cucumber-java + compile + + + org.testng + testng + ${testng.version} + compile + + + io.cucumber + cucumber-testng + ${cucumber-testng.version} + compile + + + io.cucumber + cucumber-spring + compile + + + + \ No newline at end of file diff --git a/automation-ui/automation-base/src/main/java/com/yas/AutomationBaseMain.java b/automation-ui/automation-base/src/main/java/com/yas/AutomationBaseMain.java new file mode 100644 index 0000000000..5918bc1c32 --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/AutomationBaseMain.java @@ -0,0 +1,7 @@ +package com.yas; + +public class AutomationBaseMain { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/form/BaseForm.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/form/BaseForm.java new file mode 100644 index 0000000000..82478cfe30 --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/form/BaseForm.java @@ -0,0 +1,26 @@ +package com.yas.automation.ui.form; + +import lombok.Getter; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +/** + * Abstract base class for web forms, providing common form functionality. + */ +@Getter +public abstract class BaseForm { + + private WebDriver driver; + + private BaseForm() {} + + public BaseForm(WebDriver driver) { + this.driver = driver; + } + + public void submitForm() { + getSubmitBtn().click(); + } + + public abstract WebElement getSubmitBtn(); +} diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/form/InputType.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/form/InputType.java new file mode 100644 index 0000000000..01465e66d6 --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/form/InputType.java @@ -0,0 +1,19 @@ +package com.yas.automation.ui.form; + +import lombok.Getter; + +@Getter +public enum InputType { + + TEXT("textService"), + DROPDOWN("dropdownService"), + FILE("fileService"), + CHECKBOX("checkBoxService"); + + private final String serviceName; + + InputType(String serviceName) { + this.serviceName = serviceName; + } + +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/hook/Hooks.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/hook/Hooks.java similarity index 99% rename from automation-ui/storefront/src/test/java/com/yas/automation/ui/hook/Hooks.java rename to automation-ui/automation-base/src/main/java/com/yas/automation/ui/hook/Hooks.java index 4c12686122..011e4a80b6 100644 --- a/automation-ui/storefront/src/test/java/com/yas/automation/ui/hook/Hooks.java +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/hook/Hooks.java @@ -8,6 +8,7 @@ * Cucumber hook managing cucumber test scenarios */ public class Hooks { + @Autowired private WebDriverFactory webDriverFactory; diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/hook/WebDriverFactory.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/hook/WebDriverFactory.java similarity index 79% rename from automation-ui/storefront/src/test/java/com/yas/automation/ui/hook/WebDriverFactory.java rename to automation-ui/automation-base/src/main/java/com/yas/automation/ui/hook/WebDriverFactory.java index dbc131fc6f..145e8d911a 100644 --- a/automation-ui/storefront/src/test/java/com/yas/automation/ui/hook/WebDriverFactory.java +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/hook/WebDriverFactory.java @@ -2,6 +2,7 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; import org.springframework.stereotype.Component; /** @@ -28,7 +29,14 @@ public class WebDriverFactory { */ public synchronized WebDriver getChromeDriver() { if (webDriver.get() == null) { - WebDriver webDriver = new ChromeDriver(); + ChromeOptions options = new ChromeOptions(); + if (System.getProperty("headless") != null + && System.getProperty("headless").equals("true")) { + options.addArguments("--headless"); + options.addArguments("--disable-gpu"); + } + + WebDriver webDriver = new ChromeDriver(options); webDriver.manage().window().maximize(); this.webDriver.set(webDriver); } diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/page/BasePage.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/page/BasePage.java new file mode 100644 index 0000000000..0ea02a7b2c --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/page/BasePage.java @@ -0,0 +1,69 @@ +package com.yas.automation.ui.page; + +import com.yas.automation.ui.hook.WebDriverFactory; +import lombok.Getter; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.WebDriverWait; + +import java.time.Duration; +import java.util.function.Supplier; + +/** + * BasePage provides common setup for web pages in the automation framework. + * + *

It includes:

+ * + * + *

Extend this class to use these features in your page classes.

+ */ +@Getter +public class BasePage { + + private final WebDriverFactory webDriverFactory; + + public BasePage(WebDriverFactory webDriverFactory) { + this.webDriverFactory = webDriverFactory; + } + + public void wait(Duration duration) { + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public void scrollDown() { + JavascriptExecutor javascriptExecutor = (JavascriptExecutor) getWebDriver(); + javascriptExecutor.executeScript("window.scrollBy(0,document.body.scrollHeight)"); + wait(Duration.ofSeconds(1)); + } + + public void scrollTo(WebElement webElement) { + JavascriptExecutor javascriptExecutor = (JavascriptExecutor) getWebDriver(); + javascriptExecutor.executeScript("arguments[0].scrollIntoView(true);", webElement); + wait(Duration.ofSeconds(1)); + } + + public boolean isElementPresent(Supplier webElementConsumer) { + try { + return webElementConsumer.get().isDisplayed(); + } catch (Exception exception) { + return false; + } + } + + public WebDriver getWebDriver() { + return webDriverFactory.getChromeDriver(); + } + + public WebDriverWait getWait() { + return new WebDriverWait(getWebDriver(), Duration.ofSeconds(30)); + } +} diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/InputDelegateService.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/InputDelegateService.java new file mode 100644 index 0000000000..21e368f451 --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/InputDelegateService.java @@ -0,0 +1,26 @@ +package com.yas.automation.ui.service; + +import com.yas.automation.ui.form.InputType; +import org.openqa.selenium.WebElement; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +public class InputDelegateService { + + private final Map inputServiceMap; + + public InputDelegateService(Map inputServiceMap) { + this.inputServiceMap = inputServiceMap; + } + + public void setInputValue(InputType inputType, WebElement webElement, Object value) { + if (inputServiceMap.containsKey(inputType.getServiceName())) { + inputServiceMap.get(inputType.getServiceName()).setValue(webElement, value); + } else { + throw new IllegalArgumentException("No input service found for: %s".formatted(inputType.getServiceName())); + } + } + +} diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/InputService.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/InputService.java new file mode 100644 index 0000000000..2c459670d3 --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/InputService.java @@ -0,0 +1,9 @@ +package com.yas.automation.ui.service; + +import org.openqa.selenium.WebElement; + +public interface InputService { + + void setValue(WebElement webElement, T value); + +} diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/CheckBoxService.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/CheckBoxService.java new file mode 100644 index 0000000000..8b03f0de8b --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/CheckBoxService.java @@ -0,0 +1,13 @@ +package com.yas.automation.ui.service.impl; + +import com.yas.automation.ui.service.InputService; +import org.openqa.selenium.WebElement; +import org.springframework.stereotype.Component; + +@Component +public class CheckBoxService implements InputService { + @Override + public void setValue(WebElement webElement, Object value) { + webElement.click(); + } +} diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/DropdownService.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/DropdownService.java new file mode 100644 index 0000000000..78c866d450 --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/DropdownService.java @@ -0,0 +1,16 @@ +package com.yas.automation.ui.service.impl; + +import com.yas.automation.ui.service.InputService; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.Select; +import org.springframework.stereotype.Component; + +@Component +public class DropdownService implements InputService { + + @Override + public void setValue(WebElement webElement, Object value) { + Select selectBrand = new Select(webElement); + selectBrand.selectByVisibleText((String) value); + } +} diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/FileService.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/FileService.java new file mode 100644 index 0000000000..8b5d3e9adb --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/FileService.java @@ -0,0 +1,15 @@ +package com.yas.automation.ui.service.impl; + +import com.yas.automation.ui.service.InputService; +import org.openqa.selenium.WebElement; +import org.springframework.stereotype.Component; + +import java.nio.file.Paths; + +@Component +public class FileService implements InputService { + @Override + public void setValue(WebElement webElement, Object value) { + webElement.sendKeys(Paths.get((String) value).toAbsolutePath().toString()); + } +} diff --git a/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/TextService.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/TextService.java new file mode 100644 index 0000000000..c4025a52ea --- /dev/null +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/service/impl/TextService.java @@ -0,0 +1,14 @@ +package com.yas.automation.ui.service.impl; + +import com.yas.automation.ui.service.InputService; +import org.openqa.selenium.WebElement; +import org.springframework.stereotype.Component; + +@Component +public class TextService implements InputService { + + @Override + public void setValue(WebElement webElement, Object value) { + webElement.sendKeys((CharSequence) value); + } +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/ultil/WebElementUtil.java b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/util/WebElementUtil.java similarity index 55% rename from automation-ui/storefront/src/test/java/com/yas/automation/ui/ultil/WebElementUtil.java rename to automation-ui/automation-base/src/main/java/com/yas/automation/ui/util/WebElementUtil.java index c4c9122a7f..b8b9003fa0 100644 --- a/automation-ui/storefront/src/test/java/com/yas/automation/ui/ultil/WebElementUtil.java +++ b/automation-ui/automation-base/src/main/java/com/yas/automation/ui/util/WebElementUtil.java @@ -1,4 +1,4 @@ -package com.yas.automation.ui.ultil; +package com.yas.automation.ui.util; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; @@ -8,10 +8,12 @@ import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; +import java.util.List; public final class WebElementUtil { - private WebElementUtil() {} + private WebElementUtil() { + } public static WebElement getWebElementBy(WebDriver driver, How how, String identity) { return getWebElementBy(driver, how, identity, 30); @@ -24,4 +26,19 @@ public static WebElement getWebElementBy(WebDriver driver, How how, String ident return driver.findElement(locator); } + public static boolean isCorrectUrl(WebDriver driver, String expectedUrl) { + String currentUrl = driver.getCurrentUrl(); + return expectedUrl.equals(currentUrl); + } + + public static String createFieldText(String fieldName) { + return fieldName + System.currentTimeMillis(); + } + + public static boolean isElementPresent(WebDriver driver, How how, String identity) { + By locator = how.buildBy(identity); + List elements = driver.findElements(locator); + return !elements.isEmpty(); // Returns true if element exists, false if not + } + } diff --git a/automation-ui/backoffice/pom.xml b/automation-ui/backoffice/pom.xml new file mode 100644 index 0000000000..1fd9316033 --- /dev/null +++ b/automation-ui/backoffice/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + com.yas + automation-ui + ${revision} + ../pom.xml + + + backoffice + + + nashtech-garage + https://sonarcloud.io + nashtech-garage_yas-automation-ui-backoffice + + + + + com.yas + automation-base + ${project.version} + compile + + + + + org.seleniumhq.selenium + selenium-java + + + io.github.bonigarcia + webdrivermanager + + + + + io.cucumber + cucumber-java + + + io.cucumber + cucumber-spring + + + io.cucumber + cucumber-junit + + + + \ No newline at end of file diff --git a/automation-ui/backoffice/sampledata/images/category.png b/automation-ui/backoffice/sampledata/images/category.png new file mode 100644 index 0000000000000000000000000000000000000000..8a98bb945758b50934b8362ed4f274777ce0ab27 GIT binary patch literal 5078 zcmai2Ra6uJ(J`;%m4XcV#0qGUy;xo2Ztt0 z4XR-1ZGMnt;lSM2(s$whppT6T@JV-dW>`X*m};*>FTJ~Uf}|G-fKKb`eGk_w&V9kI zus-+owZ-tzq>_^cg{S(XH?Jk=O;N>=l~nbw=1dQx_(}v?F`|rPm2*1^)|^s&shz57ZE#9A!lx`ioou{x8yWw#2;t2X@8( zf34z~HDQ-4K38VjvSOmRL5%~gIa9y(Ri21yx^SD1`&;Bm+BdX(P?b!KJ zp|F*`s0-aFLZN_j;AYYDj9GvA8g8{Nwzn33xGx8uhk&xKa}A+K`VL{S5Mc$@ueSM4 zkyB$E(|P5e$&ZQD1<4Pm)3W>3qW|fx*vQ|MtABkJ)actBnlIQ}Ltqhv* zU5*yHK5*md>BgCTdZf+M-g(8^z1hI3M`dwGNz1vj9#PZ7K!4S$K1*p^aX?AtcWiI` zo-rF`(WO^v`Y5hz19j~3^8?P7h4ah5W?|Sm=a7LyNs8PB=DV1EX@Y6RBg=7t3ylD{ z2T+a$T%haX1kHLTeNJ-o-qyNWF3vqXR{u8xeY!ctZ78YH2NtlZ@XMZ-d%_K-<=tc+ zYwR2W3bKe;pO7}qx@DXSY*PxE5u&}k!{aN^#xIYh1;ZSWlVhik$V~GYQO=@xKp5q3HRo~X!jy_ZC2oxaj{ z04l00h~E^bs{|-(#PuPrJNHHdcB3ZKW*>Jf+z}d;FfFyzco_faA9d+gEfao%Xv*E2 zDLqEt-XEUg|8k)sPPWH{GG9^D8#fRQP_Q;#PL|vU_sp8qOP@0Cdz>`_!Lc!2Lod`C z-`m&-zhmdBjq3USG!-<-3TO6E*66JXXvar+g-0^Pe6`cGjkL;JuzsFXOTCRM@@bQF z+ggZJc1g`hnta~jK#`6oAV;CZdY?SeRa(9fa{6=ucaB^o4lF5>DV6M%yQqEH^Dr*g zXQSY@sT{FNTGN&CfV%wfJ)8$>Qt(5hWKwK=V_wy#rmf4S?ImH^@Icxg`n3AurtY|p z0+y>Ww(>S&YECrCx23ny-Tkq%F{pmUPhqMxbUEik4^88kTIx;Uu_+n&s)?(hE$B4v z208D$sZ+_>O6;xNwOt{?6NgF+Bt}0jAr~xme)vXmenbFO7zz!`>?9m)-Y{(J zLUg2imfg{6&$O9HpS&s#&Bpv=ibU@dckH2OTbsK(wR45qVLn^;CZ76$VguytA0eY( zzwWF$R{NYiK9{y_aDU9bc@e_@?hZbl5k%Dd*M#wprbWF`g;LJ7S(k`e@`94``k6@` z*=75zJPQ?c4`+Yb<-X~dPcwd+s&m~8jlds!j5RzSW=Ow7syc-*?(Ur)>%JI-zxPE7!^;R?q zpn%rySU(p@+K0}CFz#W&Zo=?~Yop~aRwh%K{3KtU`3&r`l?OuqlXv?5; zC02h;KpTgfuv$I_0X1nk*M($3Bj37^%BaY>OmT8wN+$y!=B3Y9RnzgFIg>6k%m|Yy z=HCUsELgp547_oB(c?Zi*3LyMIMKt7?)!cq$*aUiYUu9~&o!|!!qrTn{EWgh>S7-u zB-XGotqMNT2_c6ABo9@iGqF@owj7aL;+~eyg%~{oCWLGzGu3$B*U|&3KSq8Ls-e<( z&HO#Ha=`O9r|C&d@nL)A8MCl9pZ6%N1F4MUK5be57-Y9bM^Msbh$$IrJMuJ&iT)$m zu_xbTrQ7vIPu8DnMWR5QrCPcI-njg7kRP5+sjPAH?suY80m`VpKpgngk+bD|({l7z z&|PG$)!J3Yc6q_T*1_gf8op<1Bq`geS`i&X59Dc}+gPE*DhX$q*@t}dHw+O$%29#e z9JysER+=|d6+A!qKcETA0{D9SaRI#zf80arfz8>Uo$vY;m^(e6c9NJCRk80z8{EIj zvyYy7TG^hzr?`Ca(=$j29`IG>S)QC3wYC9yHEc{x=Le?2ivTbWn^}F4TF=uk()U&5 zFWT?E-TvQ@0OqW&nT&y#1qzRxiWKrCR!_W#CCWJ117j=d`lg z=sP@n?EDccDTPF?mp@;03P;BZtMDsFUUM-J78LK{Uaehw#7tS!H&S3wI7-I z$`%-h=QF4+8d*ac@8ws0~VfX&W0 z;$tpU`B5vX&jI6D(KK_d84R-o7h%y1W#G<@=jpbl7WzRcl6{J~Q3a*eV^g>*3fa~K za{6_J{xRQLGI}Cypc~|t=u4Gj5y&Fz|IO`i2>)*Las8gKR%eu_c^}FCSV$LE3bnZV z`9aTxzNWkhS{IT}ny)1`QPGdE+zVLG4%MS!R#nlBE_3W}_XriGS+klmw=3%WacN(= zUDj9j_Btgyak?W(dEkg0iG7c8920rR$-I5^nf9b+%yxe7rIxs> zc2lA_2!qosk?kxk%i8KpwvH)2U3`O$BpS(0%S^c#8jNsxsO`TyxZoI(;=iq8As?+H z#=r&2Bb?Y4XTngm)A0HRutpf0RvfrtXn6dst1-ELPB2~f%66-)0sxZ^eEX*4 zv>Mmoc4v+s>=67-1XXHNLH{u$3#0;VTuCsrY)UGO3odlPb)+$l?-r-g71cKaeohi{ zok8UD=I(T+>V;1?(S(G>7ism}T#u%uN(}{U)hL@<>L6l9qV0f!`Vd{tDDfH$A{_(M zVN2fU;-cpmTF+L0U|v1vKMcri!cDeXO@E2whbfUgJ3Ci|sfhu&K(;S%M<2loyjacK zm8}962&PkItfk^eyF>&VmL_ruryR1Gh{{_Mm^hm)o%zaW02tZ-g~*pV zfNXryMFyo`=D1U|_c-hEL}si?^w)-`F!nkv8gBH>`%s&V zkRw6zd?Ut~#jkV+U3~XF{tr}tynRrp56jf)E&^taZamnXG^WwwfqUEwZ5GrAPl)Hw zVAX5XPSF8UJ#>^s-Alab^Ra1)PVn>+OoSpe2I8*sz4a={V^}9Yh;K~T)>8DdC zk!2{Osv;IGqZeMS6}A01T?XK>4+hI<7BRPf_R3_AudaiqmH9d{x+sbfK=1?gdT0-N6Xo zPBYHP+Xe>UrSVD+XHa!wrL^$}x@)H^U`ec}E^O>1T4x!B3D;;zOWQZ};7lL3{g;vq z4RR!|cPiG}=-ppU86IQZ+YG$;@t0|B>V|`kM_T-FhR=n4VgoNjU}tKv;XlAZSbK(NHeysEH*uZC4qQE46B^^7n1pER_N7_5m7e~t4jU^>Zr7$3Y|esqsf9uf7n4td`&hb* zcuEhG$6#FdAQ1uh$$Juyad*|XoHE!vM9<5WXA)ZC$aaH|5%yv-^~oLn7IQa_goCh1 zNn(Hx(|BHPptWwR1da6`dfgmtxJ!p^PL}3NlSg5?74SsedS`~89NmEw43YW{`HA;b z^mOI*^}&A~ofDQ+sdc%r(YJyh%}7rLToNeZYg&85shOeZv?}+1*E%ajlTYCQ5bcz0u9@ZI>j`rtNZQH8g!C^5y)x)N$c6_VSW=A8)|rUZ zkWLfT%7?j*4nmL2phtUd?vF@3a2W(oX`xHTz%GVwwmaV@q;*l^wR8mlv97E`;_Ru< zxsLN>s|0qRp516aDhD5)qZM^bAaZ7$@S|$nGh3IuE2aqQ{b zTL=|bUh@4XQDC{HX=h^fm9d!CIBX6oMYGtuZbIMkZOt|~csTvFljYik$T5e@j^^X;|oT>?h^I0kRobK78&DH{$y4@asfK67j3)VoK+8f1_tc#nixOixGV0;x+fUN z+NRdgfh+aIge*Dcu8tCZI0PJVLC(j-T{uTapv@iL#xBppq);>XU0uU%VoN_~9E?J8 zWwVkMdC2@r-rMOM9Er@v4d3x*1*?tekH4M1OEt4|e z=CrS-ugejKsj%7X!j<}Q&xoN9J&Qw+`J=-ENIswvaU=jv8&6iS8+lY_fR1H|1 zs^|olIV>8*t3?t$xsh9u%0;hv8x8V#JFuQb?`sN~xGpzS=hO)Z1^Y;vMBN`Vg)#9b z^*7@3OZEOvy>@!|2+)k5lb9Vscu?K3k?2iYMJj8876@ccSv?au@5ll6bdu6v6Fs@# z{vdpqjlH&HUj#?(uJ!c&z78z&BX1LCy^P&dg`CAFv{Zt~PM|(qV<}-^>T>u7U1i-i zgLs5uAF-yv;>S|t31SK;$sRt{88%vaEXS=o09`-WVuO@$0?Z5yfbS$~p z^aBi8?Qzm50M41;iOfc{8_LkdT>0PTaNa)Ay#0>%=HlTQJ?(Rr0HK@K5KrluOQ9tH z4@Azw{`mYa1aK4!j9LT!`#F36C6_iNB^(3R%1GmVIZNM@>}anxQrW-uG%+{v8u;RI zyt*kH-5feMRjxg+HOZ^M>z=pwxQTQ&ZwT_L)D%`VR4y*N_ZmldX&lHV6{b4aJ zI=9=~MNVGiSR}sdMJ5Ak{&ICNmYaIh0BUz6B13AGM!o*hq~1mvr6d}^$_&U0x-K@^oQDStMJV5_$A0h6tT)B{~1RbEv`IZGQ`B~NEdEl;&qKu-`*%;JHJ zG?S#exVwX+gQbfZle>dG7%J{A_24&iaSVLj%=3WhH;M~L>VfR_s7!jQFPY>Z&X!C< z+`?QyK0zTS5ixE)VF5ugF-|6aUOrJCUQr%C0WLmaaY0^jetxDu%LB}4&K6eUn$Hye z91ByEdhq9@U@#arOn@8WY|XzB z*h0We*Bs5vA+9b`4=|GcF$D+5zu5jSb@gwEs;d8cR0oG^1^i|Ubu z0E_P!7rJqU0=x&{)P;NuezlM)dT6A_V;U;h#lk&}?$zH^(L zgp!h)nv(K9#(3|u{#9<^-?~LgLP~$<4m}eM1r5_*9sjq$)pr0nJ{A+!d#oGefScqu zu*h#*{Qxv${QM@?jT<-q-U6_&Z`{Pe#ly!m{w{y_+_;H_je|=DxPf)^CKg8gTevvb zSOBbR4&>PPark6!YnVB`eZYSQH|ldq)4)?r=V-hc^LM3#%mP|5ceU?PZf7>jL0Mi= zhycq3U4-#>{GYK3&1Maiw^+!t?Y<|+NOAMVO>Ar|JS=PsSB&b&Z(`l&lhwG5ZRYfV z|83Nr?aw7oHSgjy4N%OOI}7YYGrzk+0*J09AjcvH$N+eaDc)036z8#WZK$Mb+|34u4{D)xD|0Y=f$#20J ze+wq}hhW~n=R*2>E`O5zF_)mkhSH<8r<(rEaC-YQldY8(L>Jp@*2p72rI*(uq=V2G z_hrBRiT3Od%FWA|G1;h5l1iUq*nV0w(Z$Yhis$Q0Bvl894X?)RLo(~FNJC!?XLU1N z-{Zd<mE0G6+b6Jzt+^sb~BYi^FOIK&N1Bc3n18g8X)rrdqQx|rntjzO|ej~mzdh^yL z)a6luEe5~Qr)KW3bt-%69Y)=$PBK>jtF=K$ldS)!S{2q6AfaX-wfz#fRcs_Z_8XKw zJ!rX}rT1N`vz1ZOKc*&q>iT=?-gikGd?07$TSJUExc^epHRjUDR>kH8-}Dm<^oKsC zF(XJLE{b6*kvEALsxgZHty$n->c-mo*An1L|Fu}azgJND#P!c*eSOeg)QGVO_HS=7 zw)O|)&*<%mI;@`>^up&mYWZz4?BD#gMlp7Wv5%Z zFtp>>w4bP?95Gfj>i6R1^%#HHuKzWU-?p0vm$E-IxHeCQs)J&Tx8yhz`COYFhG3p1 zt?IW2{byUX>$O+qzyfd^|8^zDKfoc0W{iN+znS-LXRNw{M*IwRQ)UNu4cjM6C!Z{q zPVaxNn6CwbYlUo?hBQe>^@XiTK%bCSjs)@^glsk=SV;O(cR{Agf8Gy%dd^=aEL{N> z{8YcluvzHKL7&fm7)`18wY3pCp1jrzc({g>+1pexIvRZacznzDX z;Lfi;wAJ;XGuN!wtT+FyYs6>7dIztc;VwqDhRSOW{(theM!WfuVQ93j1?{UPx@0-U zYQ9#FH9GEhhkE~U+P4GS^dF2E$@Zn$iy~{o7!U9U7$`8BO8?@6etqGwuNZ)-_+~M} z#`l&64zJNx8GlmmwGS}d#pwMsX!qXv&c4s53(^qZ&*vW+#eEnpP5Y12zxX|X^f4-q zW5)O!-nhIbPR0-)VuZRTX8TQy5#2XS=HEKPt^oJ{AipNz7q6R2la4G+N1f{)P@nx( zQU4{qC9)Km`CI!JiZ6z~gs0v6d4}Mv(n+O~$!_oy;mNXBD=yn;Y`=uZfSf+f4Nq8K zR!=>XzCCx*v+F4{n|I*5=tq)yDR$lF2i^XoW#F{pc50kSeY@i`(iIM=)?MXGZuP~B zF3y9OEn(I13PKUVqKD~f$kDYac8N<&p7Jb7d3A^*tbYmHuLJv#>WJg?3UJde?D#XR zVEqde`e+hXl!OPCmoc3lJvYA04H`c9yf~@mH(xfBw{}i>$#c0i(LQs~P+NT&&`6BI z@@u^UpwE&K!<+i2-OV%UyHtMXA22+zuK*#9ztVVgs)-WlHud@tl~`2=ekOc=Z4Q#U z4-O>ayE*Q`20cmy)7q7a*TkmGtUY74)leo7rWphyhC}{Ed@J2$4xzkRcXx6d+0fQc z&D|-sywcHj$i!!!uW7;S4JSK|9p17T+Y%6q!YL>3)VyOfWY2e=-?NlFN%~+zVu$p7 znNQEIonL7DNh6f_0EFYAyEQ(l6yS53j`pU!Wb@PAH1;26eLq{WxFu1vjN3m$|6t;X zdfN$g?j|v^>zNd3Ua}Ygt-yN-F$Sr4a?4R)(mPCe3b zm+0H^oJ9G~M;_=B&a5~2A$hIu(H zlDOg+O$NV$?CnGHj!bc`0HNu_XHoL2i{pd$loM(mPq$MA_KwU|>p5b&Z~oC8&8R~z ztSHa+dU@(|zyNV-G3ye9e$d!qft&t^h^9_v5S`Q)NZh+cISYqVBb795d<5ztCH(zmdxXj43qh49Q%Fv*GMrOhEm_GwsLBsh0|Ci2SY@JtB ztg`Q^SfANxP_g3XkiW;#_;`olr(G4TJ(G)#z(9sQll=pMQgZ!FVt?)L(fIV7Md3BQ zl7w{0JwlZ=J5DC5i)F1V_bq8?1O+vGL7?RLO~DaRVN4URev8WfxAw4l?<4m}Q?^wrDM4&vna^#md0ABbIOtHDNDWcQ*pp4?YsGQ1Ydrr_)_$uxH+t zI;sAXy2FdDGw~bnPn2#5rx6ihD|%+V8C%`j@R7BbJ#kN`J+TI2a`}a`BsrxPk_(`5 zy_?!2#@|-Sm^=nFRn(^aLccF`F7K~&q(|;(MnC!-V>#W<^utb)sZc-2)-tp*v209^ z`|;rkFhvxmlRF8P*zpi&Up;y`_ncXr^vx=V4@ZzOC>|EqW4D=x{!vl&3!2;_-??UO zou&31@gg)kaHl2v0dgN|O~to_8d4-EU2t+e{=uSrw~ZZ7mU%+_$po{bA zauxA ztUS)FVuHxUd~z*;@7=U>C`X2FTUgO*oMmr_R{>#tvDOyoxkj=l>i3yBuNWYKJKZ#rJ9Ro$)!eaZV0sTBD=~cztT8)+GfA(W zMd02<3QRdf%mr$*fNlB8Mnj{ToHoip^zoO8d$D_)-~7IS{p=>^bVFk{;f65Z?+4J! zfDW0C)(Jh!<(FjtRNOJwt@I(HE5Ld2N(J6RZ3<5FgG&{9_H;+2qXz7;``>0lm8xQ@kN+e_M6?;l?CBrYNggO5}e)oBc9#%Wtkt_ zu*pl3FIkE=Pt@zeI**bRvpToA%=pKn_)_xBH!Lz*;3(sSrl=MzdD}^YX3MPXTDR1M zwv&8=sFabZanr zzNHV8eTF=GHgfMdyXy=rA}T*QoQdm25T{9cCEV>v9o$(%AXnv_Ag=d*fp7mpW(46C**Cb;JI)c}*Jx*)Yw8WQ z7DK3)DjmJ8qur4+4Q1m6Y`v|q=eiI4W0&9*+V7OewhqT5`-Ncq)mZC>JmNunf!T~k&Ah@tORyvbJ_m{lq=GI2?< zQkmIXgfm>(?Gg4M!okr|nxnp;0?e9dWvY7o5QVn)ztd!IF|aPVJSj2bSTfb9SK-{` za|`rk?Mw38Vdi^~SEMrA*gxTZ6gylZ2AiQr|$GQ^qIbB)ti=55EHu{LEq|Fc5(zOALa(sQj*U}VgXj&H*!ND zjsef)2PPFwM*&VF8^|+l zL^MW9$~%hohdI37$SE5)(w1ulI2NnBL8e1iDW8|+-AWMg&M1p>nx1K1LiIZ22-&uY z*fA}0Ol?$`IC)8%)*hZDrq056z8)9R`71tTcFwk$C$FZUjSK1%|KWE9V6MK%@(MSR zRDSlnA8qBX9++0YZo%DcDTc3iOfcarqfb0#8*c&>a$3z)S zKjkegaVP?CpFGJVTL1h;$fiYtVA?{VJS@21p7~YF>mNEZ!Dh93ArpM^)|R;M9|$^X z(2xbUl38Hi+dXz$B9R<1f3bT8S67SgZ3y}{bFka@aD_BX0^H2Dyj>i40aWa?q+*De za7PkWiQt}AS&P|?+o6-)NW$DVoBSIVFdq2rO{b!gvDn07Sup5XbGHusW~{xOQ)W=Z zOMS~QMM}IzZ~s#@`X4Ge4n<{tnxbg3&GN6kzK>`jH!kGe>2FvTPk80Q*8>FY&u#KizA}YJF?C1}I5iB4pNOn)2TdcMC`KKwIp)0eomZ56mcMfX_$Y&bh&YLM<$vU&JG8#;W;;Er0?vgHMdpAl@Sx2LI z+cG(Ks8mr;8L~5?>0&n4PyYoyxqhgH2RBI;^sbCIB~NJWSN|&O4{FJOsw}=X!W2Dt z#_cxotOqwKObM?hCjktBqetwE6L}urrrPTb+cTb4YD0SDB_WZA{LM(Q8vW1fd$)$` z;w%#@hD~T|!|I!9OD!wdqPhhulTwR6Sc`(!gx4f$)a^yc)~jwYyMGa{lGW+1>h4@J zHmHZ&czxM<7UQc$5l-MF%>IGNZWM-Npr#0gqcPqSVfyBL@` zIlaZVG+EmWul1;Qb~R z3zZl59Rx=1M~f9%+mpgLUm|-2rD~E*mVi&q5}k`&x>bJ>k2}gRl6^1t?*lJN1$+_w z#J|e*S&mgqPV4O!!+>EZn&@^=i^+nZirU8}gPGkKbf;`rns#C=D4CvZW?Ah%&_02H{UAXpe%|>{IBX9*B{CAxqX;Bg$PAx+q_i68JYIUV87Vl)=+qW%k6uv8ra?L%#QUp-p z#3jZpiqcjHMxhX{n}DNe)>4>8auGGBo{vr4<}>=q8=xVhE$%eCcDO#Ts61!#!ke=t zE>93+aRJv_rFPcp7S^Fgtdx+9p=pSlYc2D~-M02r0M_1}A%m0}IC4o$rI#M(hgrLp z$#SH90G~lJ$lI9Dd3VCXtrz}LSm#g)uQFG6V7|*_TNXrnpFxt8(=TIl_si58?hJ~; zzBKZl#y}4yoOICFZ=`1mu|zzO_}TCc*S{x$xPPWsg-tTr zP}LPol;}OB_Z~U}lXOIw-tHx2-E4I504B5#RN*h2AZ*x?sm%U0#V`r(`o!_Nl4h9* z1l_W6Ht~W4k7|R>Ldm<0+{Gp|T}wK{nLjM__{-RAqr6ZgD2IC3FEYG>DR&f8h>fUrgBgVfz1 z705O?;Dd2x8lV1!i1-r9kmP^jey<*Vqy>6f}Fh+VNO(5CFGzFBnd!bqQl7qi_h(zOT2szz=R z$c?xqD+T)$0~f}di_3GS&(3U5-Im#+!47m-RrDF~I-lU`l&YBW1VTmfX~{pK@| z3jTM|t+evxl1%?d;}YJHCetzd%PT-D`m!hO1U9{8FuYEgU!vDzrt`4i0js6XlP{e& zi88N!_!&9C{~>?IBUyRN7X-xbLdJ_U3JRTh@qYRMzOA-Ikj@9}KRVW|wUWs#x z)5q00(+4uoE4Gy(V4BBsnw!x4PPZf6T}z8s-8DUNdOBQLf-MJRvp%SO>?Gt;qi{{# zrrF3uuT_!~iqNNxL6^3dBYRM?c5lO%yz9Lcwf|ue*J7R15Xb6>C&XZVLCVlA(Pery z&XoyST@~1q98L>fuX$@dOmB^tYMbFf7e@2!c+^g~zBxpcf6x1RB6KRQb|wtg`1QHB zA*vk)mYX10ve$gz6Rj1bz_s&z|Rd;=-AwtD}iVH z{=s%WuHr~xSv+`mk=UL!W5+p3i_a=qZPR{W>?4|oiH(vghk3) zC+jZ~5d%@q(sr`X@o*bjU#-!z#v|lM9K>qLMOB)v0P123q~kve^Q~~(#zAy?aeGCU zO>;fl0hR)bZx7)-EBCo?BWQfTdGn!OJ=^V=5I4VHEh_9NM#`+!mKn#nTx*@&Mc7*T znuKm#Fsgy%TNbTtT8W*PwNUfwz)Z2^;3TMRk;=)hi8ls9AQpF$Vp&X2Jjd6fj*ljQ zsi37_Y)?D~@7a1vw!@I6D^{MZhiZ&UIZRa{d`o0A^VPbvlICAXyR7&HY)8~(sCrl< z#M?c9WqDK5-0`id_wpZnbRyHKtvZP9t*P)G(l?!C&aX%~{%-qW?Vbb(u`oM7@s!Zh zJ^9;>@o45;krOU(p#~DTMxx6)S*Nd1Rz9(>8?b2e)7yf+FCQFo|yrzWgh@Xgq+ezStJj&N`7un;EqGgG-i(Uwl%F zu;&Sd?QY@>#K|S>P|=YQBw-!=%9fah4$W=tQ;=Oa8UKilH}2xKw`ER?n-zFX?Z!Em zeAU+UyjZlpaSeJ2ME@n-z@dD3eQ1zi5-#J=GKe(t6X|+y_{0>sJMA%IoAtKIsRU_y zS7#5eT$DKV>${zIFt3Hg=?xK9diKkX@_| zdkq_HL^+>=B{>$n8W?q|cRm>B_*l2vU|fnuOyf>@#>uF@-!GU*Wov@bxJdOK7G0Rj1gy*^0=UJyljeM zDySZ$TWpd$W&q7B9H6F9(8gAHN`y%vz43qWH3PF5JY{~E8G`dr+rs`Xt)O{0i;XO^ z0RlVR?pH``p#g7HNq}~hIhFP6`~DX9{Vj=jpEBQ-&DWrbRe)>IFg4*PJj|$M?0<@m z(x>;Q-H3?!(ZEo+21nAu4E0>P;+=;-Ki+ZcRkndoq7v8ty)4KdN4$O!a$o71K9b0Iew zg4%QG3DV`azQsca<(SC=zZcxZ-7`e%_rNbBq{JsH9`!*BO3JM7a8Cc)<;!DytFB@? zJOxHmpe;^1d^Ua|XftR8ol+>eq*IQH5=pd4g@r{oXZOYjP+DI*3RSvlHQqb=e;=4} z4)@H9w!+*kPv_6lFCI1&zq9JHx&p+OOG+@0$)DdVVyg(g+;S4iLeMM;dP?Sg9vHIU zQ>SOO7ugt@#bQ(!msYoUIAT{=O=2wZw!*7$(^Z~}=GI{(oj+0~m%XD5N;JytCztTU zI=r!PfQe50cqqrtP+ARLzfnI=?BnSkB&?96Og%9<&B3K&(xvv%zG4(F=lyF`C4p4X znNGy&h<$fd>Q2}6FJ%0=`6obw|cXrqyvewY(zTrybkQGhqN$x)aa_=CI{ zC8Rcg>e<`t4efyN40D&e!?yw)SEdRIu-|F)cPWZizDXqo&uEe801M^spyRu~ttLMr z!CnbRrNx`EAitRlN;=U!mK z14K(xcVeS%RLu8TperNoK7)+C5Zq0oD{*+Tv@T+o=t=(kPjs_ayXHi-N9E{{5#3w59DT5co=~?|G}IFatl=2|!OAW9U5^PNB=iypR$tnZ^nUM|I{&1L1*s*~@IGH;h2GAr>Y-PR%- zxBcmdwa-nloXX)or4fPsTH$ob7#N#kXn#ZEMxnN2Zt6RaCmwavh#bd-VOkc0+c*%mh$|!yZC>Cq}M_I)V@&hzk+<89bLaN>jsYB7jYxqB3o@e^RH$4+9|>B zvL51xCya^?WW-eQX^u-2#!Y?*FFPD%$X4Q??$ znh1Y?*tzF$Dc33G=@*_R;rrZ>P}1;uiinY%F5%V*Pt1T!+s?URu-}Dr_h;bQq`J(+ z)VUvN{FZeFxK`0C(Lnq1EWYoE64MrYJ|=uPDX7)jSV||M3_TCL6m>?On=%HJWK_Ie zj4~1db@=Fcfd-0CsLux$r_Qx6jT?noN@~Jhl@_7F_dQ*XjJ({68QDu0hU%Wk;*rZQ zcXe3dkP&Fx-+iF)-j>6lJl|lriVaBds>uB7gpkHJJ1VQ-oVzq1gPRDlG=uvyg|0WY zkN)`l--QDlvF?1oCnmhV1}eUnR=?O_Qw)7*KO+L8Q6pCnOt+I*+dJ+$h_@~DkZk8q z+m&;5U*$EUlY24&db+~g+HiOP4>TM;Mw4|z8$&7~k2wSaU_`Zf$U<}-Gdx8~%J6Vk z*&}t*l<})&;W8Jh14(689puQyhc2q-MhmS6PIe;4s97H9D~jvx$xbjd8^q{~S7NeV z5_|}oRc?lZGzwnhk3Y}d;4IwCI!V64vjk<3B)mnkvV`wyD+>%rv*bEiVHmSZ<7==^ zYK~*ig6#S1%+f6KXd6onX%_h_AK1{-%lV<@{hNevRfJ)vK%e0YOd^x80Ifw{-EgTK zcXBZS#sw)>rU~#$!~4!E9mj{&URu zEaG^F;2#m3BA+0IuaY^E#uxWEJ?<{G7ZSH4WNDWhTcMmiHM;iYE+S97E!Z)e(KtM+ zK1ZP}E%sO7lb;jEhJywvRuwy0(nf}zojqwj=Y!Ab)C2NBVDuC-?~gL~E%3Vn{!6fZ zf3mLlBmD~db(4XJ9+&3~yF<{g$`T#?EB*|ghGHey=5!<_8wiDpI}SdkA}(e@c?;+T-&K^~SPcQV=i%<3Fbgmr2sQk-5_n^Tg%|e`RAlrDi&3mTf zyK29}X*H@qc#76F6;{AN#E^-B?7$0ynH|U%N4-dSEiM=S1W=GTb8TV7=?c}x0C5MO z3MNyWu)ri4PVC;lNwT|EKL1YRJ!$Ia7bl%Z zdVF$@)_qV?9~ZuYttwOS24-6P!5e)7MsJ#GMZN%EtBUa5uFlst(U5G&qNSvv!g4c5}ShCJ?aeBDcu~P;SQ&MFe^yn8TP)q zz=7NLu!Qlb^&Y>3PWXxJ=G}eJqHiAir*l-ysPG)ybNiX9@h6+rQUcu}KM?%3m zb4-bO`T;1DJqLBgRzIEN)qwF!O?5>t;@0H41P>qcB1sKum>dcNYEkqjf=C}39IYeBFbdAD;~AdA^jzHy+I_J$%lo;+&$~v zDV;AS?#Wtk#yqXq8N4JIl;u>*4(HnB@vU7e?=Ft%Xs?W?RUsy{e^x}$7QrA4%3iz`5+HnEo1yM8)cwb_^;%9~gZZ6X5(Q~#%mA?wNcFGEH zuU|YK9a=nF7i}u@2Z5dT)oE`)qTpK#@=O*0-O1gt){8J z7u8p$h8&qr=p&?q`BAtO$ zw?&LxacH_j`nY4}SOs4%zTwR)r{6a}f2rzDBg{<8?Dc%+Q6P7(YtLdY2}?DSijUQ{ z%ON48^K{-pG}JvZXIzVA2()d4=(L|*xR*#8caG8{YXp7NAS{Ro;OKj!JC0xT;D~*9VR4xj(CrK>tAuk zI1XnZ`K74m*bXd1>6fH}d0vemZq9Ojwf{L(dblYHi^yRGIyk)&kZ@C25^Fg!OjF5l zkKC~!lu$Gl?&}qWmOeRBWV`dsX0BEREB^7mT1kHg^d^45@E7>^sh!03Y!>L0QAOW6 zt!bg+t`<-uAE|jHSe*bTDs<_O(}`)e8!E;zA5VA-x7fK?2k3Bgn|3@@e+`dHTP|FH zGOXEFe_xkmXfbZ@;m8u7A_yp@rQOs&{_uz{2$>$^cP{|GlX40I)$-ksYx*ICVCk~o zP)V&CdfVx(TCwe1+BMFUm>W>qyb(PVSc;B8L8!EB7IMdmRbed8I&hCamn0ca<(?+h-ZHojdb=4qlgfV}%k?Unal;S?r8B$al6xgs6$nHEW@ zT>(@}A^ecb9|ib_|Mcw>A7A?U zM42KIQzM4XjZsJkc#5;S#*vZ^x$7|3l+`Of`g1C7 z18NG23O zAAF(@dNb9lk~6HPlJ`|N$X;Q+5}2uU$yG7q05s^LR2n|v(X&rPSUPqihZd)voI+^U z(*_e_tx)PAwE~2MAEb*sXbU%!PxIg2-7|hm-xwVxJ^zC4OQ33HxS0s)b~!9Hw^zXO?<_iO54}`Bnn$5{KBvN8BN+WpA-m))dKX32$(g4 ze`N|iZ%N>%j-Q*kTq*vBp-GSEdA6{rIeL^N%OQ=_dcUIPa zGI80VrS4%1V+gL*qq>(yOozWv*V;u?RMZiEEXPf?DlTEtTM>8G3JR3rslQV;pXTud zQp8yu@#(gps37BqbXf+D3dSxE%RZZy2?w}*>m%CU*6%boh7f8~mR9@h!@Sj!DoN}L zJ32~)>H(bQMv^`I&Vmk-I$l}>oszlk@O5q-DbHc4vb&!*1hFGto{w^t?+cgmpZ4@i zr&B#fFA*~bAI5IZS?4Y+Ld<=N+6L}I)w-IycU>Cn?vyJ+OQ)i%7X+XXXyns1sxk_k z!yEDL{oY3VL!~Yfjm?y*dZ_LOo2rW2jkmw+3rWW0w3JU*+yR#q@X3NE8+yYS(1NDy$jUD zMErXCav4T5Q@7(O3b%UPnmli5{%OPz=E8rckO=5UWmM=gQaDkT^Jaba&^bv|$f>Yx zOK;ksO4jAflx!$#k5N}~%+;X&aw>+_dx{JZ}*<9Eoar}*v3KNZ<(BT ze-+Tyw=9AN6H~B1(Hv*9TjQzD>`d@ zx1|_2f7rZuX!4;{_mhX#3)giKXhn@~-x|Vt2w}`-_i#hJWkj#b9FOg11gVo?w8ELS ztgyD`>>-WX@rl?g^#0G3kFfAPN_fdwN*n;o~-P#7| zh;)a%wGLP|C}m=dnjiTkq4dBlwp7vTY7WKja29ow{nI=h8fkIR*%4@kEviU*A&~7b zhIii({bsV@+qsF_D5UX|7dN-oud?vy^Y@s?Zozl}NR!sD(a&!gbuoFjJv8pQI#g;c zDJ`o%>f@N-kf||>k#%z=am?M%mERM@*1OVHC_LKYT7ig5Tl_4)`rhB_v}AW-mtNCZ zPi;x5=SyD=L7seQzFAGMY>GkUEORKE4Pd+6Kd;BxlDk^tQp3-xT{TAayzRSpP6{SX z)MNmQBtTFkg1>BRazRp5%TkF^ywQ{)O87fUg2>H6y*2pYp-E+<5ZT1EtAzTw4yQ}3 z;NiE9r`tCqM-j3nX^gOr_MJ!`ky?%xNVlx_SxCkAjEiY8)imKD$_cGQtnLeZ7OTzOvJP(f>)| zeHJIvuT0*T&NWR5v|c8G0y{@^QL3sVx-n>HPuKQvFR5b20{auuL@T}X=9S9u=6kzv z1|b1x0Ftr;L0XZURuH5sTzxvR-nHQ&-0$o-(9u#}R@koVunA^2Yvyow1I!B$5Ocfb z9&30i6+C?&XY^c1VjlL6Z%3;prMgvDs`90Kl81bKqvRfX-hWDwyK+`sxH8I6exNl# zY!}4tVKwUG8qJ;oc%{+6;hMUe^#URCInS&vUj+9}VT6}M06V439INY-ort%K0Y=3? zBE*&i-_~-DGMDc>d=9Zhd4Iji8rliq%PC5IIf-%zcDg-rcofJHM`t4&{2*;-bIzf)h({@-Ki;G^1cEH zc`aiP>=(@|;UkAjcizAl-c>yAO})DSouKugY@K*E%}N_dL$BIW#9@l3si>GRP8hsA zn!+$N`;#}%vo;~$$n_LVhOpWk=(6x(?X!Nl0lPu!_ zOOc2V;F6+kS?qL;?k66{`O*)x>YP4YY_b-N2=L@tLDo~5s!Orr*|&rnE3Bomdi9T! zK`DK$-JLvP{E7k~y`L>EK-o6oT1rsGW>CS6m~ zATlLoS#cLpF!@_Ocyv`Fx;oIUyQP#_a^vT`xKGQnt}#bX&sWuksPO1dNUHF5B0ABK zvlNNbimtthbKmgoemMMLivLnQ=7~Bc`_O$9j(Ion=T;Aj@$H3dE&QLhdYM1A`l-!q z*==y9UvS+X^;XFKneV=5>zY+?SVhi{8z01PkF(jbBFN)}j(CI*m#p~S??t+|?)dao z)lR7O)o%Ed4|?l@;Tz6|?wJwXNvEdUm8$vN>?F|!K()<6$_RDcj;$$=?Djnm)^9%z z@3cWjY^5uFSc!y@a-dKen#q1F|4yBa8M=eHONTSKu?gCA)IPWfh&TI|IJ{`Ia@w}} zM&2i*vT+pk)}SvMwV*?J@|m;?NIv#faS_3leLw5iJ)$Ef2-mS-We-}{KvP3RZ`_%4 z`+K`Jb+Y5>LK6wG%y@`>IfO4Oz-T(ja3r&;2Wq%0x8T-vVPq5+J}bK!n`~@V)?{ct z$y~-6PAbjpp|(zXGrUL+2zyv~PpiHr?Nni^mrF$lJixQN7I&V-Wqy%8o!*0E#E>fw zUq6)da%iv1CDq=wtkjf9ShGGUdwDpP7JCrcwfQ|C0S#Ouw4l#28BB{ArIgq{fS?Jt<#xwQhDNx}a+muo z$unnz!m;PH>W{0liLjC$N@ixx)EXFP*|sC!zbdNycv&=I@hGnTky(gMDuOCngxLO3 zY;?PMyHr(JpwT%HrZn!pMUh-EkSCOiK2!dZYZFarE_J`T#H8N7J}s2Hn+U~MH8@lC z?Zl+hlCwp`-u@0@Ziu0gv$YLfJ1Lc;?R1k)j#0T&Y(%M7rKK&Z(p_>KR}>=gLJpj+ zIi)hL$8Cgki49qmY7Zl|;Cj)Pah0^M&Gy+B?M9182v+FEy0iF{x~j@=62qPju6a|M z$G$H$=2H=7p>3DdECcdz2W+Iw9+S$8RGKZ;4r~O3sMX)wWVVQ^j%rm4`ri zyKYJys0f1fGgpF}cE*l%#popAer+`QE|j?H_(_o{>Saymk`*_ZNmXbt_(q`5_?#Nm8^MO*_X^>jR{WX|Egg(RSRiZ)a4$twuzpVwN-7^Y@U;D;cs{KSN2scx zuG^`!!|2%bZaN3Ggb9>Xk=;6aQ)vGJeq6QZZ3@eE?F&;|*f-@bcZ=27>n*zie1S_Y zdbMFbDowf=itte2ju zXDIO;r+D&#C1&+olG2QR_AYgGvu?+c@0^Uv`srmK3RtvR?F1vyfY1r=?2qo5*@Cj8 z{q&h=zRgXWiet`$1K~+qKItv&aZ7*xqQWjd*r-53NDZ86J;|Kb=pk-QR>&Lp_=*IL z)2ZjisNJM~%28<+?NNo(;$46y1fAF#QYsm^GD*E))~$GD^$VMY8lB>{;Wo>f;CZ|oXHT;KRY z31L>}<*`W@OubY$olA34SRLo|2EtA6^5ldNI`C8^Ea=lFt0#})w%k!PtzT%@Y{ zVHsGi`N)C7vn<@#Zg(%QD$1FsG9fH*Tt(-gm7&f8;1o?*|>WHpth6^zpFP5vK@ zy;oFIUH31H4=O4GVxxqjf}qlS2Nk6k73obtY6t-Wp<_WndN0y^kR~Oe1V};%h&1UX z5J*DkJtXvAJiPxgzBA6bc*l44)!w&zjkV^Qzc!Ix;)3dIea$0_yt|rYjoZuihxPAz zAE@0@_E2i8+KV#EAN5dPlKJXx)rxC}RRSHs;Z_LWD2K*BH1s8SRW{LhbF=M46a9Ed zwUjuYEu16W#oX5`($;ffV8?8vLp;?`RJeYy9P*pLBkPK@=SGCX; zhAfSH8+Ue5A&c2NSfPdOApKg3D(vlqi;E|p&c0?;sdq)4=nI#n2Qs3@oYed2S6{sD z_c(*u-Dk)Uyr1(h;I!i`mnQ*SQtm%L&*6{8zVobjwKauu{nsmPgSvdmcK2KPSTf+` z502gLmxtYqkWry8X_b&&GL$VM?UR$9+rbP;XG)9X;FL zWGZ)Wnz-pPe~11`=RyOTYN{V>s(!Vl^Hz7_e!3(jJcyEiJ;+9)B~F`t%3VSKV)7Cv zdK?9>kryQpcHlSvJO%`u7aViq3{GDPkL=xMr}7a%*Gts>?(+9ouCF909zM)_zMos7 z08N@lJ}&qocPpmul8~7eUZKC>GU@o4v(Ojn=eyp;P|`m0$70L2O;=IJTbBUO^xK_E z{gSK+=#k2zm_LMXjhJv1p`#cHXJL7r3Ax{S=_O*>1&%>?2SaMHxAvuxRYm9GKxLi` zk-m!}F8 z<5G&#^0^G&DB2vVr(>=P&kqyV!cb*XGD8C64u$f{AvU4h63qy*c?Hz0IvQ0hM@cxSvwi=5AC{S*Apb0N-m1>W$zYXMqZJxy3H$g&(j>_All%? zAHFPB3WV*5cE6*Iqg>&?vPIFyV3q(APk=gqDIxA^v$<1j&T=DU#l%s5OFd7{0SfC6?(J03My}ziBX6#7ASAeRKGTLaP=kNS`HeK-F$>)$ObXWfTe-}3 z(i!Rhdm&fcc`vTq5$tQ7qKhApxjuaJ1RlxZjn?x}?7g{d-Tq0NJP>dmu`ydcnoWy8 zd$9dXT=>J)UuXUdagh13f?y?I=%gy_gV&Xo$_F&o)~;^_m=2Up^)l7{#Plb@-pBba zw$L;jvPK~;%`Tka>e{fumiFC;C&VSGzJS>;VTJFSM$o~G5}r{+ zlcep*+7JJCJ(>ESdeZ#=QcoEFcRhJ=UQZ-dDH$cC{ZgCiz@dto2HChzb;^-W0_(I* zG2Vv7onC`==h~TFtI@>c;B3*5iPoazTA**ARIMx~4E?)FjO=-g&Iw#R8x=Knewf0Y z?eraxZDS9Z|3^FylX=~b;U5rW7;2*uqGmJYt958KEGpodH|u=a^4G-m_+fT3?7By? zVUCxcjECZ6J<{vTHjdF0!X6QG9NwjjT__XrI#)e7JZd?TjhxvK$+;mtbATjh@f%IHrTf&VdS9(95oVtLu-!+JLJ)Bh*X@I`srH|aiGu32 zQAohk=q~ob8CW0pAncoVy`o8dhBrxzlof!l-1okH9I zQW5+j5Alf88;Eo}#B5S4{lB2kU9?tTJBELD40Cuw4r7r?q_D29Af+#rDeHbmg&|;v z61~dtzjUM;x&AY~F@ace@y8|~iY?f%M6b5`og-GWTe~2E4!$#n&>Yr|8gA+0agg5A z>3_3ZHkQoE`*@sL24^B$#?6tg(KghN`vo8EO;fl2 zotZH35as$ycLyac+R&K+%*yK@>AS}xnrs4V1L{Le*WnI`dNIXtb~c_}5(q1?;QVd7 zdxgi%;5(glp+BromtJNVu9#l2mf%{6Qp%wg6Y8&Q+(TBE>vsDvLuaC21+@ z=VUiLCP|7R(Np9End+0LE!?WRfbja68ElC~XpDI9c$$bb37skgdy@0K4oW+b9_v>u z(gxV;9}NKP8t!yRZF+25J!zeswE&`1yLN-y_Y&0Dpp1SE8rM|>_HRmbDH~}YW4CQ+ z?{hNZHoN@Zzxp{sVYQVe+0=ObsD+-Xm>zkDc`bWcp@Em$Hg)yH5DGV;gT1xqKG|OY{N#Oqo80lYY@bLfP17 z*CUjYr9oT2rnoZVyVLnmaDofEyD&kQI-oTrGtNW4>+Fa<1B?Fx_=#Z98BFp4likZp zbKwA2USyh`-+J!2vU?_#94ja=(`w0akXO*He8$j!T7 zP>aUT#o^6f7##h&!-_@(rEGe~x0F#jZ9-jViEPy0owwdx?ukEEkeOqzM6}fV2J?EA zlt8(#EJ{n*bNBA62~WhN%(-)5(H0hB@KMELzV@(`#mSXTf-?BueC9WT5vM(l`)sO} zmwKPFuXz-tyXC0ca9=5DNW6D)I%1pNxXnjH|A)XxqlmYqXncC(M`sgPpVd2x)2(X* zr9Jm>l1uhxf5r-W#{mc!P)lDu;F`z)W-K4b7sjn|_2N$nwXM!idRBV`FzR2I^fVWU zX3ct?abUwru=TOC<2N|x)^ShS*Kc8Fm#m}a+lFBM((p6Rf0F$*bw6Ykx@3{b(zP7e z)<=Vn7KzUdrA<2&2V$wyl^fdM@TX(@FNQS6Er_*Ud}juHQC|4{RJRuiX^9MmO0C88 zY4;ba0wltfki$=8kP0730kU|N$UjIzJSLbgALI^TkH37y4v?kOQ!&=%El*%?Wjb}4 z1Vj;W8rxWS>TeTE<$@ddS{f$xwL~f`Z(%XQ`@$3x_rGPf^5wzv=|^QHKNdp}M7&n>pTY@f}vqE;zI zUbuziLepype$~_<=95v;V2d%_jA!woi0x*O*jHfO?zfMJ3529ZU2bjx=eMFYB6dFf zvV8r7gJqT+T(>j3kgS~Cz=xw!E273Gg70FG)dcj1-#5XSPNF%;MuD~l{Sl~UmPbMUr`EJ=;4!$8~mZX?l3XZ+c)$BH`@ zg!pP3Elc+Fjy*}xd+BtQhw-C)*L1^cKz9mm)|~?vG=tHR2lY6&tosFII#?{lYY5i^ z*3BML9y<5rc~(Gu5h8HFkiyt?-rM?uvHm69fX-T26s%KYZ|seQp5Pa%#KhhzB>29z zC!W|>EY!?20(n6wkclHF>QFM0DKTGQA(z=}vMx&l&NZD|0$j0Uk8aD|4NEENWgk5E z1~TRm4^_B=^X|ONSlIk3W+rOK&IAt%?p#?Hz>H6x7&!tPldtkgjdvm0+xwIk&&^e_ zY{N6>H<6}hzfrZI`RxyJI52XBv@bt)7uPxf7VKsZ-;)m+;1zT`XgtHE#L#kYu!-q* zrZXaaA1UwHjb*HRw)Ncu`afw_>T50s+7|Df_~SKgRqyY=GVAOO|ChBXB)rFf5EKyf zCuu1k35kp*%6*x>E3!Jea;K1+>q5alG{ak7Dz&jTNxcn9hI~ezb3%=4Gc_I744MEM z*ApLEOnLfllZuyQ4af8Ge!;xm*akaIxtz`mE=%36ZFLo^3te7Zs0XF=Z)n?pXckZJ zN0jJdV&X?BV^*5k{p(HDK4!R0uXl3&HOLD8L%+T^Q5b`18mv?1H0AcwPv-nBpR5`j zuJQnYm#!{g*zWXTtP3K2p({LI^Wnpq8K5C?medb&ugE6V$`sRH+ipL(c0k#TC_(r7 z^UvDVTYQ`&bui~RYdRSWg!4jv-lL5QrQo5iV4zm}<%%ak*JiRYuOGJ7p8KZ`?kE;H z8%%M<-?o9r+lKgF{Pc@@A*%S2uH;I|Lwr-*Xz-#?{aWKmL7+m!=HTw{Ue&zpqhg1` zKrI*IFwl9;i@vs%d`+>_fWE;yU#{zEbqu?FbJC8# zZ!6m1x5lwa{g%R>i07e}RtclmHJIKr&pcT!vqkb#zj$WqZ1o$PspNi#O+2@h^hW{! z`xcvtD;^Js?UtO8vUsHbFg~lin=z$1>^tP%0VaL=s(X&%d~?TaCLeqv5B}2RR0mckU(GzM6WL{*?L2`i?_x2i1eg*qaaWT=SrosNYi>& z(c$env@)@QXhhYTKLcjyTkUVa)?3hs@PXkCyHuLOM8Z1fwJK7D#)>T@QQeccvsVkJ zjR-Vtqy-x(;8YDfyzx@&8Xp~%6bwkA&ORaj@+FKI~n?ek6~)7fOz)PIn$IB zn&S~^tBG zcBQV8wQ@C~c2K(~lcq6sZDw8JL)s2DgDoMe--*|2@V!Z=lkOm|_u?6x&;X#79vTOe zgQtvA;L>?2XS%p6l)JWW^!Jb#eq>(zbj0UpEqB*B*B5o{?1E>giR;x>|6wPw;F&{D z)=``xks^)!x=|b2AI??vmB&Tl5wbf26$q_0p<-d=$1`Q+=|1!OWNX6^U zJXfk_@DJ(aRd&lLCYRZ3Ga^YER`a(IjJo~HzeEDf8uPT8sxx)m9u2h^k;?3)a(|k8 z6>IPL|9VMTZ0CS4@hYfqdXWDOV0+Mb->Ln?R0JWsIF7$cepU*XUV9rZX$>LcBH?nE zusqHlJzsZ|=~R-Y_0VTIxebbU$2hEKSgKIshGmcS<+C;u+&3QMe)Z)pzR@*ltXWl! zHl2RYbWv^K-F$O&{A=eHgCNO4dO9DEMR7%#(uVfWW219P-!LdcWuxojck?=EPnMUW z#FmW=&uW~R);PStrwVCD?Ql`f9YYXlUz~h&!BQS`phDU|PIe%Pp<}^gqf$Ye@D==% zJ_h`Qqnt18$_4^Nqfha#XSrvSr-{xLFPVB4E}JsPuQIq>b)q)sJQajx2qO{8>GdXa z;as?ifQL>dG;|zyHGM=Etni>CI5I$4XB-%O*S*r(QHAACEI$-c5l!o1Z}K*KG@!XO?BZ;459gPQ51aV4?PxvAf1 z);@kohK|`TKz@sp5+N_^NU*l<#A>axL&1-ruA9StT?(no99>yJ zJumy968)pjo_)1B>G?dEqV2@A(`TTrs~$|`-Yafnc`?}ehS?;+f<;2$M_YZ+XslTs z3&tTwJgR8vOXa}2ZSnM2nU;1#uT#!kR0+=opME|gIKd3vK7=rlRQiOv)Fa|v?i1kD zzz34wNrRaY`k2H#IVarw$J}NqviI=qck{dbZf+BI9BSR3SnN{*{(aWc$G-xGGRR82 zBxw)L&!fxDec>aY3qpWF5vX0W0nw)N_p_5hhu5uqDb@kdHvgG7vj*_hbzX(R9uRcO zsL!R;VvSOaF0Y8IMEmr&Y0$ZhT-{?+DKE_Ro?kNMm#=cg!>d~}+!Z9myB@Mg+s`)G zBnW>itN}IU3HfaBi}Zm(Hv)Wy?Enn(HxES-DsF4n=d-_g-kuLDZMs<D%nwD(9t3gybfk6Xn7 z1^yY<@^Eqvav^I!Fh=yJ#fuAhrdp?VjfvR6W7pZ4koFDVimjd?1xpc3-k98LnTsB` zNkpJSu+4Ues7*dXcJwOO^t;9pz&VPBPPtpb^eX9SGPps}x|2^h(iGPPRn7NE$C0&za3&WO;Z_T?c z^+RqQs@KkYQ4zsS`6-raw>b7o>_|;ddcojp@f{VYbO!%X=_yW=0@E5xzXLNCJul}bO5C|kxx*UYdM!dllJP8L+&+l0_h@wP|qEN)wi3%mb^P^^{@f z^mjdD`Zo3ZU%CqDZ%U;fcM~Bvi7bIIC;uh`9=Mzi2u-SZNY(aM7ga=1#In>l)^64w z+e@4r;7`o)OD2-8Su0{91Cp;u=}b$^X4J&gIqcHxl2a1f`%TjY$;0{jfW3rvY*htq zuJK6Kl=1X6me89y?1^$^2yX>XN%Um!FH@;|M@=3*n$I#PpNcc3We9Hz0>n;M;cSQ@ zMN2id=O#Y*nwK!@J|vQn8$GuKuuJY1Ik)Z=$WI&$ie1FP%Q_YowfuXkFA~ zeeZs@{O#!iysGpI?KeZhiCFvYn)8%DmRK{)oXE}8l9bPV)%mYiWm;DqvH4|d#hBH> z3xb+9H>?BVO^59zn-P5^RalW9<#;aTV&=MC=fe9{qkEsJ-bwa8RE)lz|9r< zdg^h+l0g}?f~|&;&`I<9PX3`*;d$zS>9Nk)J!+WjA8q94Nw<2hczOEuauQ!5|C$sV zaDwacgN4s~FBqfB>oDfiFV+^wPPCx&gU*Gbh)qM6r%&YRbntDBdD~GFJfCzZyO~)_ zqb$__91_IAH#CZnwU!A_P$?%bXsV+*iHP*Y^nJ4g!5XK`)_6);P?=Mm{A@4Or1b(l z3))C%C5j1FnE>PfQ13en{*aMXFsl9Kp!(L3ukN#>nto}wgWo|=^PwL-i=Oa~9_Ws! zHznJlPTJrr_sz~8ySXdL?L9nI)}$(RpG)MVUnBT1|I*Zk1I&#kj#(c(%gPK?^4E!# z@=WCyhu@W5Qi~C0gh!GZF4GE@W{GnATiMjwIH0VIC>LQi`LUu4kgGc9*)R@89rV4E zPSoMcl2TTGhg>M&xxW=zr&9DisJrpHAt-AmQyioX zP!}CnGiMzi;{O%zewWK&^llh|o# ztqBezTeo09*}`|-lq`!*BW1q#w|ZPgs6&k=LQNUJ(HP@jE1b{c7Mzrdds~>ghQ2_6 zE)z-xsB-zv_ogOm5bjGj0KW%&B;7Lxn}aE%3Y&C=@7)DGKhWtTY**Q#9EH(L^u9j3 zjsrWV3au$=FA*NLu(rJ_YgxFWL6jCVIzSn}R^`e3+I$1pK7jFc@EW^MOB*xgP9mre zTdiNhuD=*|s_ZH@^^_*)haax+cwhdu6dc*%z0(=*3qSp$#F{}-WD}u` z6?z5^dDVH2KbvoR;;+-_wXj5jbGa0hy6kH&Zpqc}zk|rsd{wW+C^G z(Im$o2DHz!7(Jb?B*kvdt$0VnU z((b}74E^ZM2Y`6Op$Awa+lv9=?QR_>sm>P~sLy0#x7qtK)&`e;$5>kdo7s5bsIB$ogb)zds~6*TF6W+T zphnTo`_`763YqS>ur%XxrHtxinZ_AyhwXv|=vd68*OIMi%F185gvE|AL%H+B1rVtK z+|@fK)5wt8zjbIhxFjX|DX9aG_v(*v6ATKgogB1?Js7X_nT(%j?JLi23jpo+09hR? zml4`_n;z4iJi!_Trpfb=sfIQ~re3qT-dtE^12+H@BjRsYQ5Sufps0Oea%Zo_7#7&E zuYVucA&vd4aoDT+#{|Jfb%GHRdGvcl(i-`0O_^;vyY8Ii#1d7qV^I4fsUTH`J28q4 zz|hQ@iGM^V0O#_jt0LR8N_K|-?5jIGa>Nl8Fm|ndiKb1(Lo!(F#&)c%4tEiLoiA#l zdP0a5i)jqwsJpaVnaoI_84S|klZL8_X0VLzEA^tv$=AiEFLq8qOf_V%X9kf6^7ySKDiI8MI1x4f|bQl}R<4PzSS?{>z^1yPj5h zb3*g3WqHh@teNh+N)F2oo}jvMhx5qZa)P+F_>x+?+sy+_odH1?6_Ku$1v2#0Vx6;S z!{))*vw_c-N9U#8Dg!ialIXW(6H@H>y8Zb5jjsB=Jn5L}Onka+VNH4*5n{{-xsGd3 ze%(@|)cwH3i9nLp7m?euQnJ%4DHcqq$T+`EvtE~@x7EH;S=iPcs0w`TtZ=8F?e0Kr zz~&5;6ml!U)&389(tp95@!lSXBqd)tu$7swKdw74IHpUCe4DoQCy%i(VReg1>bZi< z8D2zJBsd3s58JcoUg7s*t~XuEY^t;T6Em%-*p+Etz`}A0*}=|m{A3}=VLsU;>cYh< z3r=P47%ta)#91+lXev6Xas--n^kj!+dhxiD8Q#P_kYq1%Y)2FDWGX)GbuG;f;&Go& zQU|Z1@0Y-PVfbc}j^GtX*@F4AH{;*=x4*T$+0`DN38}pKGG5T47499rrTj(*uT|w& zZ;yN2x|hY%8lRrjP&4Jzi!O&oVEI0n89yLnx3k;=)H?wM-A3}da7%rG9<-pFV60rn z(#|@UAyQ42PV0@lsQU5l$a9=-Y3xMNW8U(F+zv{STMd=Anp>7C6jzk>w>vPw!`w))Vq`jTR3+g0JO0T*3m! zh{_uF+bTV|aLt)tSk5MoQy`FC(R|0%(`A}5zoO6YT(q~@G8+wIn(0<)?-(n0onCN8 ztUc90X64akX);@W-Im?tpqcZgulck6n9vL}&q@(b)b?3KX7+1wT>~nLJ?zs>B}Iwq zC2)PUu<@^bj9nhPi$-txz&=Haq;LME6Y`vWHaUWxJP4Fofd`MkU`N1?>WM!Z6FG|K z)Q$I;6?MygBns{!R(L31j%d%fBweTA$!W995L@pP+sF4CIZv)l3HWXju}v?Xgtrdn z9waw{{)8avyj*N`V{8p7GkTiK-5WsO%WXPiPhhP{Z(8oZ@AV%E(gCYjj8od1OI)49 zO0Gx9K0TUaB~-3WIB*w-Z-D(MwOH;*1aPaaa9!(q$VP>8Xpf)zJB^v6QeIxx1>|JK=%}bex5~Xqzd}yhp^i)P z{1$a#WV0q@aAOF>QJvzX^!$Ud)U?;6ATuugi37%JQDPTmU1rwnG1HFS&CQy_3iaA3 zIjkahM|C)94Bm#mPNI`{wD3aIIJ`oR7pfMT(h^Tn5y}3tC&#+N@>T2dv6f)1&O9|( z=nwsn6V-L-9>EV3?z<6i?7r}Vn*$Z5o}Uiee~O$E7lq&Iknr4V&FvCzsqvAc|J23+ z%`+)+f}*C>qJ@_g0g->CtJ=k99AXV<;aKy96>G*Ht=Z;%N0{87xr_bX=bcV4H}tq@ zJHXPd#fx9`)beLY>z?5v+O5%I2Njgjhx7I5U(0Ih*Rq*AmfJC;8V7jo+U;!TKHRdY zQBvXmAiIB|#X6rTl-^H7bGtjWtPr1=@#Qe#ZXMcYnHoIV4vLyhe!x_yI;hBJyOv|7 zCVgr56iyi348fyFVwdpEyBE)i-_tph0M(`BX3N0*v+Vir|I<7SQC|Ib`sDbu<%5nC zSdnxd!obPWhkb@z0ef)kq}Rb*_6GR<*uLlOgote(ExGO1-=t11Dr6|i*Sk~wNIc9X zIa`f;K1sXMSSV>DO60yf?Y*JQ`}rpOP$y+;Ta}Tu)b6glD1sM~e{B6u=j^!eLA6vf z!IXzlG_arvb(B`zwb0FE-EjE5)LhCGxDr)E5_|~|$$=Ip?jXTvy{JwQu$+D9VtO7LIa)*^#fLA@ZuEHPo3J$+ul6Qk2T1!M`O&NoDwSE55 zl@Yz|J#O|*y2U8&#P6oXVGHlWF2MdN%%ep2RcbVJv06F#vtFY2@hQ)$6x0m+$1o&iY1lY$wA7PxrAr3$&`s{Ro^}|3VkzcLDS?7MhS4# zAG}O=;<(pZf(9PGI4@S3I?h)SAQvAN5PN_&7oCOkpPZ+Thq`n z7t_g7JIXess0$98!dd936AHL9SbSJQ94opJK)mkPynB5PqI?&`ZznY>=G=Phv*lwA z1VgT-kM;(?arMaj-ne*BBr&hwH~~1R9+GbmKx26Xq+$q7xH%cS!Ma|XBS{BsK+Jni z|L5$6_kXrsi1_p)65_u)@!CEXwmcCW8EX9cwaoFuUg&jb?p$e)R{WwIHqGhQ3+UW- zMEY@IRBbmkJ8svM}Ut zC$YdIFj^B&grMCPt3PXCk~)a@dD?B^*=K62n^ zIv`6fKZCtrM3ER=ygvjm?t^mr#=X=?v?jLuv6eg}HMDR>*UZR{i75nKPnu9kzIONH z<+Fx!>}@ANF4qJ`sJ8dBK9@-?A}cyzt*#H1BZllMe7&~!gWq~lf-(%`va@U4>#s>O zZ74Ao!Cdcd2Pg_DT)`n~F)}e9{t^Mr&$TO!3R~&M zC!0)I1GV(bl_nWFtd%t;FLh5=8l-Rrldq4$q7p4)Ic~YpE{Eq@qZREH`UI~30H%CF ziWc}^8w-l$AzCgDud03ZWI%=g_iJi|9nY*Hur-ivabosm864M{|BYcQy`-Wh+O6tWPa;H{v^N zPOJH(cLo(2-@O&}S|vO#OxFyb%IT{n*-z+_yP89Urvy-~?|G){$P0y2h?3ydP6Y**!>s-QdPS0=o$JXn#L^s(b6(xVV#h=-fa6C2%j`P|0B#9Z? zo6R@yE-}=5dmZPDj6OoG+4P7i+Hp0ML_GHYL-6zRCMvB!Mk~!`ZW1M%#Dg`-Rr|yo zzcPqk`}H!9Y*A)K?Rdk&xPQ-l977C&f#U~xOYoXTQ;WZJBe%|}$o)9W`QyB6r=Zoy zBXDwP`(L_AEJXAF5AAvMztNuE2Xm`ciy2|LU`A{9JsJ_HCLi9K<;=Q+ji3Bhx0PVZ zU5)6jSkqLlRaCFQj_0ii_2rOk4S;b@VK{dR+nV(K9RzrCoLQulj~j7BV#3I%n?k^ z;_uTy%t@bgFMWO3`96&^O%?x$WPa)SLnA@E5j6qYknyK+P9Dy)#PuIiT;pp>CSmBc z2MeM@Fw3xx+?}_k&?*~s&1>>5ibUg#tMv$%B7O~d0xRyd^^u6<0Q>TA<{v8Vg(iy} zw}9RCzzCuG+&m8hjHZ!TLY8ZTksGzpm;zYwzy}!6gjdLUz@dc+ViZfNMgP&x>c&`p z*Ua^3)5CkxUY7=vvB5_FT53(MKklp=dfDs0w)p%w1#3jGiL+J9+I5ok;b5G~A7d%G zp?9h+Ab;i&Wcd+LZrsVDdk5y+BfJs@z$t@QtU&8FE0xyvT-1fFevO<5V8(`Pq zBx;FX6S#>~5kDVj3Y#`)>n^s3J-lG-{?8LC`rzB;9nCtS4=;ni`>G<(8_dp$%}ztX z)R^+XFWbS#!l3X3y1fJt3AvMnJ=+Ps(O|ya#>F5@vC-a}#uJFM6TU&&IHB zX^VmNdgAvRTHT=?4u*YcA!^`IWc$+hmj@r3Pi+ig-&e1m4{|x~fKPmfERIpYZ`E+Y zDSKjPyOb;yxBr8~{l8!T!|1B5X#9VSZg%NkI)Koc=BeJG!r6Nzuu6ldauYazt$|dT zo8amzJGz#PNLhL>>%pMAPdHRv#?v!^{YI6sW>kv1jSD2+o$RS0U z)Zm&~KmkJwtnvZU|aoDUC0F zy{Z~G)LHz15z{%*Eqlq_Hp*#bi#%!ag5(Y-5bN)P8b_Dd#*IFAf*oqy8Zm)hlN0Wx z9hw^5nBwPBVwW*|E07JEEOye)=-f&mDyv?5422np*uS<*yDkfCGl5v_xNu>fcosb- zTnj8c3apx_kOJsth|^38s!D#Isy!Lk|7LMgVebU=@mGp#xRxtqPV}=v^!`YhpVE}O ziTlK}q%~{afUM+)Q|rA2T%z|X|1EIbB+3WBtG0G-`8Ex)A>{k=NW705GzpR+A*fv#l6fSz<=3fIZ-Z!Jerb}b2h7rq|u-3)CA{@!~sAAPL~O5XFaMVuqx zsXXL!q6xpu*$@#^u9qS+5c_Gsj@fRCQ|Z20x*#%DReUD@cP*`Ad<1R!J&~F}3nlu% zUR03&a7bkuJ}@xy_dR&i|Am%DBl!EJg`wV>wCuOpb8dw7dUpdDCWu2xdCS-h+n`SdQjh*Wm_eaLCJ`0)nmqJ>BmOp=N19z7hVT)Zh|N%LAQf1eD4i`OiY^Q~_7^K?l_;ML%0*x*0j8$8cCp4kzr|@(C0;T{ zJTJ5eyE4CRzdu}=K|5PoR_hFj32^v>LMRo@ZJVa6rfv9A6^5C*p~csJ@4fm~7fv1R zVaℑ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 Vz<(p~-w6CS0{@>8p!q%g{{R%RT>bz6 literal 0 HcmV?d00001 diff --git a/automation-ui/backoffice/src/main/java/com/yas/automation/ui/AutomationUiApplication.java b/automation-ui/backoffice/src/main/java/com/yas/automation/ui/AutomationUiApplication.java new file mode 100644 index 0000000000..d664464ae5 --- /dev/null +++ b/automation-ui/backoffice/src/main/java/com/yas/automation/ui/AutomationUiApplication.java @@ -0,0 +1,16 @@ +package com.yas.automation.ui; + +import com.yas.automation.ui.configuration.BackOfficeConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@SpringBootApplication +@EnableConfigurationProperties(BackOfficeConfiguration.class) +public class AutomationUiApplication { + + public static void main(String[] args) { + SpringApplication.run(AutomationUiApplication.class, args); + } + +} diff --git a/automation-ui/backoffice/src/main/java/com/yas/automation/ui/configuration/BackOfficeConfiguration.java b/automation-ui/backoffice/src/main/java/com/yas/automation/ui/configuration/BackOfficeConfiguration.java new file mode 100644 index 0000000000..f52b38a4aa --- /dev/null +++ b/automation-ui/backoffice/src/main/java/com/yas/automation/ui/configuration/BackOfficeConfiguration.java @@ -0,0 +1,6 @@ +package com.yas.automation.ui.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "backoffice") +public record BackOfficeConfiguration(String url, String username, String password) {} diff --git a/automation-ui/backoffice/src/main/java/com/yas/automation/ui/enumerate/ProductAttribute.java b/automation-ui/backoffice/src/main/java/com/yas/automation/ui/enumerate/ProductAttribute.java new file mode 100644 index 0000000000..eb7f347f01 --- /dev/null +++ b/automation-ui/backoffice/src/main/java/com/yas/automation/ui/enumerate/ProductAttribute.java @@ -0,0 +1,18 @@ +package com.yas.automation.ui.enumerate; + +public enum ProductAttribute { + CPU, + GPU, + OS, + SCREEN_SIZE, + PANEL, + BLUETOOTH, + NFC, + MAIN_CAMERA, + SUB_CAMERA, + RAM, + STORAGE, + SCREEN_RESOLUTION; + + public static final String BASE_XPATH = "//*[@class=\"fade tab-pane active show\"]/table/tbody/tr[%s]/th[2]/input"; +} diff --git a/automation-ui/backoffice/src/main/resources/application.properties b/automation-ui/backoffice/src/main/resources/application.properties new file mode 100644 index 0000000000..03ea3b50ef --- /dev/null +++ b/automation-ui/backoffice/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name=automation-ui +app.url=http://storefront/ \ No newline at end of file diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/JUnitCucumberRunner.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/JUnitCucumberRunner.java new file mode 100644 index 0000000000..2f739b4d63 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/JUnitCucumberRunner.java @@ -0,0 +1,22 @@ +package com.yas.automation.ui; + +import io.cucumber.junit.Cucumber; +import io.cucumber.junit.CucumberOptions; +import io.cucumber.testng.AbstractTestNGCucumberTests; +import org.junit.runner.RunWith; +import org.testng.annotations.DataProvider; + +@RunWith(Cucumber.class) +@CucumberOptions( + features = "src/test/resources/features", + glue = "com.yas.automation.ui" +) +public class JUnitCucumberRunner extends AbstractTestNGCucumberTests { + + @DataProvider(parallel = true) + @Override + public Object[][] scenarios() { + return super.scenarios(); + } + +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/configuration/CucumberSpringConfiguration.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/configuration/CucumberSpringConfiguration.java new file mode 100644 index 0000000000..133ad39f67 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/configuration/CucumberSpringConfiguration.java @@ -0,0 +1,10 @@ +package com.yas.automation.ui.configuration; + +import com.yas.automation.ui.AutomationUiApplication; +import io.cucumber.spring.CucumberContextConfiguration; +import org.springframework.boot.test.context.SpringBootTest; + +@CucumberContextConfiguration +@SpringBootTest(classes = AutomationUiApplication.class) +public class CucumberSpringConfiguration { +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/constants/CategoryConstants.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/constants/CategoryConstants.java new file mode 100644 index 0000000000..ada9a8f630 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/constants/CategoryConstants.java @@ -0,0 +1,9 @@ +package com.yas.automation.ui.constants; + +public class CategoryConstants { + public static final String CATEGORY_NAME = "Name"; + public static final String CATEGORY_SLUG = "SLug"; + public static final String CATEGORY_DESCRIPTION = "Description"; + public static final String CATEGORY_METAKEYWORDS = "MetaKeywords"; + public static final String CATEGORY_METADESCRIPTION = "MetaDescription"; +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/form/CategoryForm.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/form/CategoryForm.java new file mode 100644 index 0000000000..42af2296c4 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/form/CategoryForm.java @@ -0,0 +1,46 @@ +package com.yas.automation.ui.form; + +import lombok.Getter; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.How; +import org.openqa.selenium.support.PageFactory; + +@Getter +public class CategoryForm extends BaseForm { + + @FindBy(how = How.ID, using = "name") + private WebElement name; + + @FindBy(how = How.ID, using = "slug") + private WebElement slug; + + @FindBy(how = How.ID, using = "description") + private WebElement description; + + @FindBy(how = How.ID, using = "metaKeywords") + private WebElement metaKeywords; + + @FindBy(how = How.ID, using = "metaDescription") + private WebElement metaDescription; + + @FindBy(how = How.ID, using = "isPublish") + private WebElement isPublish; + + @FindBy(how = How.ID, using = "category-image") + WebElement categoryImage; + + @FindBy(xpath = "//button[@type='submit' and contains(text(),'Save')]") + WebElement saveBtn; + + public CategoryForm(WebDriver webDriver) { + super(webDriver); + PageFactory.initElements(webDriver, this); + } + + @Override + public WebElement getSubmitBtn() { + return saveBtn; + } +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/form/ProductForm.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/form/ProductForm.java new file mode 100644 index 0000000000..7b1419ca1e --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/form/ProductForm.java @@ -0,0 +1,148 @@ +package com.yas.automation.ui.form; + +import lombok.Getter; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.How; +import org.openqa.selenium.support.PageFactory; + +@Getter +public class ProductForm extends BaseForm { + + @FindBy(how = How.ID, using = "name") + private WebElement name; + + @FindBy(how = How.ID, using = "slug") + private WebElement slug; + + @FindBy(how = How.ID, using = "price") + private WebElement price; + + @FindBy(how = How.ID, using = "sku") + private WebElement sku; + + @FindBy(how = How.ID, using = "gtin") + private WebElement gtin; + + @FindBy(how = How.ID, using = "select-option-brandId") + private WebElement brand; + + @FindBy(how = How.ID, using = "isFeatured") + private WebElement isFeatured; + + @FindBy(how = How.ID, using = "select-option-taxClassId") + private WebElement tax; + + @FindBy(how = How.CLASS_NAME, using = "ql-editor") + private WebElement description; + + @FindBy(id = "shortDescription") + private WebElement shortDescription; + + @FindBy(id = "react-select-2-input") + private WebElement productVariationAvailableOption; + + @FindBy(id = "react-select-2-listbox") + private WebElement productVariationAvailableOptionListBox; + + @FindBy(id = "RAM") + private WebElement ram; + + @FindBy(how = How.NAME, using = "optionPrice") + private WebElement ramOptionPrice; + + @FindBy(how = How.NAME, using = "optionGtin") + private WebElement ramOptionGtin; + + @FindBy(how = How.NAME, using = "optionSku") + private WebElement ramOptionSku; + + @FindBy(id = "sub-thumbnail-0") + private WebElement ramThumbnail; + + @FindBy(id = "sub-images-0") + private WebElement productSubImage; + + @FindBy(xpath = "//button[@type='submit' and contains(text(),'Create')]") + private WebElement createBtn; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'Product Attributes')]") + private WebElement productAttributeNav; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'Category Mapping')]") + private WebElement categoryMappingNav; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'Cross-sell Product')]") + private WebElement crossSellProductNav; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'SEO')]") + private WebElement seoNav; + + @FindBy(id = "metaTitle") + private WebElement seoMetaTitle; + + @FindBy(id = "metaKeyword") + private WebElement seoMetaKeyword; + + @FindBy(id = "metaDescription") + private WebElement seoMetaDescription; + + @FindBy(xpath = "//button[contains(text(),'Manage Cross-Sell Product')]") + private WebElement managedCrossSellProductBtn; + + @FindBy(xpath = "//button[contains(text(),'Apply')]") + private WebElement productTemplateApplyButton; + + @FindBy(id = "product-template") + private WebElement productTemplate; + + @FindBy(id = "attribute") + private WebElement productTemplateAvailableAttribute; + + @FindBy(id = "iphone-15-pro-max") + private WebElement ip15ChkBox; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'Product Images')]") + private WebElement productImgNav; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'Product Variations')]") + private WebElement productVariantsNav; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'Related Products')]") + private WebElement relatedProductNav; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'Add Related Product')]") + private WebElement relatedProductAddBtn; + + @FindBy(xpath = "//button[@type='button' and contains(text(),'Close')]") + private WebElement modalCloseBtn; + + @FindBy(xpath = "//button[contains(text(),'Add Option')]") + private WebElement addOptionBtn; + + @FindBy(xpath = "//button[contains(text(),'Create')]") + private WebElement productAttributeCreateBtn; + + @FindBy(xpath = "//button[contains(text(),'Generate Combine')]") + private WebElement generateCombinationBtn; + + @FindBy(how = How.ID, using = "main-thumbnail") + private WebElement thumbnail; + + @FindBy(how = How.ID, using = "main-product-images") + private WebElement image; + + @FindBy(id = "checkbox-5") + private WebElement catMappingLaptopChkBox; + + public ProductForm(WebDriver webDriver) { + super(webDriver); + PageFactory.initElements(webDriver, this); + } + + @Override + public WebElement getSubmitBtn() { + return createBtn; + } +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/CategoryPage.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/CategoryPage.java new file mode 100644 index 0000000000..5e038a6a02 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/CategoryPage.java @@ -0,0 +1,27 @@ +package com.yas.automation.ui.pages; + +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.page.BasePage; +import com.yas.automation.ui.service.InputDelegateService; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.springframework.stereotype.Component; + +@Component +public class CategoryPage extends BasePage { + private final WebDriverFactory webDriverFactory; + + private final InputDelegateService inputDelegateService; + + public CategoryPage(WebDriverFactory webDriverFactory, + InputDelegateService inputDelegateService) { + super(webDriverFactory); + this.webDriverFactory = webDriverFactory; + this.inputDelegateService = inputDelegateService; + } + + public void clickCreateCategoryButton() { + WebElement createCategoryButton = webDriverFactory.getChromeDriver().findElement(By.cssSelector("a[href='/catalog/categories/create'] button")); + createCategoryButton.click(); + } +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/HomePage.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/HomePage.java new file mode 100644 index 0000000000..ee907180d3 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/HomePage.java @@ -0,0 +1,33 @@ +package com.yas.automation.ui.pages; + +import com.yas.automation.ui.hook.WebDriverFactory; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.How; +import org.springframework.stereotype.Component; + +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; + +@Component +public class HomePage { + + private final WebDriverFactory webDriverFactory; + + public HomePage(WebDriverFactory webDriverFactory) { + this.webDriverFactory = webDriverFactory; + } + + /** + * Click event to click to item on side menu + * @param item it should correspond to value on fronted, usually entity name with (s) + * for example: products, brands, etc. + */ + public void clickToCatalogItem(String item) { + WebElement productLink = getWebElementBy( + webDriverFactory.getChromeDriver(), + How.XPATH, + String.format("//li/a[@href='/catalog/%s']", item) + ); + productLink.click(); + } + +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/pages/LoginPage.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/LoginPage.java similarity index 81% rename from automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/pages/LoginPage.java rename to automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/LoginPage.java index 826aeb0e7d..00ec5b5021 100644 --- a/automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/pages/LoginPage.java +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/LoginPage.java @@ -1,28 +1,17 @@ -package com.yas.automation.ui.storefront.pages; +package com.yas.automation.ui.pages; import com.yas.automation.ui.hook.WebDriverFactory; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.How; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import static com.yas.automation.ui.ultil.WebElementUtil.getWebElementBy; +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; @Component public class LoginPage { - @Autowired private final WebDriverFactory webDriverFactory; - public void login(String username, String password) { - WebElement usernameEle = getWebElementBy(webDriverFactory.getChromeDriver(), How.ID, "username"); - usernameEle.sendKeys(username); - - WebElement passwordEle = getWebElementBy(webDriverFactory.getChromeDriver(), How.ID, "password"); - passwordEle.sendKeys(password); - } - - @Autowired public LoginPage(WebDriverFactory webDriverFactory) { this.webDriverFactory = webDriverFactory; } @@ -31,4 +20,12 @@ public void clickLogin() { WebElement loginBtn = getWebElementBy(webDriverFactory.getChromeDriver(), How.CLASS_NAME, "submit"); loginBtn.click(); } + + public void login(String username, String password) { + WebElement usernameEle = getWebElementBy(webDriverFactory.getChromeDriver(), How.ID, "username"); + usernameEle.sendKeys(username); + + WebElement passwordEle = getWebElementBy(webDriverFactory.getChromeDriver(), How.ID, "password"); + passwordEle.sendKeys(password); + } } diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/NewCategoryPage.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/NewCategoryPage.java new file mode 100644 index 0000000000..37cd7b4321 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/NewCategoryPage.java @@ -0,0 +1,55 @@ +package com.yas.automation.ui.pages; + +import com.yas.automation.ui.constants.CategoryConstants; +import com.yas.automation.ui.form.CategoryForm; +import com.yas.automation.ui.form.InputType; +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.page.BasePage; +import com.yas.automation.ui.service.InputDelegateService; +import com.yas.automation.ui.util.WebElementUtil; +import lombok.Getter; +import org.springframework.stereotype.Component; + +@Component +public class NewCategoryPage extends BasePage { + + private final WebDriverFactory webDriverFactory; + + private final InputDelegateService inputDelegateService; + + private final CategoryForm categoryForm; + + @Getter + private String createdName = ""; + + public NewCategoryPage(WebDriverFactory webDriverFactory, + InputDelegateService inputDelegateService) { + super(webDriverFactory); + this.webDriverFactory = webDriverFactory; + this.inputDelegateService = inputDelegateService; + categoryForm = new CategoryForm(webDriverFactory.getChromeDriver()); + } + + public void fillInAllNecessaryField() { + createdName = WebElementUtil.createFieldText( + CategoryConstants.CATEGORY_NAME); + inputDelegateService.setInputValue(InputType.TEXT, categoryForm.getName(), createdName); + inputDelegateService.setInputValue(InputType.TEXT, categoryForm.getSlug(), WebElementUtil.createFieldText( + CategoryConstants.CATEGORY_SLUG)); + inputDelegateService.setInputValue(InputType.TEXT, categoryForm.getDescription(), WebElementUtil.createFieldText( + CategoryConstants.CATEGORY_DESCRIPTION)); + inputDelegateService.setInputValue(InputType.TEXT, categoryForm.getMetaKeywords(), WebElementUtil.createFieldText( + CategoryConstants.CATEGORY_METAKEYWORDS)); + this.scrollDown(); + inputDelegateService.setInputValue(InputType.TEXT, categoryForm.getMetaDescription(), WebElementUtil.createFieldText( + CategoryConstants.CATEGORY_METADESCRIPTION)); + categoryForm.getIsPublish().click(); + inputDelegateService.setInputValue(InputType.FILE, categoryForm.getCategoryImage(), "sampledata/images/category.png"); + + } + + public void clickSaveButton() { + categoryForm.getSubmitBtn().click(); + } + +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/ProductPage.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/ProductPage.java new file mode 100644 index 0000000000..8bae66f8e8 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/pages/ProductPage.java @@ -0,0 +1,216 @@ +package com.yas.automation.ui.pages; + +import com.yas.automation.ui.enumerate.ProductAttribute; +import com.yas.automation.ui.form.InputType; +import com.yas.automation.ui.form.ProductForm; +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.page.BasePage; +import com.yas.automation.ui.service.InputDelegateService; +import com.yas.automation.ui.util.WebElementUtil; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.How; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import static com.yas.automation.ui.enumerate.ProductAttribute.BASE_XPATH; +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; +import static org.junit.Assert.assertTrue; + +@Component +public class ProductPage extends BasePage { + + public static final int NUM_OF_TABLE_ROW = 6; + public static final int NUM_OF_DEFAULT_ATTRIBUTE = 7; + public static final String SAMPLE_TEMPLATE = "Sample Template"; + public static final String DUMP_FILE_PATH = "sampledata/images/dell.jpg"; + + private final WebDriverFactory webDriverFactory; + private final InputDelegateService inputDelegateService; + + public ProductPage(WebDriverFactory webDriverFactory, InputDelegateService inputDelegateService) { + super(webDriverFactory); + this.webDriverFactory = webDriverFactory; + this.inputDelegateService = inputDelegateService; + } + + public void clickToCreateProductBtn() { + WebElement createProductLink = getWebElementBy( + webDriverFactory.getChromeDriver(), + How.CSS, + "a[href='/catalog/products/create']" + ); + createProductLink.click(); + } + + public void fillGeneralProductData(ProductForm productForm) { + // General Information + inputDelegateService.setInputValue(InputType.TEXT, productForm.getName(), String.format("Dell-%s", UUID.randomUUID())); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getPrice(), "100000000"); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getSku(), UUID.randomUUID().toString()); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getGtin(), UUID.randomUUID().toString()); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getDescription(), UUID.randomUUID().toString()); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getShortDescription(), UUID.randomUUID().toString()); + scrollTo(productForm.getIsFeatured()); + inputDelegateService.setInputValue(InputType.CHECKBOX, productForm.getIsFeatured(), null); + inputDelegateService.setInputValue(InputType.DROPDOWN, productForm.getBrand(), "Apple"); + inputDelegateService.setInputValue(InputType.DROPDOWN, productForm.getTax(), "Value Added Tax (VAT)"); + } + + public void uploadProductImg(ProductForm productForm) { + scrollTo(productForm.getProductImgNav()); + this.wait(Duration.ofSeconds(2)); + productForm.getProductImgNav().click(); + + inputDelegateService.setInputValue(InputType.FILE, productForm.getImage(), DUMP_FILE_PATH); + boolean isProductImgPreviewVisible = isElementPresent( + () -> WebElementUtil.getWebElementBy( + webDriverFactory.getChromeDriver(), + How.XPATH, + "//img[@alt='Image']") + ); + assertTrue("Product Image preview must be visible", isProductImgPreviewVisible); + + inputDelegateService.setInputValue(InputType.FILE, productForm.getThumbnail(), DUMP_FILE_PATH); + boolean isThumbnailPreviewVisible = isElementPresent( + () -> WebElementUtil.getWebElementBy( + webDriverFactory.getChromeDriver(), + How.XPATH, + "//img[@alt='Thumbnail']") + ); + assertTrue("Thumbnail preview must be visible", isThumbnailPreviewVisible); + } + + public void fillProductVariants(ProductForm productForm) { + scrollTo(productForm.getProductVariantsNav()); + productForm.getProductVariantsNav().click(); + + // Select available options + inputDelegateService.setInputValue(InputType.TEXT, productForm.getProductVariationAvailableOption(), "RAM"); + productForm.getProductVariationAvailableOptionListBox().click(); + + // Add Option + productForm.getAddOptionBtn().click(); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getRam(), "32GB"); + + // Add combination + productForm.getGenerateCombinationBtn().click(); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getRamOptionPrice(), "2000000"); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getRamOptionGtin(), UUID.randomUUID().toString()); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getRamOptionSku(), UUID.randomUUID().toString()); + + // Add combination images + inputDelegateService.setInputValue(InputType.FILE, productForm.getRamThumbnail(), DUMP_FILE_PATH); + inputDelegateService.setInputValue(InputType.FILE, productForm.getProductSubImage(), DUMP_FILE_PATH); + } + + public void fillProductAttribute(ProductForm productForm) { + scrollTo(productForm.getProductAttributeNav()); + productForm.getProductAttributeNav().click(); + + inputDelegateService.setInputValue(InputType.DROPDOWN, productForm.getProductTemplate(), SAMPLE_TEMPLATE); + inputDelegateService.setInputValue( + InputType.DROPDOWN, + productForm.getProductTemplateAvailableAttribute(), + ProductAttribute.CPU.toString() + ); + productForm.getProductTemplateApplyButton().click(); + + // Add product attributes + for (int i = 1; i < NUM_OF_DEFAULT_ATTRIBUTE; i++) { + WebElementUtil.getWebElementBy( + webDriverFactory.getChromeDriver(), + How.XPATH, + BASE_XPATH.formatted(i) + ).sendKeys(UUID.randomUUID().toString()); + } + scrollTo(productForm.getProductAttributeCreateBtn()); + } + + public void fillCategoryMapping(ProductForm productForm) { + scrollTo(productForm.getCategoryMappingNav()); + productForm.getCategoryMappingNav().click(); + inputDelegateService.setInputValue(InputType.CHECKBOX, productForm.getCatMappingLaptopChkBox(), null); + } + + public void fillRelatedProduct(ProductForm productForm) { + scrollTo(productForm.getRelatedProductNav()); + productForm.getRelatedProductNav().click(); + productForm.getRelatedProductAddBtn().click(); + getFirstCheckBoxElementInModalList("//*/table/tbody/tr[1]/td[1]").click(); + productForm.getModalCloseBtn().click(); + } + + private WebElement getFirstCheckBoxElementInModalList(String identity) { + return WebElementUtil.getWebElementBy( + webDriverFactory.getChromeDriver(), + How.XPATH, + identity + + ).findElement(By.cssSelector("input[type='checkbox'].form-check-input")); + } + + public void fillCrossSellProduct(ProductForm productForm) { + scrollTo(productForm.getCrossSellProductNav()); + productForm.getCrossSellProductNav().click(); + productForm.getManagedCrossSellProductBtn().click(); + getFirstCheckBoxElementInModalList("/html/body/div[3]/div/div/div[2]/table/tbody/tr[1]/td[1]").click(); + productForm.getModalCloseBtn().click(); + } + + public void fillSEOProduct(ProductForm productForm) { + scrollTo(productForm.getSeoNav()); + productForm.getSeoNav().click(); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getSeoMetaTitle(), UUID.randomUUID().toString()); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getSeoMetaKeyword(), UUID.randomUUID().toString()); + inputDelegateService.setInputValue(InputType.TEXT, productForm.getSeoMetaDescription(), UUID.randomUUID().toString()); + } + + /** + * This method used to look up all pages until we find the created product. + * Currently, It does not show at first page, as sample data lack of lastModified field + * (product list is order by lastModified desc then sample data always on top pages) + * @param newProductName productName which we look for + * @return is the new product show in all pages or not. + */ + public boolean isNewProductShow(String newProductName) { + final WebDriver chromeDriver = webDriverFactory.getChromeDriver(); + WebElement paginationBar = getWebElementBy( + chromeDriver, + How.CLASS_NAME, + "pagination-container" + ); + List pageItems = paginationBar.findElements(By.tagName("li")); + + // pageItems.size() - 2 >> is to skip "Next" link + /* + * for example: 1 2 3 ... 12 Next + * >> try to get '12' + */ + int totalPage = pageItems.isEmpty() ? 0 : Integer.parseInt(pageItems.get(pageItems.size() - 2).getText()); + final String baseProductItemXPath = "//*/table/tbody/tr[%s]/td[2]"; + int currentPage = 1; + while (currentPage < totalPage) { + // check all rows in table + for (int i = 1; i < NUM_OF_TABLE_ROW; i++) { + WebElement productName = getWebElementBy( + chromeDriver, + How.XPATH, + baseProductItemXPath.formatted(i) + ); + if (Objects.equals(newProductName, productName.getText())) { + return true; + } + } + currentPage++; + getWebElementBy(chromeDriver, How.LINK_TEXT, String.valueOf(currentPage)).click(); // next page + } + return false; + } + +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/service/AuthenticationService.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/service/AuthenticationService.java new file mode 100644 index 0000000000..08255dc161 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/service/AuthenticationService.java @@ -0,0 +1,23 @@ +package com.yas.automation.ui.service; + +import com.yas.automation.ui.configuration.BackOfficeConfiguration; +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.pages.LoginPage; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@AllArgsConstructor +public class AuthenticationService { + + private final LoginPage loginPage; + private final WebDriverFactory webDriverFactory; + private final BackOfficeConfiguration backOfficeConf; + + public void authorizeWithAdminUser() { + webDriverFactory.getChromeDriver().navigate().to(backOfficeConf.url()); + loginPage.login(backOfficeConf.username(), backOfficeConf.password()); + loginPage.clickLogin(); + } + +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/CreateCategorySteps.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/CreateCategorySteps.java new file mode 100644 index 0000000000..679be25fac --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/CreateCategorySteps.java @@ -0,0 +1,85 @@ +package com.yas.automation.ui.steps; + +import static org.junit.Assert.assertTrue; + +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.pages.CategoryPage; +import com.yas.automation.ui.pages.HomePage; +import com.yas.automation.ui.pages.NewCategoryPage; +import com.yas.automation.ui.service.AuthenticationService; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +public class CreateCategorySteps { + private final AuthenticationService authenticationService; + private final WebDriverFactory webDriverFactory; + private final HomePage homePage; + private final CategoryPage categoryPage; + private final NewCategoryPage newCategoryPage; + + public CreateCategorySteps(AuthenticationService authenticationService, WebDriverFactory webDriverFactory, + HomePage homePage, CategoryPage categoryPage, NewCategoryPage newCategoryPage) { + this.authenticationService = authenticationService; + this.webDriverFactory = webDriverFactory; + this.homePage = homePage; + this.categoryPage = categoryPage; + this.newCategoryPage = newCategoryPage; + } + + @Given("I am logged in successfully") + public void i_am_logged_in_successfully() { + authenticationService.authorizeWithAdminUser(); + } + + @When("I click on the {string} option in the menu") + public void i_click_on_option_in_the_menu(String option) { + homePage.clickToCatalogItem(option); + } + + @Then("I should be redirected to the category list page") + public void i_should_be_redirected_to_category_list_page() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/catalog/categories")); + } + + @When("I click on the Create Category button") + public void i_click_on_button() throws InterruptedException { + Thread.sleep(1000); + categoryPage.clickCreateCategoryButton(); + } + + @Then("I should be redirected to the create category page") + public void i_should_be_redirected_to_create_category_page() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/catalog/categories/create")); + } + + @Given("I have filled in all the necessary data for the new category") + public void i_have_filled_in_all_the_necessary_data_for_new_category() { + newCategoryPage.fillInAllNecessaryField(); + } + + @When("I click the Save button") + public void iClickTheButton() throws InterruptedException { + newCategoryPage.clickSaveButton(); + Thread.sleep(2000); + } + + @Then("I should be redirected to the category list page again") + public void i_should_be_redirected_to_category_list_page_again() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/catalog/categories")); + } + + @Then("the new category should be displayed in the category list") + public void the_new_category_should_be_displayed_in_the_category_list() { + WebElement row = webDriverFactory.getChromeDriver().findElement(By.xpath("//tr[td/text()='" + newCategoryPage.getCreatedName() + "']")); + + // Verify that the row with the given category name is displayed + assertTrue("The category " + newCategoryPage.getCreatedName() + " is not displayed in the list.", row.isDisplayed()); + } +} + diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/CreateProductSteps.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/CreateProductSteps.java new file mode 100644 index 0000000000..97f1020842 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/CreateProductSteps.java @@ -0,0 +1,84 @@ +package com.yas.automation.ui.steps; + +import com.yas.automation.ui.form.ProductForm; +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.pages.HomePage; +import com.yas.automation.ui.pages.ProductPage; +import com.yas.automation.ui.service.AuthenticationService; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import static org.junit.Assert.assertTrue; + +public class CreateProductSteps { + + private String productName; + + private final HomePage homePage; + private final ProductPage productPage; + private final WebDriverFactory webDriverFactory; + private final AuthenticationService authenticationService; + + public CreateProductSteps(HomePage homePage, ProductPage productPage, WebDriverFactory webDriverFactory, AuthenticationService authenticationService) { + this.homePage = homePage; + this.productPage = productPage; + this.webDriverFactory = webDriverFactory; + this.authenticationService = authenticationService; + } + + @Given("I logged in successfully") + public void i_should_be_logged_in_successfully() { + authenticationService.authorizeWithAdminUser(); + } + + @When("I click to product on menu") + public void i_should_click_to_product_on_menu() { + homePage.clickToCatalogItem("products"); + } + + @Then("I should be in product list page") + public void i_should_be_in_product_list_page() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/catalog/products")); + } + + @When("I click to create product button") + public void i_should_click_to_create_product_button() { + productPage.clickToCreateProductBtn(); + } + + @Then("I should be in create product page") + public void i_should_be_in_create_product_page() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/catalog/products/create")); + } + + @Given("I fill necessary data for product and submit") + public void i_should_fill_necessary_data_for_product() { + ProductForm productForm = new ProductForm(webDriverFactory.getChromeDriver()); + + // create product data + productPage.fillGeneralProductData(productForm); + productPage.uploadProductImg(productForm); + productPage.fillProductVariants(productForm); + productPage.fillProductAttribute(productForm); + productPage.fillCategoryMapping(productForm); + productPage.fillRelatedProduct(productForm); + productPage.fillCrossSellProduct(productForm); + productPage.fillSEOProduct(productForm); + + // submit form + productPage.scrollTo(productForm.getSubmitBtn()); + productForm.submitForm(); + productName = productForm.getName().getAttribute("value"); + } + + @Then("Created product shown in product list") + public void createdProductShownInProductList() throws Exception { + assertTrue( + "New product must be shown on product list", + productPage.isNewProductShow(this.productName) + ); + } +} diff --git a/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/LoginSteps.java b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/LoginSteps.java new file mode 100644 index 0000000000..fd6fd7eed8 --- /dev/null +++ b/automation-ui/backoffice/src/test/java/com/yas/automation/ui/steps/LoginSteps.java @@ -0,0 +1,73 @@ +package com.yas.automation.ui.steps; + +import com.yas.automation.ui.configuration.BackOfficeConfiguration; +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.pages.LoginPage; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import org.junit.Assert; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +public class LoginSteps { + private final LoginPage loginPage; + private final WebDriverFactory webDriverFactory; + private final BackOfficeConfiguration backOfficeConf; + + @Autowired + public LoginSteps(LoginPage loginPage, WebDriverFactory webDriverFactory, BackOfficeConfiguration backOfficeConf) { + this.loginPage = loginPage; + this.backOfficeConf = backOfficeConf; + this.webDriverFactory = webDriverFactory; + } + + @Given("I am on the home page") + public void i_am_on_the_home_page() { + webDriverFactory.getChromeDriver().navigate().to(backOfficeConf.url()); + } + + @When("I enter valid credentials") + public void i_enter_valid_credentials() { + loginPage.login(backOfficeConf.username(), backOfficeConf.password()); + } + + @When("I click on the login button") + public void i_click_on_the_login_button() { + loginPage.clickLogin(); + } + + @Then("I should be redirected to the dashboard") + public void i_should_be_redirected_to_the_dashboard() { + WebDriverWait wait = new WebDriverWait(webDriverFactory.getChromeDriver(), Duration.of(10, ChronoUnit.SECONDS)); + WebElement userDropdown = wait.until(ExpectedConditions.visibilityOfElementLocated(By.className("navbar-text"))); + String dropdownText = userDropdown.getText(); + Assert.assertTrue("Check failed: User is not logged in as admin.", dropdownText.contains("Signed in as: admin")); + } + + @When("I enter invalid credentials") + public void i_enter_invalid_credentials() { + String invalidUsername = "invalidAdmin"; + String invalidPassword = "invalidPassword"; + loginPage.login(invalidUsername, invalidPassword); + } + + @Then("I should see an error message") + public void i_should_see_an_error_message() { + WebDriverWait wait = new WebDriverWait(webDriverFactory.getChromeDriver(), Duration.of(10, ChronoUnit.SECONDS)); // 10 seconds wait + WebElement errorMessage = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".alert.alert-error .message-text"))); + + // Assert the text content of the error message + String expectedMessage = "Invalid username or password."; + String actualMessage = errorMessage.getText(); + Assert.assertEquals("The error message is not as expected.", expectedMessage, actualMessage); + + System.out.println("Test passed: Correct error message is displayed."); + } +} diff --git a/automation-ui/backoffice/src/test/resources/application.yml b/automation-ui/backoffice/src/test/resources/application.yml new file mode 100644 index 0000000000..590b38e5d5 --- /dev/null +++ b/automation-ui/backoffice/src/test/resources/application.yml @@ -0,0 +1,4 @@ +backoffice: + url: http://backoffice/ + username: admin + password: password \ No newline at end of file diff --git a/automation-ui/backoffice/src/test/resources/features/createCategory.feature b/automation-ui/backoffice/src/test/resources/features/createCategory.feature new file mode 100644 index 0000000000..d80e9c3356 --- /dev/null +++ b/automation-ui/backoffice/src/test/resources/features/createCategory.feature @@ -0,0 +1,12 @@ +Feature: Create Category + + Scenario: Successfully create a new category + Given I am logged in successfully + When I click on the "categories" option in the menu + Then I should be redirected to the category list page + When I click on the Create Category button + Then I should be redirected to the create category page + Given I have filled in all the necessary data for the new category + When I click the Save button + Then I should be redirected to the category list page again + And the new category should be displayed in the category list \ No newline at end of file diff --git a/automation-ui/backoffice/src/test/resources/features/createProduct.feature b/automation-ui/backoffice/src/test/resources/features/createProduct.feature new file mode 100644 index 0000000000..a94335299f --- /dev/null +++ b/automation-ui/backoffice/src/test/resources/features/createProduct.feature @@ -0,0 +1,11 @@ +Feature: Create Product + + Scenario: Create Product successfully (with general information, product images) + Given I logged in successfully + When I click to product on menu + Then I should be in product list page + When I click to create product button + Then I should be in create product page + Given I fill necessary data for product and submit + Then I should be in product list page + Then Created product shown in product list \ No newline at end of file diff --git a/automation-ui/backoffice/src/test/resources/features/login.feature b/automation-ui/backoffice/src/test/resources/features/login.feature new file mode 100644 index 0000000000..b190b3ad40 --- /dev/null +++ b/automation-ui/backoffice/src/test/resources/features/login.feature @@ -0,0 +1,7 @@ +Feature: User Login Flow + + Scenario: Successful login with valid credentials + Given I am on the home page + When I enter valid credentials + And I click on the login button + Then I should be redirected to the dashboard \ No newline at end of file diff --git a/automation-ui/pom.xml b/automation-ui/pom.xml index 02fce45bec..ea920d1647 100644 --- a/automation-ui/pom.xml +++ b/automation-ui/pom.xml @@ -15,18 +15,30 @@ automation-ui YAS automation-ui test + automation-base storefront + backoffice + + 21 + 21 + 21 + UTF-8 + + 3.3.2 + 1.0-SNAPSHOT - 1.18.34 + 7.10.2 4.14.0 5.7.0 + 7.18.1 - 7.10.2 7.18.1 7.14.0 - 7.18.1 + 7.14.0 + + 1.18.34 @@ -34,55 +46,95 @@ org.springframework.boot spring-boot-starter-web - - org.seleniumhq.selenium - selenium-java - ${selenium-java.version} - test - - - io.github.bonigarcia - webdrivermanager - ${webdrivermanager.version} - test - - - io.cucumber - cucumber-java - ${cucumber-java.version} - test - - - io.cucumber - cucumber-spring - ${cucumber-spring.version} - test - - - org.projectlombok - lombok - ${lombok.version} - org.springframework.boot spring-boot-starter-test - - - org.junit.jupiter - junit-jupiter-engine - - - test + + - io.cucumber - cucumber-junit - ${cucumber-junit.version} - test + org.projectlombok + lombok + + + + org.springframework.boot + spring-boot-starter-web + ${springboot.version} + + + org.seleniumhq.selenium + selenium-java + ${selenium-java.version} + test + + + io.github.bonigarcia + webdrivermanager + ${webdrivermanager.version} + test + + + io.cucumber + cucumber-java + ${cucumber-java.version} + test + + + io.cucumber + cucumber-spring + ${cucumber-spring.version} + test + + + org.projectlombok + lombok + ${lombok.version} + + + org.springframework.boot + spring-boot-starter-test + ${springboot.version} + + + org.junit.jupiter + junit-jupiter-engine + + + test + + + io.cucumber + cucumber-junit + ${cucumber-junit.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.source} + + + + org.apache.maven.plugins + maven-surefire-plugin + + + @@ -138,9 +190,16 @@ org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} + + methods + 12 + 3 + + **/*JUnitCucumberRunner.java + + - diff --git a/automation-ui/storefront/pom.xml b/automation-ui/storefront/pom.xml index 9a31b38421..889bc58bd0 100644 --- a/automation-ui/storefront/pom.xml +++ b/automation-ui/storefront/pom.xml @@ -13,35 +13,37 @@ storefront - 21 - 21 - UTF-8 - nashtech-garage https://sonarcloud.io nashtech-garage_yas-automation-ui-storefront - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-surefire-plugin - - - - **/JUnitCucumberRunner*.java - - - - - + + + com.yas + automation-base + ${project.version} + + + org.seleniumhq.selenium + selenium-java + + + io.github.bonigarcia + webdrivermanager + + + io.cucumber + cucumber-java + + + io.cucumber + cucumber-spring + + + io.cucumber + cucumber-junit + + + \ No newline at end of file diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/JUnitCucumberRunner.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/JUnitCucumberRunner.java index 8d9e34fb12..46b64ec25b 100644 --- a/automation-ui/storefront/src/test/java/com/yas/automation/ui/JUnitCucumberRunner.java +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/JUnitCucumberRunner.java @@ -2,13 +2,20 @@ import io.cucumber.junit.Cucumber; import io.cucumber.junit.CucumberOptions; +import io.cucumber.testng.AbstractTestNGCucumberTests; import org.junit.runner.RunWith; +import org.testng.annotations.DataProvider; @RunWith(Cucumber.class) @CucumberOptions( - features = "src/test/resources/storefront/features", - glue = "com.yas.automation.ui", - plugin = {"pretty", "html:target/cucumber-reports"} + features = "src/test/resources/features", + glue = "com.yas.automation.ui" ) -public class JUnitCucumberRunner { +public class JUnitCucumberRunner extends AbstractTestNGCucumberTests { + + @DataProvider(parallel = true) + @Override + public Object[][] scenarios() { + return super.scenarios(); + } } diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/form/UserRegisterForm.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/form/UserRegisterForm.java new file mode 100644 index 0000000000..64dcaca3fb --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/form/UserRegisterForm.java @@ -0,0 +1,43 @@ +package com.yas.automation.ui.form; + +import lombok.Getter; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.How; +import org.openqa.selenium.support.PageFactory; + +@Getter +public class UserRegisterForm extends BaseForm { + + @FindBy(how = How.ID, using = "firstName") + private WebElement firstName; + + @FindBy(how = How.ID, using = "lastName") + private WebElement lastName; + + @FindBy(how = How.ID, using = "email") + private WebElement email; + + @FindBy(how = How.ID, using = "username") + private WebElement username; + + @FindBy(how = How.ID, using = "password") + private WebElement password; + + @FindBy(how = How.ID, using = "password-confirm") + private WebElement passwordConfirm; + + @FindBy(xpath = "//input[@class='register-submit' and @value='Register']") + private WebElement btnRegister; + + public UserRegisterForm(WebDriver driver) { + super(driver); + PageFactory.initElements(driver, this); + } + + @Override + public WebElement getSubmitBtn() { + return btnRegister; + } +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CartPage.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CartPage.java new file mode 100644 index 0000000000..edec41982f --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CartPage.java @@ -0,0 +1,78 @@ +package com.yas.automation.ui.pages; + +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; + +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.page.BasePage; +import com.yas.automation.ui.util.WebElementUtil; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.How; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.List; +import java.util.Objects; + +@Component +public class CartPage extends BasePage { + private final WebDriverFactory webDriverFactory; + + public CartPage(WebDriverFactory webDriverFactory) { + super(webDriverFactory); + this.webDriverFactory = webDriverFactory; + } + + public boolean checkProductName(String productName) { + this.wait(Duration.ofSeconds(1)); + + // Get all rows (tr) inside the table body + List rows = getAllRowsInBasket(); + + // Loop through each row and get the product name + for (WebElement row : rows) { + // Get the product title in the current row + WebElement productTitleElement = row.findElement(By.xpath(".//h6[@class='product-link']")); + String productTitle = productTitleElement.getText(); + if (Objects.nonNull(productTitle) && productTitle.equals(productName)) { + return true; + } + } + return false; + } + + // Locate all the rows in the basket table + public List getAllRowsInBasket() { + // Locate the table body containing the products + WebElement tableBody = getWebElementBy(webDriverFactory.getChromeDriver(), How.XPATH, "//div[@class='shop__cart__table']//tbody"); + + // Get all rows (tr) inside the table body + return tableBody.findElements(By.tagName("tr")); + } + + // Get the delete button for a specific row + public void clickDeleteButton() { + List rows = getAllRowsInBasket(); + // Locate the delete button in the row + WebElement deleteButton = rows.get(0).findElement(By.className("remove_product")); + deleteButton.click(); // Perform click action to delete the product + } + + public boolean existedRemoveButton() { + this.wait(Duration.ofSeconds(1)); + return WebElementUtil.isElementPresent(webDriverFactory.getChromeDriver(), How.XPATH, "//button[@type='button' and contains(text(),'Remove')]"); + } + + public void clickRemoveButton() { + WebElement removeBtn = getWebElementBy(webDriverFactory.getChromeDriver(), How.XPATH, "//button[@type='button' and contains(text(),'Remove')]"); + removeBtn.click(); + } + + public String getProductName() { + List rows = getAllRowsInBasket(); + // Locate the delete button in the row + WebElement product = rows.get(0).findElement(By.className("product-link")); + return product.getText(); + } +} + diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryItemDetailPage.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryItemDetailPage.java new file mode 100644 index 0000000000..cc5ed5efe4 --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryItemDetailPage.java @@ -0,0 +1,44 @@ +package com.yas.automation.ui.pages; + +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; + +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.page.BasePage; +import com.yas.automation.ui.util.WebElementUtil; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.How; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +public class CategoryItemDetailPage extends BasePage { + private final WebDriverFactory webDriverFactory; + + public CategoryItemDetailPage(WebDriverFactory webDriverFactory) { + super(webDriverFactory); + this.webDriverFactory = webDriverFactory; + } + + public boolean existedButtonAddToCart() { + this.wait(Duration.ofSeconds(1)); + return WebElementUtil.isElementPresent(webDriverFactory.getChromeDriver(), How.XPATH, "//button[span[text()='Add to cart']]"); + } + + public void clickAddToCart() { + WebElement btnAddToCart = getWebElementBy(webDriverFactory.getChromeDriver(), How.XPATH, "//button[span[text()='Add to cart']]"); + btnAddToCart.click(); + } + + public void clickObBasket() { + this.wait(Duration.ofSeconds(5)); + WebElement btnBasket = getWebElementBy(webDriverFactory.getChromeDriver(), How.CLASS_NAME, "header-cart"); + btnBasket.click(); + } + + public String getProductName() { + WebElement title = getWebElementBy(webDriverFactory.getChromeDriver(), How.CLASS_NAME, "title"); + return title.getText(); + } + +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryItemPage.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryItemPage.java new file mode 100644 index 0000000000..f120c67092 --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryItemPage.java @@ -0,0 +1,22 @@ +package com.yas.automation.ui.pages; + +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; + +import com.yas.automation.ui.hook.WebDriverFactory; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.How; +import org.springframework.stereotype.Component; + +@Component +public class CategoryItemPage { + private final WebDriverFactory webDriverFactory; + + public CategoryItemPage(WebDriverFactory webDriverFactory) { + this.webDriverFactory = webDriverFactory; + } + + public void clickCategoryItem() { + WebElement categoryItem = getWebElementBy(webDriverFactory.getChromeDriver(), How.XPATH, "//div[contains(@class, 'ProductCard_product-card')]"); + categoryItem.click(); + } +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryPage.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryPage.java new file mode 100644 index 0000000000..dd816fd644 --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/CategoryPage.java @@ -0,0 +1,23 @@ +package com.yas.automation.ui.pages; + +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; + +import com.yas.automation.ui.hook.WebDriverFactory; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.How; +import org.springframework.stereotype.Component; + +@Component +public class CategoryPage { + private final WebDriverFactory webDriverFactory; + + public CategoryPage(WebDriverFactory webDriverFactory) { + this.webDriverFactory = webDriverFactory; + } + + public void clickCategory() { + WebElement category = getWebElementBy(webDriverFactory.getChromeDriver(), How.CLASS_NAME, "image"); + category.click(); + } + +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/pages/HomePage.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/HomePage.java similarity index 66% rename from automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/pages/HomePage.java rename to automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/HomePage.java index d78adcc179..2ce8192f6c 100644 --- a/automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/pages/HomePage.java +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/HomePage.java @@ -1,20 +1,16 @@ -package com.yas.automation.ui.storefront.pages; +package com.yas.automation.ui.pages; + +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; import com.yas.automation.ui.hook.WebDriverFactory; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.How; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import static com.yas.automation.ui.ultil.WebElementUtil.getWebElementBy; - @Component public class HomePage { - - @Autowired private final WebDriverFactory webDriverFactory; - @Autowired public HomePage(WebDriverFactory webDriverFactory) { this.webDriverFactory = webDriverFactory; } @@ -24,4 +20,8 @@ public void clickLogin() { loginBtn.click(); } + public String getUsername() { + WebElement title = getWebElementBy(webDriverFactory.getChromeDriver(), How.ID, "user-dropdown"); + return title.getText(); + } } diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/LoginPage.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/LoginPage.java new file mode 100644 index 0000000000..f4f0609cea --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/LoginPage.java @@ -0,0 +1,30 @@ +package com.yas.automation.ui.pages; + +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; + +import com.yas.automation.ui.hook.WebDriverFactory; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.How; +import org.springframework.stereotype.Component; + +@Component +public class LoginPage { + private final WebDriverFactory webDriverFactory; + + public LoginPage(WebDriverFactory webDriverFactory) { + this.webDriverFactory = webDriverFactory; + } + + public void login(String username, String password) { + WebElement usernameEle = getWebElementBy(webDriverFactory.getChromeDriver(), How.ID, "username"); + usernameEle.sendKeys(username); + + WebElement passwordEle = getWebElementBy(webDriverFactory.getChromeDriver(), How.ID, "password"); + passwordEle.sendKeys(password); + } + + public void clickLogin() { + WebElement loginBtn = getWebElementBy(webDriverFactory.getChromeDriver(), How.CLASS_NAME, "submit"); + loginBtn.click(); + } +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/UserRegistrationPage.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/UserRegistrationPage.java new file mode 100644 index 0000000000..2803398c50 --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/pages/UserRegistrationPage.java @@ -0,0 +1,59 @@ +package com.yas.automation.ui.pages; + +import static com.yas.automation.ui.util.WebElementUtil.getWebElementBy; + +import com.yas.automation.ui.form.InputType; +import com.yas.automation.ui.form.UserRegisterForm; +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.page.BasePage; +import com.yas.automation.ui.service.InputDelegateService; +import com.yas.automation.ui.util.WebElementUtil; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.How; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +public class UserRegistrationPage extends BasePage { + private final WebDriverFactory webDriverFactory; + private final InputDelegateService inputDelegateService; + + public UserRegistrationPage(WebDriverFactory webDriverFactory, InputDelegateService inputDelegateService) { + super(webDriverFactory); + this.webDriverFactory = webDriverFactory; + this.inputDelegateService = inputDelegateService; + } + + public void clickRegister() { + WebElement registerBtn = getWebElementBy(webDriverFactory.getChromeDriver(), How.CLASS_NAME, "register"); + registerBtn.click(); + } + + public boolean existedErrorMessage() { + return isElementPresent( + () -> WebElementUtil.getWebElementBy( + webDriverFactory.getChromeDriver(), + How.ID, + "input-error-email")); + } + + public void clickBackToLogin() { + WebElement backToLogin = getWebElementBy(webDriverFactory.getChromeDriver(), How.XPATH, "//div[@id='kc-form-options']//a"); + backToLogin.click(); + } + + public void fillUserRegistrationData(UserRegisterForm userRegisterForm) { + inputDelegateService.setInputValue(InputType.TEXT, userRegisterForm.getFirstName(), "firstName"); + inputDelegateService.setInputValue(InputType.TEXT, userRegisterForm.getLastName(), "lastName"); + inputDelegateService.setInputValue(InputType.TEXT, userRegisterForm.getEmail(), String.format("%s@gmail.com", UUID.randomUUID())); + inputDelegateService.setInputValue(InputType.TEXT, userRegisterForm.getUsername(), UUID.randomUUID().toString()); + inputDelegateService.setInputValue(InputType.TEXT, userRegisterForm.getPassword(), "password"); + inputDelegateService.setInputValue(InputType.TEXT, userRegisterForm.getPasswordConfirm(), "password"); + } + + public void fillInvalidEmail(UserRegisterForm userRegisterForm) { + inputDelegateService.setInputValue(InputType.TEXT, userRegisterForm.getEmail(), "email"); + } +} + diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/services/AuthenticationService.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/services/AuthenticationService.java new file mode 100644 index 0000000000..9fd2083fe0 --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/services/AuthenticationService.java @@ -0,0 +1,52 @@ +package com.yas.automation.ui.services; + +import com.yas.automation.ui.configuration.StorefrontConfiguration; +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.pages.HomePage; +import com.yas.automation.ui.pages.LoginPage; +import org.junit.Assert; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +import static org.junit.Assert.assertTrue; + +@Component +public class AuthenticationService { + private final HomePage homePage; + private final LoginPage loginPage; + private final WebDriverFactory webDriverFactory; + private final StorefrontConfiguration storefrontConf; + + public AuthenticationService(HomePage homePage, LoginPage loginPage, WebDriverFactory webDriverFactory, StorefrontConfiguration storefrontConf) { + this.homePage = homePage; + this.loginPage = loginPage; + this.webDriverFactory = webDriverFactory; + this.storefrontConf = storefrontConf; + } + + public void loginSuccessful() { + webDriverFactory.getChromeDriver().navigate().to(storefrontConf.url()); + homePage.clickLogin(); + + // Verify that the current URL is the login page + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/login")); + + loginPage.login(storefrontConf.username(), storefrontConf.password()); + loginPage.clickLogin(); + + WebDriverWait wait = new WebDriverWait(webDriverFactory.getChromeDriver(), Duration.of(15, ChronoUnit.SECONDS)); // 10 seconds wait + WebElement userDropdown = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("user-dropdown"))); + //WebElement userDropdown = webDriver.findElement(By.id("user-dropdown")); + String dropdownText = userDropdown.getText(); + + // Assert to check if the correct text is present + Assert.assertTrue("Check failed: User is not logged in as admin.", dropdownText.contains("Signed in as: admin")); + } +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/CartProcessSteps.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/CartProcessSteps.java new file mode 100644 index 0000000000..c91537b663 --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/CartProcessSteps.java @@ -0,0 +1,106 @@ +package com.yas.automation.ui.steps; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.page.BasePage; +import com.yas.automation.ui.pages.CartPage; +import com.yas.automation.ui.pages.CategoryItemDetailPage; +import com.yas.automation.ui.pages.CategoryItemPage; +import com.yas.automation.ui.pages.CategoryPage; +import com.yas.automation.ui.services.AuthenticationService; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +public class CartProcessSteps extends BasePage { + private final WebDriverFactory webDriverFactory; + private final CategoryPage categoryPage; + private final CategoryItemPage categoryItemPage; + private final CategoryItemDetailPage categoryItemDetailPage; + private final CartPage cartPage; + private final AuthenticationService authenticationService; + + private String productName; + + public CartProcessSteps(WebDriverFactory webDriverFactory, CategoryPage categoryPage, CategoryItemPage categoryItemPage, CategoryItemDetailPage categoryItemDetailPage, CartPage cartPage, CartPage cartPage1, AuthenticationService authenticationService) { + super(webDriverFactory); + this.webDriverFactory = webDriverFactory; + this.categoryPage = categoryPage; + this.categoryItemPage = categoryItemPage; + this.categoryItemDetailPage = categoryItemDetailPage; + this.cartPage = cartPage1; + this.authenticationService = authenticationService; + } + + @Given("I login successful") + public void i_login_successful() { + authenticationService.loginSuccessful(); + } + + @When("I click on category item") + public void i_click_on_category_item() { + categoryPage.clickCategory(); + } + + @Then("I should be redirected to the product list") + public void i_should_be_redirected_to_the_dashboard() { + // Verify that the current URL is the login page + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/products")); + } + + @When("I click on product item") + public void i_click_on_product_item() throws InterruptedException { + categoryItemPage.clickCategoryItem(); + } + + @Then("I should be redirected to the product item detail") + public void i_should_be_redirected_to_the_product_item_detail() { + boolean result = categoryItemDetailPage.existedButtonAddToCart(); + assertTrue(result); + } + + @When("I click on button add to cart") + public void i_click_on_button_add_to_cart() throws InterruptedException { + productName = categoryItemDetailPage.getProductName(); + categoryItemDetailPage.clickAddToCart(); + } + + @When("I click on basket") + public void i_click_on_basket() throws InterruptedException { + categoryItemDetailPage.clickObBasket(); + } + + @Then("This item is existed on table") + public void this_item_is_existed_on_table() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/cart")); + assertTrue(cartPage.checkProductName(productName)); + } + + @When("I click on icon delete on each row") + public void iClickOnIconDeleteOnEachRow() { + cartPage.clickDeleteButton(); + } + + + @Then("It shows popup confirm with button Remove") + public void itShowsPopupConfirmWithButtonRemove() { + productName = cartPage.getProductName(); + boolean result = cartPage.existedRemoveButton(); + assertTrue(result); + } + + @When("I click on button Remove") + public void iClickOnButtonRemove() { + cartPage.clickRemoveButton(); + } + + @Then("This item is not existed on table") + public void thisItemIsNotExistedOnTable() { + assertFalse(cartPage.checkProductName(productName)); + } + +} diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/steps/LoginSteps.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/LoginSteps.java similarity index 81% rename from automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/steps/LoginSteps.java rename to automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/LoginSteps.java index 34951ea16f..6a2fb262e0 100644 --- a/automation-ui/storefront/src/test/java/com/yas/automation/ui/storefront/steps/LoginSteps.java +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/LoginSteps.java @@ -1,16 +1,14 @@ -package com.yas.automation.ui.storefront.steps; +package com.yas.automation.ui.steps; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.Assert.assertTrue; import com.yas.automation.ui.configuration.StorefrontConfiguration; import com.yas.automation.ui.hook.WebDriverFactory; -import com.yas.automation.ui.storefront.pages.HomePage; -import com.yas.automation.ui.storefront.pages.LoginPage; +import com.yas.automation.ui.pages.HomePage; +import com.yas.automation.ui.pages.LoginPage; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import java.time.Duration; -import java.time.temporal.ChronoUnit; import org.junit.Assert; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; @@ -18,17 +16,17 @@ import org.openqa.selenium.support.ui.WebDriverWait; import org.springframework.beans.factory.annotation.Autowired; +import java.time.Duration; +import java.time.temporal.ChronoUnit; public class LoginSteps { - private final HomePage homePage; private final LoginPage loginPage; private final WebDriverFactory webDriverFactory; private final StorefrontConfiguration storefrontConf; @Autowired - public LoginSteps(HomePage homePage, LoginPage loginPage, WebDriverFactory webDriverFactory, - StorefrontConfiguration storefrontConf) { + public LoginSteps(HomePage homePage, LoginPage loginPage, WebDriverFactory webDriverFactory, StorefrontConfiguration storefrontConf) { this.homePage = homePage; this.loginPage = loginPage; this.storefrontConf = storefrontConf; @@ -65,14 +63,13 @@ public void i_click_on_the_login_button() { @Then("I should be redirected to the dashboard") public void i_should_be_redirected_to_the_dashboard() { - WebDriverWait wait = new WebDriverWait(webDriverFactory.getChromeDriver(), - Duration.of(10, ChronoUnit.SECONDS)); // 10 seconds wait + WebDriverWait wait = new WebDriverWait(webDriverFactory.getChromeDriver(), Duration.of(10, ChronoUnit.SECONDS)); // 10 seconds wait WebElement userDropdown = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("user-dropdown"))); + //WebElement userDropdown = webDriver.findElement(By.id("user-dropdown")); String dropdownText = userDropdown.getText(); // Assert to check if the correct text is present - Assert.assertTrue("Check failed: User is not logged in as admin.", - dropdownText.contains("Signed in as: admin")); + Assert.assertTrue("Check failed: User is not logged in as admin.", dropdownText.contains("Signed in as: admin")); } @When("I enter invalid credentials") @@ -84,10 +81,8 @@ public void i_enter_invalid_credentials() { @Then("I should see an error message") public void i_should_see_an_error_message() { - WebDriverWait wait = new WebDriverWait(webDriverFactory.getChromeDriver(), - Duration.of(10, ChronoUnit.SECONDS)); // 10 seconds wait - WebElement errorMessage = wait.until( - ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".alert.alert-error .message-text"))); + WebDriverWait wait = new WebDriverWait(webDriverFactory.getChromeDriver(), Duration.of(10, ChronoUnit.SECONDS)); // 10 seconds wait + WebElement errorMessage = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".alert.alert-error .message-text"))); // Assert the text content of the error message String expectedMessage = "Invalid username or password."; diff --git a/automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/UserRegistrationSteps.java b/automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/UserRegistrationSteps.java new file mode 100644 index 0000000000..0e43213fc4 --- /dev/null +++ b/automation-ui/storefront/src/test/java/com/yas/automation/ui/steps/UserRegistrationSteps.java @@ -0,0 +1,89 @@ +package com.yas.automation.ui.steps; + +import static org.junit.Assert.assertTrue; + +import com.yas.automation.ui.configuration.StorefrontConfiguration; +import com.yas.automation.ui.form.UserRegisterForm; +import com.yas.automation.ui.hook.WebDriverFactory; +import com.yas.automation.ui.pages.HomePage; +import com.yas.automation.ui.pages.UserRegistrationPage; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +public class UserRegistrationSteps { + private final HomePage homePage; + private final UserRegistrationPage userRegistrationPage; + private final WebDriverFactory webDriverFactory; + private final StorefrontConfiguration storefrontConf; + private String INPUT_USERNAME; + + public UserRegistrationSteps(HomePage homePage, UserRegistrationPage userRegistrationPage, WebDriverFactory webDriverFactory, StorefrontConfiguration storefrontConf) { + this.homePage = homePage; + this.userRegistrationPage = userRegistrationPage; + this.webDriverFactory = webDriverFactory; + this.storefrontConf = storefrontConf; + } + + @Given("I'm on the home page") + public void iMOnTheHomePage() { + webDriverFactory.getChromeDriver().navigate().to(storefrontConf.url()); + } + + @When("I click the login link on header") + public void iClickTheLoginLinkOnHeader() { + homePage.clickLogin(); + } + + @Then("I should be redirected to the welcome page") + public void iShouldBeRedirectedToTheWelcomePage() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/login")); + } + + @When("I click on the register button") + public void iClickOnTheRegisterButton() { + userRegistrationPage.clickRegister(); + } + + @Then("I should be redirected to the register page") + public void iShouldBeRedirectedToTheRegisterPage() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/registration")); + } + + @Given("I fill necessary data for registration user and click Register button") + public void iFillNecessaryDataForRegistrationUserAndClickButton() { + UserRegisterForm userRegisterForm = new UserRegisterForm(webDriverFactory.getChromeDriver()); + userRegistrationPage.fillUserRegistrationData(userRegisterForm); + INPUT_USERNAME = userRegisterForm.getUsername().getAttribute("value"); + userRegisterForm.submitForm(); + + } + + @Then("I should be redirected to the home page and display this user") + public void iShouldBeRedirectedToTheHomePageAndDisplayThisUser() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains(storefrontConf.url())); + assertTrue(homePage.getUsername().contains(INPUT_USERNAME)); + } + + @Given("I fill invalid email and click Register button") + public void iFillInvalidEmailAndClickRegisterButton() { + UserRegisterForm userRegisterForm = new UserRegisterForm(webDriverFactory.getChromeDriver()); + userRegistrationPage.fillInvalidEmail(userRegisterForm); + userRegisterForm.submitForm(); + } + + @Then("I should be kept the register page and display error message") + public void iShouldBeKeptTheRegisterPageAndDisplayErrorMessage() { + String currentUrl = webDriverFactory.getChromeDriver().getCurrentUrl(); + assertTrue(currentUrl.contains("/registration")); + assertTrue(userRegistrationPage.existedErrorMessage()); + } + + @When("I click on Back to Login link") + public void iClickOnBackToLoginLink() { + userRegistrationPage.clickBackToLogin(); + } +} diff --git a/automation-ui/storefront/src/test/resources/features/cart-process.feature b/automation-ui/storefront/src/test/resources/features/cart-process.feature new file mode 100644 index 0000000000..39c5a9ed13 --- /dev/null +++ b/automation-ui/storefront/src/test/resources/features/cart-process.feature @@ -0,0 +1,25 @@ +Feature: User Login and Add To Cart Flow - Remove Cart + + Scenario: Successful login with valid credentials and add to basket + Given I login successful + When I click on category item + Then I should be redirected to the product list + When I click on product item + Then I should be redirected to the product item detail + When I click on button add to cart + When I click on basket + Then This item is existed on table + + Scenario: Successful login with valid credentials, remove cat from basket + Given I login successful + When I click on category item + Then I should be redirected to the product list + When I click on product item + Then I should be redirected to the product item detail + When I click on button add to cart + When I click on basket + Then This item is existed on table + When I click on icon delete on each row + Then It shows popup confirm with button Remove + When I click on button Remove + Then This item is not existed on table \ No newline at end of file diff --git a/automation-ui/storefront/src/test/resources/storefront/features/login.feature b/automation-ui/storefront/src/test/resources/features/login.feature similarity index 100% rename from automation-ui/storefront/src/test/resources/storefront/features/login.feature rename to automation-ui/storefront/src/test/resources/features/login.feature diff --git a/automation-ui/storefront/src/test/resources/features/user-registration.feature b/automation-ui/storefront/src/test/resources/features/user-registration.feature new file mode 100644 index 0000000000..057ca4e687 --- /dev/null +++ b/automation-ui/storefront/src/test/resources/features/user-registration.feature @@ -0,0 +1,28 @@ +Feature: User registration Flow + + Scenario: Register user successfully + Given I'm on the home page + When I click the login link on header + Then I should be redirected to the welcome page + When I click on the register button + Then I should be redirected to the register page + Given I fill necessary data for registration user and click Register button + Then I should be redirected to the home page and display this user + + Scenario: Register user unsuccessfully - invalid email + Given I'm on the home page + When I click the login link on header + Then I should be redirected to the welcome page + When I click on the register button + Then I should be redirected to the register page + Given I fill invalid email and click Register button + Then I should be kept the register page and display error message + + Scenario: Back to login page from user registration page + Given I'm on the home page + When I click the login link on header + Then I should be redirected to the welcome page + When I click on the register button + Then I should be redirected to the register page + When I click on Back to Login link + Then I should be redirected to the welcome page \ No newline at end of file diff --git a/pom.xml b/pom.xml index 221bb48049..76801e7105 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,6 @@ yas - common-library backoffice-bff cart customer @@ -33,6 +32,7 @@ tax webhook sampledata + automation-ui @@ -62,6 +62,13 @@ 3.3.0 5.4.0 4.8.1 + 4.14.0 + 5.7.0 + 7.18.1 + 7.10.2 + 7.18.1 + 7.14.0 + @@ -145,6 +152,42 @@ ${testcontainers-keycloak.version} test + + org.seleniumhq.selenium + selenium-java + ${selenium-java.version} + test + + + io.github.bonigarcia + webdrivermanager + ${webdrivermanager.version} + test + + + io.cucumber + cucumber-java + ${cucumber-java.version} + test + + + org.testng + testng + ${testng.version} + test + + + io.cucumber + cucumber-testng + ${cucumber-testng.version} + test + + + io.cucumber + cucumber-spring + ${cucumber-spring.version} + test + @@ -312,14 +355,6 @@ - - - com/yas/**/*Application.class - com/yas/**/config/** - com/yas/**/exception/** - com/yas/**/constants/** - - From 22956b0d9ef48aaea211b61a1bb197e607beeb77 Mon Sep 17 00:00:00 2001 From: Chinh Duong Date: Wed, 25 Sep 2024 10:58:59 +0700 Subject: [PATCH 2/2] #1022 - Automation test - add sonar key + revert pom parents --- automation-ui/automation-base/pom.xml | 2 +- pom.xml | 53 +++++---------------------- 2 files changed, 10 insertions(+), 45 deletions(-) diff --git a/automation-ui/automation-base/pom.xml b/automation-ui/automation-base/pom.xml index 8bad890efc..4f26c9c0a1 100644 --- a/automation-ui/automation-base/pom.xml +++ b/automation-ui/automation-base/pom.xml @@ -14,7 +14,7 @@ nashtech-garage https://sonarcloud.io - nashtech-garage_yas-automation-ui-automation_base + nashtech-garage_yas-automation-ui-base diff --git a/pom.xml b/pom.xml index 76801e7105..221bb48049 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ yas + common-library backoffice-bff cart customer @@ -32,7 +33,6 @@ tax webhook sampledata - automation-ui @@ -62,13 +62,6 @@ 3.3.0 5.4.0 4.8.1 - 4.14.0 - 5.7.0 - 7.18.1 - 7.10.2 - 7.18.1 - 7.14.0 - @@ -152,42 +145,6 @@ ${testcontainers-keycloak.version} test - - org.seleniumhq.selenium - selenium-java - ${selenium-java.version} - test - - - io.github.bonigarcia - webdrivermanager - ${webdrivermanager.version} - test - - - io.cucumber - cucumber-java - ${cucumber-java.version} - test - - - org.testng - testng - ${testng.version} - test - - - io.cucumber - cucumber-testng - ${cucumber-testng.version} - test - - - io.cucumber - cucumber-spring - ${cucumber-spring.version} - test - @@ -355,6 +312,14 @@ + + + com/yas/**/*Application.class + com/yas/**/config/** + com/yas/**/exception/** + com/yas/**/constants/** + +