diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5bc1949 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,50 @@ +name: Run tests +env: + ALLURE_VERSION: "2.10.0" + +on: + push: + branches: + - main + + pull_request: + branches: + - main + release: + types: + - created + +jobs: + build: + name: Tests on JDK + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: [ 11, 17 ] + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + java-package: jdk + distribution: 'temurin' + cache: 'maven' + + - name: Build with Maven + id: build + run: mvn clean install -DskipTests -ntp + + - name: Tests + id: functests + timeout-minutes: 10 + continue-on-error: true + run: mvn test -ntp + + - name: Check tests are passed + if: ${{ steps.functests.outcome != 'success' }} + run: | + echo Tests result: ${{ steps.functests.outcome }} + exit 1 diff --git a/.gitignore b/.gitignore index 524f096..45d878c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,12 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* + +# Idea files +*.iml +.idea/ +target/ + +# Allure files +.allure/ +allure-results/ \ No newline at end of file diff --git a/README.md b/README.md index 18acb46..90d4105 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ -# jdn-template-selenium -Template project for JDN with Selenium +# Selenium Testng empty template + +Empty template for Test Automation project with Selenium + +# Instruction: + +1. Download template and unpack in appropriate folder + +2. Open project in IDE (for example IntelliJIdea) + +3. Reporting: Allure is enabled, after running tests just run **allure:serve** in maven plugins (allure should be + installed locally) + +4. Parameters that can be changed: configure the headless mode, base URL and browser type (chrome/safari/ie/firefox/edg) + by updating the properties in the pom.xml file. + +5. TestNg Retry and before after listeners: You can also modify rules of retry tests (now it is 1 retry for each test) + and actions before/after all tests (now it prints test name and result) in **testng** folder + +6. pages generated by JDN should be places in src/main/java/pages package + +7. Each page object class must be inherited from BasePage, including one of the mandatory paremeters is PATH describing + the URI of the page e.g. in http://example.com/login PATH = "/login". diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2b6d909 --- /dev/null +++ b/pom.xml @@ -0,0 +1,132 @@ + + 4.0.0 + + com.epam.jdi + jdn-template-selenium + 1.0.0 + Template: Selenium TestNG Testing project + + + 7.8.0 + 2.7 + 2.22.0 + 3.141.59 + 1.18.22 + 5.6.3 + 2.13.6 + 2.12.0 + 3.8.1 + 2.22.2 + 11 + 11 + + https://example.com + true + chrome + + + + + + org.apache.commons + commons-configuration2 + ${commons.config.version} + + + + org.apache.logging.log4j + log4j-core + ${log4j.core.version} + + + + org.seleniumhq.selenium + selenium-java + ${selenium.java.version} + + + + org.testng + testng + ${testng.version} + test + + + + org.projectlombok + lombok + ${lombok.version} + test + + + + io.github.bonigarcia + webdrivermanager + ${webdrivermanager.version} + + + + io.qameta.allure + allure-testng + ${allure.testng.version} + + + + + + + io.qameta.allure + allure-maven + 2.12.0 + + 2.13.6 + + + allure.results.directory + target + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + ${project.build.directory}/allure-results + + + src/test/resources/testng.xml + + + + listener + io.qameta.allure.testng.AllureTestNg + + + + + + + + src/test/resources + + + src/test/resources + true + + + + + diff --git a/src/main/java/configuration/Configuration.java b/src/main/java/configuration/Configuration.java new file mode 100644 index 0000000..1d1143c --- /dev/null +++ b/src/main/java/configuration/Configuration.java @@ -0,0 +1,39 @@ +package configuration; + +import static java.lang.Boolean.parseBoolean; + +public class Configuration { + private static final Configuration INSTANCE = new Configuration(); + + private final String baseUrl; + private final String browser; + private final boolean headless; + + private Configuration() { + // Initialize configuration properties + this.baseUrl = getProperty("base.url", "https://example.com"); + this.browser = getProperty("browser", "chrome"); + this.headless = parseBoolean(getProperty("headless", "true")); + } + + public static Configuration getInstance() { + return INSTANCE; + } + + public String getBaseUrl() { + return baseUrl; + } + + public String getBrowser() { + return browser; + } + + public boolean isHeadless() { + return headless; + } + + private String getProperty(String propertyName, String defaultValue) { + return System.getProperty(propertyName, defaultValue); + } +} + diff --git a/src/main/java/pages/BasePage.java b/src/main/java/pages/BasePage.java new file mode 100644 index 0000000..601fb64 --- /dev/null +++ b/src/main/java/pages/BasePage.java @@ -0,0 +1,62 @@ +package pages; + +import java.util.List; + +import configuration.Configuration; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.PageFactory; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +public abstract class BasePage { + + public final WebDriver driver; + public final int DEFAULT_TIMEOUT = 30; + protected String baseUrl; + protected String path; + + public BasePage(WebDriver driver, String path) { + this.driver = driver; + this.path = path; + baseUrl = Configuration.getInstance() + .getBaseUrl(); + PageFactory.initElements(driver, this); + } + + public void waitUntilElementVisible(WebElement webElement) { + WebDriverWait wait = new WebDriverWait(driver, DEFAULT_TIMEOUT); + wait.until(ExpectedConditions.visibilityOf(webElement)); + } + + public void waitUntilElementClickable(WebElement webElement) { + WebDriverWait wait = new WebDriverWait(driver, DEFAULT_TIMEOUT); + wait.until(ExpectedConditions.elementToBeClickable(webElement)); + } + + public void click(WebElement webElement) { + waitUntilElementClickable(webElement); + webElement.click(); + } + + public WebElement getElement(WebElement webElement) { + waitUntilElementVisible(webElement); + return webElement; + } + + public List getElements(List webElements) { + for (WebElement element : webElements) { + waitUntilElementVisible(element); + } + return webElements; + } + + public void sendKeys(WebElement webElement, String text) { + waitUntilElementVisible(webElement); + webElement.sendKeys(text); + } + + public void open() { + driver.get(baseUrl + this.path); + } +} \ No newline at end of file diff --git a/src/main/java/pages/HomePage.java b/src/main/java/pages/HomePage.java new file mode 100644 index 0000000..a94a6d1 --- /dev/null +++ b/src/main/java/pages/HomePage.java @@ -0,0 +1,16 @@ +package pages; + +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +// This is an example of Page Object definition. To be removed +public class HomePage extends BasePage { + private static final String PATH = "/"; + @FindBy(css = "p > a") + public WebElement link; + + public HomePage(final WebDriver driver) { + super(driver, PATH); + } +} diff --git a/src/main/java/utilities/DriverFactory.java b/src/main/java/utilities/DriverFactory.java new file mode 100644 index 0000000..d1000b5 --- /dev/null +++ b/src/main/java/utilities/DriverFactory.java @@ -0,0 +1,64 @@ +package utilities; + +import configuration.Configuration; +import io.github.bonigarcia.wdm.WebDriverManager; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.edge.EdgeDriver; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.ie.InternetExplorerDriver; +import org.openqa.selenium.safari.SafariDriver; + +public class DriverFactory { + + public static final String CHROME = "chrome"; + public static final String FIREFOX = "firefox"; + public static final String IE = "ie"; + public static final String EDGE = "edge"; + public static final String SAFARI = "safari"; + + public static WebDriver getDriver(String browserName) { + if (browserName == null) { + WebDriverManager.chromedriver() + .setup(); + return new ChromeDriver(); + } + switch (browserName.toLowerCase()) { + case CHROME: + boolean headlessMode = Configuration.getInstance() + .isHeadless(); + ChromeOptions chromeOptions = new ChromeOptions(); + chromeOptions.addArguments("--no-sandbox"); + chromeOptions.addArguments("--window-size=1920x1080"); + chromeOptions.addArguments("disable-infobars"); + chromeOptions.addArguments("--disable-extensions"); + chromeOptions.addArguments("--disable-gpu"); + chromeOptions.addArguments("--disable-dev-shm-usage"); + if (headlessMode) { + chromeOptions.addArguments("--headless"); + } + WebDriverManager.chromedriver() + .setup(); + return new ChromeDriver(chromeOptions); + case FIREFOX: + WebDriverManager.firefoxdriver() + .setup(); + return new FirefoxDriver(); + case IE: + WebDriverManager.iedriver() + .setup(); + return new InternetExplorerDriver(); + case EDGE: + WebDriverManager.edgedriver() + .setup(); + return new EdgeDriver(); + case SAFARI: + return new SafariDriver(); + default: + WebDriverManager.chromedriver() + .setup(); + return new ChromeDriver(); + } + } +} \ No newline at end of file diff --git a/src/test/java/test/BaseTest.java b/src/test/java/test/BaseTest.java new file mode 100644 index 0000000..349368e --- /dev/null +++ b/src/test/java/test/BaseTest.java @@ -0,0 +1,35 @@ +package test; + +import configuration.Configuration; +import org.openqa.selenium.WebDriver; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import utilities.DriverFactory; + +@Listeners() +public abstract class BaseTest { + + protected WebDriver driver; + protected String baseUrl; + + @BeforeMethod + public void setUp() { + baseUrl = Configuration.getInstance() + .getBaseUrl(); + driver = DriverFactory.getDriver(Configuration.getInstance() + .getBrowser()); + driver.manage() + .window() + .maximize(); + driver.get(baseUrl); + } + + @AfterMethod + public void tearDown() { + if (driver != null) { + driver.quit(); + } + } +} + diff --git a/src/test/java/test/ExampleTests.java b/src/test/java/test/ExampleTests.java new file mode 100644 index 0000000..c96ff80 --- /dev/null +++ b/src/test/java/test/ExampleTests.java @@ -0,0 +1,21 @@ +package test; + +import io.qameta.allure.Description; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import pages.HomePage; + +public class ExampleTests extends BaseTest { + private HomePage homePage; + + @BeforeMethod + public void setUpPage() { + homePage = new HomePage(driver); + } + + @Test(description = "Example test") + public void testMethod() { + homePage.open(); + homePage.link.click(); + } +} diff --git a/src/test/java/testng/RetryAnalyzer.java b/src/test/java/testng/RetryAnalyzer.java new file mode 100644 index 0000000..494a042 --- /dev/null +++ b/src/test/java/testng/RetryAnalyzer.java @@ -0,0 +1,19 @@ +package testng; + +import org.testng.IRetryAnalyzer; +import org.testng.ITestResult; + +public class RetryAnalyzer implements IRetryAnalyzer { + + private int retryCount = 0; + private static final int MAX_RETRY_COUNT = 3; + + @Override + public boolean retry(ITestResult iTestResult) { + if (retryCount < MAX_RETRY_COUNT) { + retryCount++; + return true; // Retry the test + } + return false; // No retry + } +} diff --git a/src/test/java/testng/RetryTransformer.java b/src/test/java/testng/RetryTransformer.java new file mode 100644 index 0000000..a0efd14 --- /dev/null +++ b/src/test/java/testng/RetryTransformer.java @@ -0,0 +1,15 @@ +package testng; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.testng.IAnnotationTransformer; +import org.testng.annotations.ITestAnnotation; + +public class RetryTransformer implements IAnnotationTransformer { + + @Override + public void transform(ITestAnnotation iTestAnnotation, Class aClass, Constructor constructor, Method method) { + iTestAnnotation.setRetryAnalyzer(RetryAnalyzer.class); + } +} diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..6b7aec5 --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,61 @@ + + + + + jdiEvents + ./target/.logs + + + + + + + + + ${baseDir}/${logFileName}_%d{yyyy-MM-dd__HH-mm}_%i.log + + %d{HH:mm:ss} [%t] %-5level %logger{6} - %msg%n + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/testng.xml b/src/test/resources/testng.xml new file mode 100644 index 0000000..c50c8bc --- /dev/null +++ b/src/test/resources/testng.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file