diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml
new file mode 100644
index 0000000..69175b4
--- /dev/null
+++ b/.github/workflows/on-pr.yml
@@ -0,0 +1,28 @@
+name: On Pull Request
+
+on:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ build:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK 24
+ uses: actions/setup-java@v4
+ with:
+ distribution: oracle
+ java-version: 24
+
+ - name: Cache Maven packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Build
+ run: mvn -q compile
diff --git a/.github/workflows/test-execution.yml b/.github/workflows/test-execution.yml
new file mode 100644
index 0000000..338cdd1
--- /dev/null
+++ b/.github/workflows/test-execution.yml
@@ -0,0 +1,33 @@
+name: Build and Test
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+jobs:
+ local-test:
+ runs-on: macos-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK 24
+ uses: actions/setup-java@v4
+ with:
+ java-version: 24
+ distribution: oracle
+
+ - name: Cache Maven packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Build with Maven
+ run: mvn -q -DskipTests package
+
+ - name: Run local tests
+ run: mvn -q test -Pweb-execution -Dsuite=local -Dtarget=local -Dheadless=true -Dbrowser=chrome
diff --git a/.github/workflows/tests-executon.yml b/.github/workflows/tests-executon.yml
deleted file mode 100644
index e225e8d..0000000
--- a/.github/workflows/tests-executon.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: Test execution
-on:
- push:
- branches:
- - master
- pull_request:
- branches:
- - master
-jobs:
- build:
- runs-on: ubuntu-latest
- env:
- SELENIUM_HUB_HOST: hub
- services:
- hub:
- image: selenium/hub
- firefox:
- image: selenium/standalone-chrome
- env:
- HUB_HOST: hub
- HUB_PORT: 4444
- steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 11
- uses: actions/setup-java@v1
- with:
- java-version: 11
- - name: Cache Maven packages
- uses: actions/cache@v1
- with:
- path: ~/.m2
- key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- restore-keys: ${{ runner.os }}-m2
- - name: Build with Maven
- run: mvn -B package --file pom.xml
- - name: Run the tests
- run: mvn test
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index afd597e..6cf5705 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.settings/
+grid/assets
# Intellij
.idea/
diff --git a/pipeline_as_code/.gitlab-ci.yml b/.sdlc/.gitlab-ci.yml
similarity index 59%
rename from pipeline_as_code/.gitlab-ci.yml
rename to .sdlc/.gitlab-ci.yml
index 2203018..bf76065 100644
--- a/pipeline_as_code/.gitlab-ci.yml
+++ b/.sdlc/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: maven:3.5.4-jdk-8
+image: maven:3.8.4-openjdk-17
stages:
- build
@@ -17,4 +17,4 @@ build:
test:
stage: test
script:
- - mvn test -Pweb-execution -Dsuite=multi_browser -Denv=test
\ No newline at end of file
+ - mvn test -Pweb-execution -Dsuite=local -Dtarget=local -Dheadless=true -Dbrowser=chrome
diff --git a/pipeline_as_code/Jenkinsfile b/.sdlc/Jenkinsfile
similarity index 86%
rename from pipeline_as_code/Jenkinsfile
rename to .sdlc/Jenkinsfile
index d0a4f6e..21899b8 100644
--- a/pipeline_as_code/Jenkinsfile
+++ b/.sdlc/Jenkinsfile
@@ -2,7 +2,7 @@ node {
def mvnHome
stage('Preparation') {
- git 'https://github.com/eliasnogueira/selenium-java-bootstrap.git'
+ git 'https://github.com/eliasnogueira/selenium-java-lean-test-achitecture.git'
mvnHome = tool 'M3'
}
@@ -12,7 +12,7 @@ node {
stage('Test Execution') {
try {
- sh "'${mvnHome}/bin/mvn' test -Pweb-execution -Dsuite=multi_browser"
+ sh "'${mvnHome}/bin/mvn' test -Pweb-execution -Dsuite=local -Dtarget=local -Dheadless=true -Dbrowser=chrome
} catch (Exception e) {
currentBuild.result = 'FAILURE'
} finally {
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index aa61071..21a0ef8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,7 +1,7 @@
# Contribution guide
## I have an idea or I want to create an issue
-If you have an idea, suggestion, feature or an issue, please log an [issue](https://github.com/eliasnogueira/selenium-java-bootstrap/issues).
+If you have an idea, suggestion, feature or an issue, please log an [issue](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/issues).
Select _Bug report_ if tou want log an issue or _Feature request_ if you want to see something new.
@@ -11,7 +11,7 @@ Do not forget to add a _label_ on the issue or feature.
Excellent! Thank you to help me out!
You're going to need a few things first:
-* JDK 11+
+* JDK 17+
* [Configure your IDE](https://projectlombok.org/setup/overview) in order to support Lombok.
## Send a pull request
diff --git a/README.MD b/README.MD
index b76d386..d90fecc 100644
--- a/README.MD
+++ b/README.MD
@@ -1,26 +1,45 @@
-This project delivers to you a complete lean test architecture for your web tests using the best frameworks and practices.
+# Lean Test Automation Architecture using Java and Selenium WebDriver
-
+[](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/actions)
+
+**This project delivers to you a complete lean test architecture for your web tests using the best frameworks and
+practices.**
+
+It has a complete solution to run tests in different ways:
+
+* local testing using the browser on your local machine
+* parallel (or single) testing using Selenium Docker
+* local testing using TestContainers
+* Distributed execution using Selenium Grid
+
+## Examples
+
+### Local testing execution example
+
+
+
+### Parallel testing execution example with Selenium Grid
+
+
## Languages and Frameworks
-This project using the following languages and frameworks:
+This project uses the following languages and frameworks:
-* [Java 11](https://openjdk.java.net/projects/jdk/11/) as the programming language
+* [Java 23](https://openjdk.java.net/projects/jdk/23/) as the programming language
* [TestNG](https://testng.org/doc/) as the UnitTest framework to support the test creation
-* [Seleium WebDriver](https://www.selenium.dev/) as the web browser automation framework using the Java binding
-* [AsseertJ](https://joel-costigliola.github.io/assertj/) as the fluent assertion library
+* [Selenium WebDriver](https://www.selenium.dev/) as the web browser automation framework using the Java binding
+* [AssertJ](https://joel-costigliola.github.io/assertj/) as the fluent assertion library
* [Allure Report](https://docs.qameta.io/allure/) as the testing report strategy
-* [JavaFaker](https://github.com/DiUS/java-faker) as the faker data generation strategy
-* [Log4J2](https://logging.apache.org/log4j/2.x/) as the logging manage strategy
-* [WebDriverManager](https://github.com/bonigarcia/webdrivermanager) as the Selenium binaries management
+* [DataFaker](https://www.datafaker.net/) as the faker data generation strategy
+* [Log4J2](https://logging.apache.org/log4j/2.x/) as the logging management strategy
* [Owner](http://owner.aeonbits.org/) to minimize the code to handle the properties file
-* [Lombok](https://projectlombok.org/) to minimize the boilerplate in the Java code
-
+* [TestContainers](https://java.testcontainers.org/modules/webdriver_containers/) Webdriver Containers
## Test architecture
-We know that any automation project starting with a good test architecture.
+We know that any automation project starts with a good test architecture.
+
This project can be your initial test architecture for a faster start.
You will see the following items in this architecture:
@@ -34,15 +53,18 @@ You will see the following items in this architecture:
* [Test Data Factory](#test-data-factory)
* [Profiles executors on pom.xml](#profiles-executors-on-pomxml)
* [Pipeline as a code](#pipeline-as-a-code)
+* [Test environment abstraction](#execution-with-docker-selenium-distributed)
-Do you have any other item to add on this test architecture? Please do a pull request or open an issue to discuss
+Do you have any other items to add to this test architecture? Please do a pull request or open an issue to discuss.
### Page Objects pattern
-I will not explain the Page Object pattern because you can find a lot of good explanations and examples on the internet.
+
+I will not explain the Page Object pattern because you can find a lot of good explanations and examples on the internet.
Instead, I will explain what exactly about page objects I'm using in this project.
#### AbstractPageObject
-This class has a protected constructor to remove the necessity to init the elements using the Page Factory.
+
+This class has a protected constructor to remove the necessity to init the elements using the Page Factory.
Also, it sets the timeout from the `timeout` property value located on `general.properties` file.
All the Page Object classes should extend the `AbstractPageObject`.
@@ -53,78 +75,152 @@ It also tries to remove the `driver` object from the Page Object class as much a
> There's a `NavigationPage` on the `common` package inside the Page Objects.
> Notice that all the pages extend this one instead of the `AbstractPageObject`. I implemented this way:
> * because the previous and next buttons are fixed on the page (there's no refresh on the page)
-> * to avoid create or pass the new reference to the `NavigationPage` when we need to hit previous or next buttons
+> * to avoid creating or passing the new reference to the `NavigationPage` when we need to hit previous or next buttons
As much as possible avoid this strategy to not get an `ElementNotFoundException` or `StaleElementReferenceException`.
Use this approach if you know that the page does not refresh.
### Execution types
-There are two execution types: **local** and **remote**.
-For both, there's a factory class [DriverFactory](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/driver/DriverFactory.java)
-to resolve if the execution is local or remote based on the `target ` property value located on `general.properties` file.
+There are different execution types:
-The class [DriverManager](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/driver/DriverManager.java)
-create a `ThreadLocal` for the WebDriver instance, to make sure there's no conflict when we run it in parallel.
+- `local`
+- `local-suite`
+- `selenium-grid`
+- `testcontainers`
+
+The `TargetFactory` class will resolve the target execution based on the `target` property value located
+on `general.properties` file. Its usage is placed on the `BaseWeb` class before each test execution.
#### Local execution
-This execution type uses [WebDriverManager](https://github.com/bonigarcia/webdrivermanager) class to instantiate the web browsers.
-The class [LocalDriverManager](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/driver/local/LocalDriverManager.java)
-create a browser instance from the value placed on the `browser` property on the `local.properties` file.
-It matches the browser name with an internal Enum type on WebDriverManager.
+##### Local machine
+
+**This approach is automatically used when you run the test class in your IDE.**
+
+When the `target` is `local` the `createLocalDriver()` method is used from the `BrowserFactory` class to return the
+browser instance.
+
+The browser used in the test is placed on the `browser` property in the `general.properties` file.
+
+##### Local Suite
+
+It's the same as the Local Execution, where the difference is that the browser is taken from the TestNG suite file
+instead of the `general.properties`
+file, enabling you to run multi-browser test approach locally.
+
+##### Testcontainers
+
+This execution type uses the [WebDriver Containers](https://www.testcontainers.org/modules/webdriver_containers/) in
+Testcontainers to run the tests in your machine, but using the Selenium docker images for Chrome or Firefox.
+
+When the `target` is `testcontainers` the `TargetFactory` uses the `createTestContainersInstance()` method to initialize
+the container based on the browser set in the `browser` property. Currently, Testcontainers only supports Chrome and
+Firefox.
+
+Example
+
+```shell
+mvn test -Pweb-execution -Dtarget=testcontainers -Dbrowser=chrome
+```
#### Remote execution
-This execution type uses [RemoteDriverManager](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/driver/remote/RemoteDriverManager.java)
-class to connect on the remote grid and create the browser instance.
-The approach of the remote execution here was created to enable the parallel execution, but if you set the `target`
-property value to `grid` and run a test it will work because if there's no explicit browser in use, Google Chrome will be used.
-You can check this on the [BaseWeb](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/test/BaseWeb.java) class.
+##### Selenium Grid
+
+The Selenium Grid approach executes the tests in remote machines (local or remote/cloud grid).
+When the `target` is `selenium-grid` the `getOptions` method is used from the `BrowserFactory` to return the browser
+option
+class as the remote execution needs the browser capability.
-You must pay attention to the two required information regarding the remote execution: the `grid.url` and `grid.port`
+The `DriverFactory` class has an internal method `createRemoteInstance` to return a `RemoteWebDriver` instance based on
+the browser capability.
+
+You must pay attention to the two required information regarding the remote execution: the `grid.url` and `grid.port`
property values on the `grid.properties` file. You must update these values before the start.
-If you are using the `docker-compose.yml` file to start the Zalenium grid, the values on the `grid.property` file should work.
+If you are using the `docker-compose.yml` file to start the Docker Selenium grid, the values on the `grid.properties`
+file should work.
+
+You can take a look at the [Execution with Docker Selenium Distributed](#execution-with-docker-selenium-distributed)
+to run the parallel tests using this example.
+
+#### BrowserFactory class
-Please take a look at the [Parallel Execution](#parallel-execution) section.
+This Factory class is a Java enum that has all implemented browsers to use during the test execution.
+Each browser is an `enum`, and each enum implements four methods:
+
+* `createLocalDriver()`: creates the browser instance for the local execution. The browser driver is automatically
+ managed by the WebDriverManager library
+* `createDriver()`: creates the browser instance for the remote execution
+* `getOptions()`: creates a new browser `Options` setting some specific configurations, and it's used for the remote
+ executions using the Selenium Grid
+* `createTestContainerDriver()` : Creates selenium grid lightweight test container in Standalone mode with
+ Chrome/Firefox/Edge browser support.
+
+You can see that the `createLocalDriver()` method use the `getOptions()` to get specific browser configurations, as
+starting the browser maximized and others.
+
+The `getOptions()` is also used for the remote execution as it is a subclass of the `AbstractDriverOptions` and can be
+automatically accepted as either a `Capabilities` or `MutableCapabilities` class, which is required by
+the `RemoteWebDriver` class.
+
+#### DriverManager class
+
+The
+class [DriverManager](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/driver/DriverManager.java)
+create a `ThreadLocal` for the WebDriver instance, to make sure there's no conflict when we run it in parallel.
### BaseTest
-This testing pattern was implemented on the [BaseWeb](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/test/BaseWeb.java)
+
+This testing pattern was implemented on
+the [BaseWeb](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/test/java/com/eliasnogueira/BaseWeb.java)
class to automatically run the pre (setup) and post (teardown) conditions.
-The pre-condition uses `@BeforeMethod` from TestNG creates the browser instance based on the values passed either local or remote execution.
+The pre-condition uses `@BeforeMethod` from TestNG creates the browser instance based on the values passed either local
+or remote execution.
The post-condition uses `@AfterMethod` to close the browser instance.
Both have the `alwaysRun` parameter as `true` to force the run on a pipeline.
-Pay attention that it was designed to open a browser instance to each `@Test` located on the test class.
+Pay attention that it was designed to open a browser instance to each `@Test` located in the test class.
-This class also the `TestListener` that is a custom TestNG listener, and will be described in the next section.
+This class also has the `TestListener` annotation which is a custom TestNG listener, and will be described in the next
+section.
### TestListener
-The `TestListener` is a class that implements [ITestListener](https://testng.org/doc/documentation-main.html#logging-listeners).
-The following method is in use to help logging errors and attach additional information on the test report:
-* `onTestStart`: add the browser information into the test report
-* `onTestFailure`: log the exceptions and add a screenshot on the test report
-* `onTestSkipped`: add the skipped test on the log
+The `TestListener` is a class that
+implements [ITestListener](https://testng.org/doc/documentation-main.html#logging-listeners).
+The following method is used to help logging errors and attach additional information to the test report:
+
+* `onTestStart`: add the browser information to the test report
+* `onTestFailure`: log the exceptions and add a screenshot to the test report
+* `onTestSkipped`: add the skipped test to the log
### Logging
+
All the log is done by the Log4J using the `@Log4j2` annotation.
The `log4j2.properties` has two strategies: console and file.
-A file with all the log information will be automatically created on the user folder with `test_automation.log` filename.
+A file with all the log information will be automatically created on the user folder with `test_automation.log`
+filename.
If you want to change it, update the `appender.file.fileName` property value.
-The `log.error` is used to log all the exceptions this architecture might throw. Use `log.info` or `log.debug` to log
-important information, like the users automatically generated by the factory [BookingDataFactory](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/data/BookingDataFactory.java)
+The `log.error` is used to log all the exceptions this architecture might throw. Use `log.info` or `log.debug` to log
+important information, like the users, automatically generated by the
+factory [BookingDataFactory](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/data/BookingDataFactory.java)
### Parallel execution
-The parallel test execution is based on the [parallel tests](https://testng.org/doc/documentation-main.html#parallel-tests)
-feature on TestNG. This is in use on the `multi_browser.xml` test suite file by the `parallel="tests"` attribute and value,
-which means each `test` item inside the test suite will execute in parallel.
+
+The parallel test execution is based on
+the [parallel tests](https://testng.org/doc/documentation-main.html#parallel-tests)
+feature on TestNG. This is used by `selenium-grid.xml` test suite file which has the `parallel="tests"` attribute and
+value,
+whereas `test` item inside the test suite will execute in parallel.
The browser in use for each `test` should be defined by a parameter, like:
+
```xml
+
```
@@ -132,79 +228,125 @@ You can define any parallel strategy.
It can be an excellent combination together with the grid strategy.
+#### Execution with Docker Selenium Distributed
+
+This project has the `docker-compose.yml` file to run the tests in a parallel way using Docker Selenium.
+To be able to run it in parallel the file has
+the [Dynamic Grid Implementation](https://github.com/SeleniumHQ/docker-selenium#dynamic-grid-) that will start the
+container on demand.
+
+This means that Docker Selenium will start a container test for a targeting browser.
+
+Please note that you need to do the following actions before running it in parallel:
+
+* Docker installed
+* Pull the images for Chrome Edge and Firefox - Optional
+ * Images are pulled if not available and initial test execution will be slow
+ * `docker pull selenium-standalog-chrome`
+ * `docker pull selenium-standalog-firefox`
+ * `docker pull selenium/standalone-edge`
+ * If you are using a MacBook with either M1 or M2 chip you must check the following experimental feature in Docker
+ Desktop: Settings -> Features in development -> Use Rosetta for x86/amd64 emulation on Apple Silicon
+* Pay attention to the `grid/config.toml` file that has comments for each specific SO
+* Start the Grid by running the following command inside the `grid` folder
+ * `docker-compose up`
+* Run the project using the following command
+
+```shell
+mvn test -Pweb-execution -Dsuite=selenium-grid -Dtarget=selenium-grid -Dheadless=true
+```
+
+* Open the [Selenium Grid] page to see the node status
+
### Configuration files
-This project uses a library called [Owner](http://owner.aeonbits.org/). You can find the class related to the property
+
+This project uses a library called [Owner](http://owner.aeonbits.org/). You can find the class related to the property
file reader in the following classes:
-* [Configuration](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/config/Configuration.java)
-* [ConfigurationManager](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/config/ConfigurationManager.java)
-There are 3 properties (configuration) files located on `src/main/java/resources/conf`:
-* `general.properties`: general configuration as the target execution, base url, timeout, and faker locale
-* `grid.properties`: url and port for the Selenium grid usage (Zalenium)
-* `local.properties`: browser to use in the local execution
+* [Configuration](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/config/Configuration.java)
+* [ConfigurationManager](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/config/ConfigurationManager.java)
+
+There are 3 properties (configuration) files located on `src/test/java/resources/`:
-The properties were divided into three different ones to better separate the responsibilities and enable the changes easy
-without have a lot of properties inside a single file.
+* `general.properties`: general configuration as the target execution, browser, base url, timeout, and faker locale
+* `grid.properties`: url and port for the Selenium grid usage
+
+The properties were divided into three different ones to better separate the responsibilities and enable the changes
+easy without having a lot of properties inside a single file.
### Test Data Factory
+
Is the utilization of the Factory design pattern with the Fluent Builder to generate dynamic data.
-The [BookingDataFactory](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/data/BookingDataFactory.java)
-has only one factory `createBookingData` returning a `Booking` object with dinamyc data.
+The [BookingDataFactory](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/data/BookingDataFactory.java)
+has only one factory `createBookingData` returning a `Booking` object with dynamic data.
This dynamic data is generated by JavaFaker filling all the fields using the Build pattern.
-The [Booking](https://github.com/eliasnogueira/selenium-java-bootstrap/blob/master/src/main/java/model/Booking.java)
-the class uses Lombok to reduce the boilerplate, easily providing getters, setters, constructors, and the build pattern.
+The [Booking](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/model/Booking.java)
+is the plain Java objects
+and
+the [BookingBuilder](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/model/BookingBuilder.java)
+is the builder class.
+
+You can see the usage of the Builder pattern in
+the [BookingDataFactory](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/data/BookingDataFactory.java)
+class.
Reading reference: https://reflectoring.io/objectmother-fluent-builder
### Profiles executors on pom.xml
-There is a profile called _multi-browser_ created to execute the test suite _multi_browser.xml_ inside _src/test/resources/suites_ folder.
-To execute this suite, via command line you can call the parameter `-P` and the profile id.
+There is a profile called `web-execution` created to execute the test suite `local.xml`
+inside `src/test/resources/suites` folder.
+To execute this suite, via the command line you can call the parameter `-P` and the profile id.
Eg: executing the multi_browser suite
+
``` bash
-mvn test -Pmulti-browser
+mvn test -Pweb-execution
```
If you have more than one suite on _src/test/resources/suites_ folder you can parameterize the xml file name.
To do this you need:
-* Create a property on pom.xml called _suite_
+* Create a property on `pom.xml` called _suite_
```xml
-
- multi_browser
-
+
+
+ local
+
```
* Change the profile id
```xml
+
- web_execution
+ web-execution
```
* Replace the xml file name to `${suite}` on the profile
```xml
+
-
- src/test/resources/suites/${suite}.xml
-
+
+ src/test/resources/suites/${suite}.xml
+
```
* Use `-Dsuite=suite_name` to call the suite
````bash
-mvn test -Pweb_execution -Dsuite=multi_browser
+mvn test -Pweb-execution -Dsuite=suite_name
````
### Pipeline as a code
-The two files of pipeline as a code are inside `pipeline_as_code` folder.
+The two files of the pipeline as a code are inside `pipeline_as_code` folder.
-* Jenkins: `Jenkinsfile` to be used on a Jenkins pipeline
-* GitLab CI: `.gitlab-ci.yml` to be used on a GitLab CI
\ No newline at end of file
+* GitHub Actions to use it inside the GitHub located at `.github\workflows`
+* Jenkins: `Jenkinsfile` to be used on a Jenkins pipeline located at `pipeline_as_code`
+* GitLab CI: `.gitlab-ci.yml` to be used on a GitLab CI `pipeline_as_code`
diff --git a/example_filed_test_with_report.gif b/assets/example_filed_test_with_report.gif
similarity index 100%
rename from example_filed_test_with_report.gif
rename to assets/example_filed_test_with_report.gif
diff --git a/assets/selenium-grid-execution.gif b/assets/selenium-grid-execution.gif
new file mode 100644
index 0000000..67d0c29
Binary files /dev/null and b/assets/selenium-grid-execution.gif differ
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 2b634ac..0000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,25 +0,0 @@
- # Usage:
- # docker-compose up --force-recreate
- version: '2.1'
-
- services:
- #--------------#
- zalenium:
- image: "dosel/zalenium"
- container_name: zalenium
- hostname: zalenium
- tty: true
- volumes:
- - /tmp/videos:/home/seluser/videos
- - /var/run/docker.sock:/var/run/docker.sock
- ports:
- - 4444:4444
- command: >
- start --desiredContainers 2
- --maxDockerSeleniumContainers 10
- --maxTestSessions 4
- --screenWidth 1280 --screenHeight 1024
- --timeZone "Europe/Amsterdam"
- --videoRecordingEnabled false
- environment:
- ZALENIUM_NO_PROXY: localhost,127.0.0.1
\ No newline at end of file
diff --git a/grid/config.toml b/grid/config.toml
new file mode 100644
index 0000000..2d961ae
--- /dev/null
+++ b/grid/config.toml
@@ -0,0 +1,28 @@
+[docker]
+# Configs have a mapping between the Docker image to use and the capabilities that need to be matched to
+# start a container with the given image.
+configs = [
+ "selenium/standalone-firefox:latest", "{\"browserName\": \"firefox\"}",
+ "selenium/standalone-chrome:latest", "{\"browserName\": \"chrome\"}"
+ ]
+
+host-config-keys = ["Binds"]
+
+# URL for connecting to the docker daemon
+# Most simple approach, leave it as http://127.0.0.1:2375, and mount /var/run/docker.sock.
+# 127.0.0.1 is used because internally the container uses socat when /var/run/docker.sock is mounted
+# If var/run/docker.sock is not mounted:
+# Windows: make sure Docker Desktop exposes the daemon via tcp, and use http://host.docker.internal:2375.
+# macOS: install socat and run the following command, socat -4 TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock,
+# then use http://host.docker.internal:2375.
+# Linux: varies from machine to machine, please mount /var/run/docker.sock. If this does not work, please create an issue.
+
+url = "http://host.docker.internal:2375/"
+# Docker image used for video recording
+video-image = "selenium/video:latest"
+
+# Uncomment the following section if you are running the node on a separate VM
+# Fill out the placeholders with appropriate values
+#[server]
+#host =
+#port =
diff --git a/grid/docker-compose.yml b/grid/docker-compose.yml
new file mode 100644
index 0000000..7163cac
--- /dev/null
+++ b/grid/docker-compose.yml
@@ -0,0 +1,22 @@
+services:
+ node-docker:
+ image: selenium/node-docker:latest
+ volumes:
+ - ./assets:/opt/selenium/assets
+ - ./config.toml:/opt/bin/config.toml
+ - ~/Downloads:/home/seluser/Downloads
+ - /var/run/docker.sock:/var/run/docker.sock
+ depends_on:
+ - selenium-hub
+ environment:
+ - SE_EVENT_BUS_HOST=selenium-hub
+ - SE_EVENT_BUS_PUBLISH_PORT=4442
+ - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+
+ selenium-hub:
+ image: selenium/hub:latest
+ container_name: selenium-hub
+ ports:
+ - "4442:4442"
+ - "4443:4443"
+ - "4444:4444"
diff --git a/pom.xml b/pom.xml
index e101705..d3faf76 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,31 +5,41 @@
4.0.0com.eliasnogueira
- selenium-java-bootstrap
- 1.4
+ selenium-java-lean-test-architecture
+ 3.8.0
+
+
+ scm:git@github.com:eliasnogueira/selenium-java-lean-test-architecture.git
+ scm:git@github.com:eliasnogueira/selenium-java-lean-test-architecture.git
+
+
+ 24UTF-8UTF-8
- 11
- 2.22.2
- 3.8.1
-
- 1.9.5
- 4.0.0-alpha-6
- 4.0.0-alpha-2
- 7.1.0
- 3.16.1
- 1.0.2
- 2.13.3
- 1.18.12
- 4.0.0
+ 3.5.3
+ 3.14.0
+
+ 1.9.24
+ 4.35.0
+ 7.11.0
+ 3.27.4
+ 2.4.4
+ 2.25.11.0.12
- 2.13.3
- 2.10.0
+ 2.33.0
+ 2.29.1
+ 2.29.1
+ 2.15.21.0.0
+
+ https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline
+
+ 1.21.3
+ 2.0.17
- multi_browser
+ local
@@ -39,12 +49,6 @@
${selenium.version}
-
- org.seleniumhq.selenium
- selenium-server
- ${selenium-server.version}
-
-
org.testngtestng
@@ -58,9 +62,9 @@
- com.github.javafaker
- javafaker
- ${javafaker.version}
+ net.datafaker
+ datafaker
+ ${datafaker.version}
@@ -76,10 +80,9 @@
- org.projectlombok
- lombok
- ${lombok.version}
- provided
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ ${log4j.version}
@@ -88,33 +91,52 @@
${owner.version}
-
- io.github.bonigarcia
- webdrivermanager
- ${webdrivermanager.version}
-
-
io.qameta.allureallure-testng
- ${allure.version}
+ ${allure-testng.version}io.qameta.allureallure-attachments
- ${allure.version}
+ ${allure-attachments.version}com.github.automatedowlallure-environment-writer${allure-environment-writer.version}
+
+
+ com.google.guava
+ guava
+
+
+
+
+
+ org.testcontainers
+ selenium
+ ${testcontainers.selenium.version}
+
+
+ org.apache.commons
+ commons-compress
+
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j-simple.version}
+ test
-
+
web-execution
@@ -133,6 +155,7 @@
+
@@ -147,9 +170,6 @@
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
false
-
- target/allure-results
-
@@ -163,27 +183,23 @@
io.qameta.allureallure-maven${allure-maven.version}
+
+ ${allure.version}
+
+ ${allure.cmd.download.url}/${allure.version}/allure-commandline-${allure.version}.zip
+
+ org.apache.maven.pluginsmaven-compiler-plugin${maven-compiler-plugin.version}
- ${java-compiler.version}
- ${java-compiler.version}
+ ${java-compiler.version}
-
-
- src/test/resources
- true
-
- *.properties
-
-
-
-
\ No newline at end of file
+
diff --git a/src/main/java/config/Configuration.java b/src/main/java/com/eliasnogueira/config/Configuration.java
similarity index 91%
rename from src/main/java/config/Configuration.java
rename to src/main/java/com/eliasnogueira/config/Configuration.java
index 47e948e..dda4233 100644
--- a/src/main/java/config/Configuration.java
+++ b/src/main/java/com/eliasnogueira/config/Configuration.java
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
-package config;
+package com.eliasnogueira.config;
import org.aeonbits.owner.Config;
import org.aeonbits.owner.Config.LoadPolicy;
@@ -31,9 +31,8 @@
@LoadPolicy(LoadType.MERGE)
@Config.Sources({
"system:properties",
- "classpath:conf/general.properties",
- "classpath:conf/local.properties",
- "classpath:conf/grid.properties"})
+ "classpath:general.properties",
+ "classpath:selenium-grid.properties"})
public interface Configuration extends Config {
@Key("target")
@@ -49,7 +48,7 @@ public interface Configuration extends Config {
String url();
@Key("timeout")
- String timeout();
+ int timeout();
@Key("grid.url")
String gridUrl();
diff --git a/src/main/java/config/ConfigurationManager.java b/src/main/java/com/eliasnogueira/config/ConfigurationManager.java
similarity index 94%
rename from src/main/java/config/ConfigurationManager.java
rename to src/main/java/com/eliasnogueira/config/ConfigurationManager.java
index 3fd91db..ce15843 100644
--- a/src/main/java/config/ConfigurationManager.java
+++ b/src/main/java/com/eliasnogueira/config/ConfigurationManager.java
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
-package config;
+package com.eliasnogueira.config;
import org.aeonbits.owner.ConfigCache;
@@ -31,8 +31,7 @@ public class ConfigurationManager {
private ConfigurationManager() {
}
- public static Configuration getConfiguration() {
+ public static Configuration configuration() {
return ConfigCache.getOrCreate(Configuration.class);
}
}
-
diff --git a/src/main/java/model/Booking.java b/src/main/java/com/eliasnogueira/data/changeless/BrowserData.java
similarity index 65%
rename from src/main/java/model/Booking.java
rename to src/main/java/com/eliasnogueira/data/changeless/BrowserData.java
index 82876b5..810154d 100644
--- a/src/main/java/model/Booking.java
+++ b/src/main/java/com/eliasnogueira/data/changeless/BrowserData.java
@@ -1,7 +1,7 @@
/*
* MIT License
*
- * Copyright (c) 2018 Elias Nogueira
+ * Copyright (c) 2022 Elias Nogueira
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,28 +22,17 @@
* SOFTWARE.
*/
-package model;
+package com.eliasnogueira.data.changeless;
-import enums.RoomType;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.ToString;
+public final class BrowserData {
-@Data
-@Builder
-@AllArgsConstructor
-@NoArgsConstructor
-@ToString
-public class Booking {
+ private BrowserData() {
+ }
- String email;
- String country;
- @ToString.Exclude
- String password;
- String dailyBudget;
- Boolean newsletter;
- RoomType roomType;
- String roomDescription;
+ public static final String START_MAXIMIZED = "--start-maximized";
+ public static final String DISABLE_INFOBARS = "--disable-infobars";
+ public static final String DISABLE_NOTIFICATIONS = "--disable-notifications";
+ public static final String REMOTE_ALLOW_ORIGINS = "--remote-allow-origins=*";
+ public static final String GENERIC_HEADLESS = "-headless";
+ public static final String CHROME_HEADLESS = "--headless=new";
}
diff --git a/src/main/java/data/BookingDataFactory.java b/src/main/java/com/eliasnogueira/data/dynamic/BookingDataFactory.java
similarity index 59%
rename from src/main/java/data/BookingDataFactory.java
rename to src/main/java/com/eliasnogueira/data/dynamic/BookingDataFactory.java
index 99e74c5..c1dc321 100644
--- a/src/main/java/data/BookingDataFactory.java
+++ b/src/main/java/com/eliasnogueira/data/dynamic/BookingDataFactory.java
@@ -22,51 +22,46 @@
* SOFTWARE.
*/
-package data;
+package com.eliasnogueira.data.dynamic;
+
+import com.eliasnogueira.enums.RoomType;
+import com.eliasnogueira.model.Booking;
+import net.datafaker.Faker;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
-import com.github.javafaker.Faker;
-import config.Configuration;
-import config.ConfigurationManager;
-import enums.RoomType;
import java.util.Locale;
-import java.util.Random;
-import lombok.extern.log4j.Log4j2;
-import model.Booking;
-@Log4j2
-public class BookingDataFactory {
+import static com.eliasnogueira.config.ConfigurationManager.configuration;
+
+public final class BookingDataFactory {
- private final Faker faker;
+ private static final Faker faker = new Faker(new Locale.Builder().setLanguageTag(configuration().faker()).build());
+ private static final Logger logger = LogManager.getLogger(BookingDataFactory.class);
- public BookingDataFactory() {
- Configuration configuration = ConfigurationManager.getConfiguration();
- faker = new Faker(new Locale(configuration.faker()));
+ private BookingDataFactory() {
}
- public Booking createBookingData() {
- Booking booking = Booking.builder().
+ public static Booking createBookingData() {
+ var booking = new Booking.BookingBuilder().
email(faker.internet().emailAddress()).
country(returnRandomCountry()).
password(faker.internet().password()).
dailyBudget(returnDailyBudget()).
newsletter(faker.bool().bool()).
- roomType(RoomType.getRandom()).
+ roomType(faker.options().option(RoomType.class)).
roomDescription(faker.lorem().paragraph()).
build();
- log.info(booking);
+ logger.info(booking);
return booking;
}
- private String returnRandomCountry() {
- return returnRandomItemOnArray(new String[]{"Belgium", "Brazil", "Netherlands"});
- }
-
- private String returnDailyBudget() {
- return returnRandomItemOnArray(new String[]{"$100", "$100 - $499", "$499 - $999", "$999+"});
+ private static String returnRandomCountry() {
+ return faker.options().option("Belgium", "Brazil", "Netherlands");
}
- private String returnRandomItemOnArray(String[] array) {
- return array[(new Random().nextInt(array.length))];
+ private static String returnDailyBudget() {
+ return faker.options().option("$100", "$100 - $499", "$499 - $999", "$999+");
}
}
diff --git a/src/main/java/com/eliasnogueira/driver/BrowserFactory.java b/src/main/java/com/eliasnogueira/driver/BrowserFactory.java
new file mode 100644
index 0000000..0b7f43f
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/driver/BrowserFactory.java
@@ -0,0 +1,163 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Elias Nogueira
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.eliasnogueira.driver;
+
+import com.eliasnogueira.exceptions.HeadlessNotSupportedException;
+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.edge.EdgeOptions;
+import org.openqa.selenium.firefox.FirefoxDriver;
+import org.openqa.selenium.firefox.FirefoxOptions;
+import org.openqa.selenium.remote.AbstractDriverOptions;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.safari.SafariDriver;
+import org.openqa.selenium.safari.SafariOptions;
+import org.testcontainers.containers.BrowserWebDriverContainer;
+
+import static com.eliasnogueira.config.ConfigurationManager.configuration;
+import static com.eliasnogueira.data.changeless.BrowserData.CHROME_HEADLESS;
+import static com.eliasnogueira.data.changeless.BrowserData.DISABLE_INFOBARS;
+import static com.eliasnogueira.data.changeless.BrowserData.DISABLE_NOTIFICATIONS;
+import static com.eliasnogueira.data.changeless.BrowserData.GENERIC_HEADLESS;
+import static com.eliasnogueira.data.changeless.BrowserData.REMOTE_ALLOW_ORIGINS;
+import static com.eliasnogueira.data.changeless.BrowserData.START_MAXIMIZED;
+import static java.lang.Boolean.TRUE;
+
+public enum BrowserFactory {
+
+ CHROME {
+ @Override
+ public WebDriver createLocalDriver() {
+ return new ChromeDriver(getOptions());
+ }
+
+ @Override
+ public WebDriver createTestContainerDriver() {
+ BrowserWebDriverContainer> driverContainer = new BrowserWebDriverContainer<>().withCapabilities(new ChromeOptions());
+ driverContainer.start();
+
+ return new RemoteWebDriver(driverContainer.getSeleniumAddress(), new ChromeOptions());
+ }
+
+ @Override
+ public ChromeOptions getOptions() {
+ var chromeOptions = new ChromeOptions();
+ chromeOptions.addArguments(START_MAXIMIZED);
+ chromeOptions.addArguments(DISABLE_INFOBARS);
+ chromeOptions.addArguments(DISABLE_NOTIFICATIONS);
+ chromeOptions.addArguments(REMOTE_ALLOW_ORIGINS);
+
+ if (configuration().headless()) chromeOptions.addArguments(CHROME_HEADLESS);
+
+ return chromeOptions;
+ }
+ }, FIREFOX {
+ @Override
+ public WebDriver createLocalDriver() {
+ return new FirefoxDriver(getOptions());
+ }
+
+ @Override
+ public WebDriver createTestContainerDriver() {
+ BrowserWebDriverContainer> driverContainer = new BrowserWebDriverContainer<>().withCapabilities(new FirefoxOptions());
+ driverContainer.start();
+
+ return new RemoteWebDriver(driverContainer.getSeleniumAddress(), new FirefoxOptions());
+ }
+
+ @Override
+ public FirefoxOptions getOptions() {
+ var firefoxOptions = new FirefoxOptions();
+ firefoxOptions.addArguments(START_MAXIMIZED);
+
+ if (configuration().headless()) firefoxOptions.addArguments(GENERIC_HEADLESS);
+
+ return firefoxOptions;
+ }
+ }, EDGE {
+ @Override
+ public WebDriver createLocalDriver() {
+ return new EdgeDriver(getOptions());
+ }
+
+ public WebDriver createTestContainerDriver() {
+ BrowserWebDriverContainer> driverContainer = new BrowserWebDriverContainer<>().withCapabilities(new EdgeOptions());
+ driverContainer.start();
+
+ return new RemoteWebDriver(driverContainer.getSeleniumAddress(), new EdgeOptions());
+ }
+
+ @Override
+ public EdgeOptions getOptions() {
+ var edgeOptions = new EdgeOptions();
+ edgeOptions.addArguments(START_MAXIMIZED);
+
+ if (configuration().headless()) edgeOptions.addArguments(GENERIC_HEADLESS);
+
+ return edgeOptions;
+ }
+ }, SAFARI {
+ @Override
+ public WebDriver createLocalDriver() {
+ return new SafariDriver(getOptions());
+ }
+
+ public WebDriver createTestContainerDriver() {
+ throw new IllegalArgumentException("Browser Safari not supported on TestContainers yet");
+ }
+
+ @Override
+ public SafariOptions getOptions() {
+ var safariOptions = new SafariOptions();
+ safariOptions.setAutomaticInspection(false);
+
+ if (TRUE.equals(configuration().headless()))
+ throw new HeadlessNotSupportedException(safariOptions.getBrowserName());
+
+ return safariOptions;
+ }
+ };
+
+ /**
+ * Used to run local tests where the WebDriverManager will take care of the driver
+ *
+ * @return a new WebDriver instance based on the browser set
+ */
+ public abstract WebDriver createLocalDriver();
+
+ /**
+ * @return a new AbstractDriverOptions instance based on the browser set
+ */
+ public abstract AbstractDriverOptions> getOptions();
+
+ /**
+ * Used to run the remote test execution using Testcontainers
+ *
+ * @return a new WebDriver instance based on the browser set
+ */
+ public abstract WebDriver createTestContainerDriver();
+}
diff --git a/src/main/java/driver/DriverManager.java b/src/main/java/com/eliasnogueira/driver/DriverManager.java
similarity index 88%
rename from src/main/java/driver/DriverManager.java
rename to src/main/java/com/eliasnogueira/driver/DriverManager.java
index a4f614d..680076f 100644
--- a/src/main/java/driver/DriverManager.java
+++ b/src/main/java/com/eliasnogueira/driver/DriverManager.java
@@ -22,9 +22,8 @@
* SOFTWARE.
*/
-package driver;
+package com.eliasnogueira.driver;
-import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
@@ -48,10 +47,11 @@ public static void quit() {
}
public static String getInfo() {
- Capabilities cap = ((RemoteWebDriver) DriverManager.getDriver()).getCapabilities();
+ var cap = ((RemoteWebDriver) DriverManager.getDriver()).getCapabilities();
String browserName = cap.getBrowserName();
- String platform = cap.getPlatform().toString();
- String version = cap.getVersion();
+ String platform = cap.getPlatformName().toString();
+ String version = cap.getBrowserVersion();
+
return String.format("browser: %s v: %s platform: %s", browserName, version, platform);
}
}
diff --git a/src/main/java/com/eliasnogueira/driver/TargetFactory.java b/src/main/java/com/eliasnogueira/driver/TargetFactory.java
new file mode 100644
index 0000000..3919b61
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/driver/TargetFactory.java
@@ -0,0 +1,70 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2021 Elias Nogueira
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.eliasnogueira.driver;
+
+import com.eliasnogueira.enums.Target;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openqa.selenium.MutableCapabilities;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.remote.RemoteWebDriver;
+
+import java.net.URI;
+
+import static com.eliasnogueira.config.ConfigurationManager.configuration;
+import static com.eliasnogueira.driver.BrowserFactory.valueOf;
+import static java.lang.String.format;
+
+public class TargetFactory {
+
+ private static final Logger logger = LogManager.getLogger(TargetFactory.class);
+
+ public WebDriver createInstance(String browser) {
+ Target target = Target.get(configuration().target().toUpperCase());
+
+ return switch (target) {
+ case LOCAL -> valueOf(configuration().browser().toUpperCase()).createLocalDriver();
+ case LOCAL_SUITE -> valueOf(browser.toUpperCase()).createLocalDriver();
+ case SELENIUM_GRID -> createRemoteInstance(valueOf(browser.toUpperCase()).getOptions());
+ case TESTCONTAINERS -> valueOf(configuration().browser().toUpperCase()).createTestContainerDriver();
+ };
+ }
+
+ private RemoteWebDriver createRemoteInstance(MutableCapabilities capability) {
+ RemoteWebDriver remoteWebDriver = null;
+ try {
+ String gridURL = format("http://%s:%s", configuration().gridUrl(), configuration().gridPort());
+
+ remoteWebDriver = new RemoteWebDriver(URI.create(gridURL).toURL(), capability);
+ } catch (java.net.MalformedURLException e) {
+ logger.error("Grid URL is invalid or Grid is not available");
+ logger.error("Browser: {}", capability.getBrowserName(), e);
+ } catch (IllegalArgumentException e) {
+ logger.error("Browser {} is not valid or recognized", capability.getBrowserName(), e);
+ }
+
+ return remoteWebDriver;
+ }
+}
diff --git a/src/main/java/enums/RoomType.java b/src/main/java/com/eliasnogueira/enums/RoomType.java
similarity index 85%
rename from src/main/java/enums/RoomType.java
rename to src/main/java/com/eliasnogueira/enums/RoomType.java
index 0b7fab8..d7b0980 100644
--- a/src/main/java/enums/RoomType.java
+++ b/src/main/java/com/eliasnogueira/enums/RoomType.java
@@ -22,11 +22,11 @@
* SOFTWARE.
*/
-package enums;
+package com.eliasnogueira.enums;
-import java.util.Random;
+import java.util.function.Supplier;
-public enum RoomType {
+public enum RoomType implements Supplier {
SINGLE("Single"), FAMILY("Family"), BUSINESS("Business");
@@ -36,12 +36,8 @@ public enum RoomType {
this.value = value;
}
- public static RoomType getRandom() {
- return values()[new Random().nextInt(values().length)];
- }
-
@Override
- public String toString() {
- return value;
+ public String get() {
+ return this.value;
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/enums/Target.java b/src/main/java/com/eliasnogueira/enums/Target.java
similarity index 51%
rename from src/main/java/enums/Target.java
rename to src/main/java/com/eliasnogueira/enums/Target.java
index 21d0dfd..d9dfa4e 100644
--- a/src/main/java/enums/Target.java
+++ b/src/main/java/com/eliasnogueira/enums/Target.java
@@ -1,7 +1,7 @@
/*
* MIT License
*
- * Copyright (c) 2018 Elias Nogueira
+ * Copyright (c) 2022 Elias Nogueira
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,8 +22,37 @@
* SOFTWARE.
*/
-package enums;
+package com.eliasnogueira.enums;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.toMap;
public enum Target {
- LOCAL, GRID
+
+ LOCAL("local"), LOCAL_SUITE("local-suite"), SELENIUM_GRID("selenium-grid"),
+ TESTCONTAINERS("testcontainers");
+
+ private final String value;
+ private static final Map ENUM_MAP;
+
+ Target(String value) {
+ this.value = value;
+ }
+
+ static {
+ Map map = stream(Target.values())
+ .collect(toMap(instance -> instance.value.toLowerCase(), instance -> instance, (_, b) -> b, ConcurrentHashMap::new));
+ ENUM_MAP = Collections.unmodifiableMap(map);
+ }
+
+ public static Target get(String value) {
+ if (!ENUM_MAP.containsKey(value.toLowerCase()))
+ throw new IllegalArgumentException(String.format("Value %s not valid. Use one of the TARGET enum values", value));
+
+ return ENUM_MAP.get(value.toLowerCase());
+ }
}
diff --git a/src/main/java/driver/IDriver.java b/src/main/java/com/eliasnogueira/exceptions/HeadlessNotSupportedException.java
similarity index 78%
rename from src/main/java/driver/IDriver.java
rename to src/main/java/com/eliasnogueira/exceptions/HeadlessNotSupportedException.java
index 61ef761..67e6a0e 100644
--- a/src/main/java/driver/IDriver.java
+++ b/src/main/java/com/eliasnogueira/exceptions/HeadlessNotSupportedException.java
@@ -1,7 +1,7 @@
/*
* MIT License
*
- * Copyright (c) 2018 Elias Nogueira
+ * Copyright (c) 2021 Elias Nogueira
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,11 +22,11 @@
* SOFTWARE.
*/
-package driver;
+package com.eliasnogueira.exceptions;
-import org.openqa.selenium.WebDriver;
+public class HeadlessNotSupportedException extends IllegalStateException {
-public interface IDriver {
-
- WebDriver createInstance(String browser);
+ public HeadlessNotSupportedException(String browser) {
+ super(String.format("Headless not supported for %s browser", browser));
+ }
}
diff --git a/src/main/java/com/eliasnogueira/model/Booking.java b/src/main/java/com/eliasnogueira/model/Booking.java
new file mode 100644
index 0000000..5348d03
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/model/Booking.java
@@ -0,0 +1,81 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 Elias Nogueira
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.eliasnogueira.model;
+
+import com.eliasnogueira.enums.RoomType;
+
+public record Booking(String email, String country, String password, String dailyBudget, Boolean newsletter,
+ RoomType roomType, String roomDescription) {
+
+ public static final class BookingBuilder {
+
+ private String email;
+ private String country;
+ private String password;
+ private String dailyBudget;
+ private Boolean newsletter;
+ private RoomType roomType;
+ private String roomDescription;
+
+ public BookingBuilder email(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public BookingBuilder country(String country) {
+ this.country = country;
+ return this;
+ }
+
+ public BookingBuilder password(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public BookingBuilder dailyBudget(String dailyBudget) {
+ this.dailyBudget = dailyBudget;
+ return this;
+ }
+
+ public BookingBuilder newsletter(Boolean newsletter) {
+ this.newsletter = newsletter;
+ return this;
+ }
+
+ public BookingBuilder roomType(RoomType roomType) {
+ this.roomType = roomType;
+ return this;
+ }
+
+ public BookingBuilder roomDescription(String roomDescription) {
+ this.roomDescription = roomDescription;
+ return this;
+ }
+
+ public Booking build() {
+ return new Booking(email, country, password, dailyBudget, newsletter, roomType, roomDescription);
+ }
+ }
+}
diff --git a/src/main/java/page/objects/AbstractPageObject.java b/src/main/java/com/eliasnogueira/page/AbstractPageObject.java
similarity index 75%
rename from src/main/java/page/objects/AbstractPageObject.java
rename to src/main/java/com/eliasnogueira/page/AbstractPageObject.java
index 3282069..827215d 100644
--- a/src/main/java/page/objects/AbstractPageObject.java
+++ b/src/main/java/com/eliasnogueira/page/AbstractPageObject.java
@@ -22,20 +22,17 @@
* SOFTWARE.
*/
-package page.objects;
+package com.eliasnogueira.page;
-import config.Configuration;
-import config.ConfigurationManager;
-import driver.DriverManager;
-import org.openqa.selenium.support.PageFactory;
+import com.eliasnogueira.driver.DriverManager;
import org.openqa.selenium.support.pagefactory.AjaxElementLocatorFactory;
+import static com.eliasnogueira.config.ConfigurationManager.configuration;
+import static org.openqa.selenium.support.PageFactory.initElements;
+
public class AbstractPageObject {
protected AbstractPageObject() {
- Configuration configuration = ConfigurationManager.getConfiguration();
- int timeout = Integer.parseInt(configuration.timeout());
-
- PageFactory.initElements(new AjaxElementLocatorFactory(DriverManager.getDriver(), timeout), this);
+ initElements(new AjaxElementLocatorFactory(DriverManager.getDriver(), configuration().timeout()), this);
}
}
diff --git a/src/main/java/page/objects/booking/AccountPage.java b/src/main/java/com/eliasnogueira/page/booking/AccountPage.java
similarity index 92%
rename from src/main/java/page/objects/booking/AccountPage.java
rename to src/main/java/com/eliasnogueira/page/booking/AccountPage.java
index 8323433..aae5331 100644
--- a/src/main/java/page/objects/booking/AccountPage.java
+++ b/src/main/java/com/eliasnogueira/page/booking/AccountPage.java
@@ -22,12 +22,13 @@
* SOFTWARE.
*/
-package page.objects.booking;
+package com.eliasnogueira.page.booking;
+import com.eliasnogueira.page.booking.common.NavigationPage;
+import io.qameta.allure.Step;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select;
-import page.objects.booking.common.NavigationPage;
public class AccountPage extends NavigationPage {
@@ -46,22 +47,27 @@ public class AccountPage extends NavigationPage {
@FindBy(css = ".check")
private WebElement newsletter;
+ @Step
public void fillEmail(String email) {
this.email.sendKeys(email);
}
+ @Step
public void fillPassword(String password) {
this.password.sendKeys(password);
}
+ @Step
public void selectCountry(String country) {
new Select(this.country).selectByVisibleText(country);
}
+ @Step
public void selectBudget(String value) {
new Select(budget).selectByVisibleText(value);
}
+ @Step
public void clickNewsletter() {
newsletter.click();
}
diff --git a/src/main/java/page/objects/booking/DetailPage.java b/src/main/java/com/eliasnogueira/page/booking/DetailPage.java
similarity index 89%
rename from src/main/java/page/objects/booking/DetailPage.java
rename to src/main/java/com/eliasnogueira/page/booking/DetailPage.java
index c7181e1..2ba247f 100644
--- a/src/main/java/page/objects/booking/DetailPage.java
+++ b/src/main/java/com/eliasnogueira/page/booking/DetailPage.java
@@ -22,13 +22,14 @@
* SOFTWARE.
*/
-package page.objects.booking;
+package com.eliasnogueira.page.booking;
-import driver.DriverManager;
+import com.eliasnogueira.driver.DriverManager;
+import com.eliasnogueira.page.booking.common.NavigationPage;
+import io.qameta.allure.Step;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.FindBy;
-import page.objects.booking.common.NavigationPage;
public class DetailPage extends NavigationPage {
@@ -38,10 +39,12 @@ public class DetailPage extends NavigationPage {
@FindBy(css = "#message > p")
private WebElement message;
+ @Step
public void fillRoomDescription(String description) {
new Actions(DriverManager.getDriver()).sendKeys(roomDescription, description);
}
+ @Step
public String getAlertMessage() {
return message.getText();
}
diff --git a/src/main/java/page/objects/booking/RoomPage.java b/src/main/java/com/eliasnogueira/page/booking/RoomPage.java
similarity index 84%
rename from src/main/java/page/objects/booking/RoomPage.java
rename to src/main/java/com/eliasnogueira/page/booking/RoomPage.java
index 67a3327..5b14b06 100644
--- a/src/main/java/page/objects/booking/RoomPage.java
+++ b/src/main/java/com/eliasnogueira/page/booking/RoomPage.java
@@ -22,16 +22,17 @@
* SOFTWARE.
*/
-package page.objects.booking;
+package com.eliasnogueira.page.booking;
-import driver.DriverManager;
-import enums.RoomType;
+import com.eliasnogueira.driver.DriverManager;
+import com.eliasnogueira.page.booking.common.NavigationPage;
+import io.qameta.allure.Step;
import org.openqa.selenium.By;
-import page.objects.booking.common.NavigationPage;
public class RoomPage extends NavigationPage {
- public void selectRoomType(RoomType room) {
+ @Step
+ public void selectRoomType(String room) {
DriverManager.getDriver().findElement(By.xpath("//h6[text()='" + room + "']")).click();
}
}
diff --git a/src/main/java/page/objects/booking/common/NavigationPage.java b/src/main/java/com/eliasnogueira/page/booking/common/NavigationPage.java
similarity index 91%
rename from src/main/java/page/objects/booking/common/NavigationPage.java
rename to src/main/java/com/eliasnogueira/page/booking/common/NavigationPage.java
index 12dedde..ceafb1d 100644
--- a/src/main/java/page/objects/booking/common/NavigationPage.java
+++ b/src/main/java/com/eliasnogueira/page/booking/common/NavigationPage.java
@@ -22,11 +22,12 @@
* SOFTWARE.
*/
-package page.objects.booking.common;
+package com.eliasnogueira.page.booking.common;
+import com.eliasnogueira.page.AbstractPageObject;
+import io.qameta.allure.Step;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
-import page.objects.AbstractPageObject;
public class NavigationPage extends AbstractPageObject {
@@ -39,14 +40,12 @@ public class NavigationPage extends AbstractPageObject {
@FindBy(name = "finish")
private WebElement finish;
+ @Step
public void next() {
next.click();
}
- public void previous() {
- previous.click();
- }
-
+ @Step
public void finish() {
finish.click();
}
diff --git a/src/main/java/com/eliasnogueira/report/AllureManager.java b/src/main/java/com/eliasnogueira/report/AllureManager.java
new file mode 100644
index 0000000..2180e2e
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/report/AllureManager.java
@@ -0,0 +1,58 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 Elias Nogueira
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.eliasnogueira.report;
+
+import com.eliasnogueira.enums.Target;
+import com.github.automatedowl.tools.AllureEnvironmentWriter;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.eliasnogueira.config.ConfigurationManager.configuration;
+
+public class AllureManager {
+
+ private AllureManager() {
+ }
+
+ public static void setAllureEnvironmentInformation() {
+ var basicInfo = new HashMap<>(Map.of(
+ "Test URL", configuration().url(),
+ "Target execution", configuration().target(),
+ "Global timeout", String.valueOf(configuration().timeout()),
+ "Headless mode", String.valueOf(configuration().headless()),
+ "Faker locale", configuration().faker(),
+ "Local browser", configuration().browser()
+ ));
+
+ if (configuration().target().equals(Target.SELENIUM_GRID.name())) {
+ var gridMap = Map.of("Grid URL", configuration().gridUrl(), "Grid port", configuration().gridPort());
+ basicInfo.putAll(gridMap);
+ }
+
+ AllureEnvironmentWriter.allureEnvironmentWriter(ImmutableMap.copyOf(basicInfo));
+ }
+}
diff --git a/src/main/java/driver/DriverFactory.java b/src/main/java/com/eliasnogueira/report/AllureTestLifecycleListener.java
similarity index 50%
rename from src/main/java/driver/DriverFactory.java
rename to src/main/java/com/eliasnogueira/report/AllureTestLifecycleListener.java
index 8fc178e..4d09ef2 100644
--- a/src/main/java/driver/DriverFactory.java
+++ b/src/main/java/com/eliasnogueira/report/AllureTestLifecycleListener.java
@@ -1,7 +1,7 @@
/*
* MIT License
*
- * Copyright (c) 2018 Elias Nogueira
+ * Copyright (c) 2024 Elias Nogueira
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,41 +21,36 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-
-package driver;
-
-import config.Configuration;
-import config.ConfigurationManager;
-import driver.local.LocalDriverManager;
-import driver.remote.RemoteDriverManager;
-import enums.Target;
-import lombok.extern.log4j.Log4j2;
+package com.eliasnogueira.report;
+
+import com.eliasnogueira.driver.DriverManager;
+import io.qameta.allure.Attachment;
+import io.qameta.allure.listener.TestLifecycleListener;
+import io.qameta.allure.model.TestResult;
+import org.openqa.selenium.OutputType;
+import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
-@Log4j2
-public class DriverFactory {
+import static io.qameta.allure.model.Status.BROKEN;
+import static io.qameta.allure.model.Status.FAILED;
- private DriverFactory() {}
+/*
+ * Approach implemented using the https://github.com/biczomate/allure-testng7.5-attachment-example as reference
+ */
+public class AllureTestLifecycleListener implements TestLifecycleListener {
- public static WebDriver createInstance(String browser) {
- Configuration configuration = ConfigurationManager.getConfiguration();
- Target target = Target.valueOf(configuration.target().toUpperCase());
- WebDriver webdriver;
+ public AllureTestLifecycleListener() {
+ }
- switch (target) {
+ @Attachment(value = "Page Screenshot", type = "image/png")
+ public byte[] saveScreenshot(WebDriver driver) {
+ return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
+ }
- case LOCAL:
- //override the browser value from @Optional on BeseWeb
- webdriver = new LocalDriverManager().createInstance(configuration.browser());
- break;
- case GRID:
- // getting the browser from the suite file or @Optional on BaseWeb
- webdriver = new RemoteDriverManager().createInstance(browser);
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + target);
+ @Override
+ public void beforeTestStop(TestResult result) {
+ if (FAILED == result.getStatus() || BROKEN == result.getStatus()) {
+ saveScreenshot(DriverManager.getDriver());
}
-
- return webdriver;
}
}
diff --git a/src/main/java/driver/local/LocalDriverManager.java b/src/main/java/driver/local/LocalDriverManager.java
deleted file mode 100644
index 8b9d82e..0000000
--- a/src/main/java/driver/local/LocalDriverManager.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2018 Elias Nogueira
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package driver.local;
-
-import static java.lang.Boolean.TRUE;
-
-import config.Configuration;
-import config.ConfigurationManager;
-import driver.IDriver;
-import io.github.bonigarcia.wdm.WebDriverManager;
-import io.github.bonigarcia.wdm.config.DriverManagerType;
-import java.lang.reflect.InvocationTargetException;
-import lombok.extern.log4j.Log4j2;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.chrome.ChromeDriver;
-import org.openqa.selenium.chrome.ChromeOptions;
-import org.openqa.selenium.firefox.FirefoxDriver;
-import org.openqa.selenium.firefox.FirefoxOptions;
-
-@Log4j2
-public class LocalDriverManager implements IDriver {
-
- @Override
- public WebDriver createInstance(String browser) {
- WebDriver driverInstance = null;
-
- try {
- DriverManagerType driverManagerType = DriverManagerType.valueOf(browser.toUpperCase());
- Class> driverClass = Class.forName(driverManagerType.browserClass());
- WebDriverManager.getInstance(driverManagerType).setup();
- Configuration configuration = ConfigurationManager.getConfiguration();
-
- if (TRUE.equals(configuration.headless())) {
- driverInstance = defineHeadless(driverClass);
- } else {
- driverInstance = (WebDriver) driverClass.getDeclaredConstructor().newInstance();
- }
-
- } catch (IllegalAccessException | ClassNotFoundException e) {
- log.error("The class could not be found", e);
- } catch (InstantiationException | NoSuchMethodException | InvocationTargetException e) {
- log.error("Problem during driver instantiation", e);
- }
- return driverInstance;
- }
-
- private WebDriver defineHeadless(Class> driverClass) {
- WebDriver driver;
-
- if (ChromeDriver.class == driverClass) {
- driver = new ChromeDriver(new ChromeOptions().setHeadless(true));
- } else if (FirefoxDriver.class == driverClass) {
- driver = new FirefoxDriver(new FirefoxOptions().setHeadless(true));
- } else {
- throw new IllegalArgumentException("Headless is only supported by Google Chrome or Firefox");
- }
-
- return driver;
- }
-}
diff --git a/src/main/java/driver/remote/RemoteDriverManager.java b/src/main/java/driver/remote/RemoteDriverManager.java
deleted file mode 100644
index aff023d..0000000
--- a/src/main/java/driver/remote/RemoteDriverManager.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2018 Elias Nogueira
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package driver.remote;
-
-import config.Configuration;
-import config.ConfigurationManager;
-import driver.IDriver;
-import io.github.bonigarcia.wdm.config.DriverManagerType;
-import java.net.MalformedURLException;
-import java.net.URL;
-import lombok.extern.log4j.Log4j2;
-import org.openqa.selenium.MutableCapabilities;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.chrome.ChromeOptions;
-import org.openqa.selenium.edge.EdgeOptions;
-import org.openqa.selenium.firefox.FirefoxOptions;
-import org.openqa.selenium.ie.InternetExplorerOptions;
-import org.openqa.selenium.opera.OperaOptions;
-import org.openqa.selenium.remote.RemoteWebDriver;
-
-@Log4j2
-public class RemoteDriverManager implements IDriver {
-
- @Override
- public WebDriver createInstance(String browser) {
- RemoteWebDriver remoteWebDriver = null;
- Configuration configuration = ConfigurationManager.getConfiguration();
- try {
- // a composition of the target grid address and port
- String gridURL = String.format("http://%s:%s/wd/hub", configuration.gridUrl(), configuration.gridPort());
-
- remoteWebDriver = new RemoteWebDriver(new URL(gridURL), getCapability(browser));
- } catch (MalformedURLException e) {
- log.error("Grid URL is invalid or Grid is not available");
- log.error("Browser: " + browser, e);
- } catch (IllegalArgumentException e) {
- log.error("Browser: " + browser + "is not valid or recognized", e);
- }
-
- return remoteWebDriver;
- }
-
- private static MutableCapabilities getCapability(String browser) {
- MutableCapabilities mutableCapabilities;
- DriverManagerType driverManagerType = DriverManagerType.valueOf(browser.toUpperCase());
-
- switch (driverManagerType) {
-
- case CHROME:
- mutableCapabilities = defaultChromeOptions();
- break;
- case FIREFOX:
- mutableCapabilities = new FirefoxOptions();
- break;
- case OPERA:
- mutableCapabilities = new OperaOptions();
- break;
- case EDGE:
- mutableCapabilities = new EdgeOptions();
- break;
- case IEXPLORER:
- mutableCapabilities = new InternetExplorerOptions();
- break;
- case PHANTOMJS:
- case SELENIUM_SERVER_STANDALONE:
- throw new IllegalStateException("Not supported: " + driverManagerType);
- default:
- throw new IllegalStateException("Unexpected value: " + driverManagerType);
- }
-
- return mutableCapabilities;
- }
-
- private static MutableCapabilities defaultChromeOptions() {
- ChromeOptions capabilities = new ChromeOptions();
- capabilities.addArguments("start-maximized");
-
- return capabilities;
- }
-}
diff --git a/src/main/java/report/AllureManager.java b/src/main/java/report/AllureManager.java
deleted file mode 100644
index ac9212b..0000000
--- a/src/main/java/report/AllureManager.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package report;
-
-import com.github.automatedowl.tools.AllureEnvironmentWriter;
-import com.google.common.collect.ImmutableMap;
-import config.Configuration;
-import config.ConfigurationManager;
-import driver.DriverManager;
-import io.qameta.allure.Attachment;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-
-public class AllureManager {
-
- private AllureManager() {}
-
- public static void setAllureEnvironmentInformation() {
- Configuration configuration = ConfigurationManager.getConfiguration();
-
- AllureEnvironmentWriter.allureEnvironmentWriter(
- ImmutableMap.builder().
- put("URL", configuration.url()).
- put("Target", configuration.target()).
- build());
- }
-
- @Attachment(value = "Failed test screenshot", type = "image/png")
- public static byte[] takeScreenshotToAttachOnAllureReport() {
- return ((TakesScreenshot) DriverManager.getDriver()).getScreenshotAs(OutputType.BYTES);
- }
-
- @Attachment(value = "Browser information", type = "text/plain")
- public static String addBrowserInformationOnAllureReport() {
- return DriverManager.getInfo();
- }
-
-}
diff --git a/src/main/java/test/TestListener.java b/src/main/java/test/TestListener.java
deleted file mode 100644
index 1a4091a..0000000
--- a/src/main/java/test/TestListener.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2018 Elias Nogueira
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-package test;
-
-import lombok.extern.log4j.Log4j2;
-import org.testng.ITestContext;
-import org.testng.ITestListener;
-import org.testng.ITestResult;
-import report.AllureManager;
-
-@Log4j2
-public class TestListener implements ITestListener {
-
- @Override
- public void onTestStart(ITestResult result) {
- // empty
- }
-
- @Override
- public void onTestSuccess(ITestResult result) {
- // empty
- }
-
- @Override
- public void onTestFailure(ITestResult result) {
- failTest(result);
- }
-
- @Override
- public void onTestSkipped(ITestResult result) {
- log.error(result.getThrowable());
- }
-
- @Override
- public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
- // empty
- }
-
- @Override
- public void onStart(ITestContext context) {
- // empty
- }
-
- @Override
- public void onFinish(ITestContext context) {
- // empty
- }
-
- private void failTest(ITestResult iTestResult) {
- log.error(iTestResult.getTestClass().getName());
- log.error(iTestResult.getThrowable());
-
- AllureManager.takeScreenshotToAttachOnAllureReport();
- }
-}
diff --git a/src/main/resources/conf/general.properties b/src/main/resources/conf/general.properties
deleted file mode 100644
index 45685b5..0000000
--- a/src/main/resources/conf/general.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# target execution: local or grid
-target = local
-
-# initial URL
-url.base = https://page-example.imfast.io/
-
-# global test timeout
-timeout = 3
-
-# javafaker locale
-faker.locale = pt-BR
-
-# headless mode only for chrome or firefox and local execution
-headless = true
\ No newline at end of file
diff --git a/src/main/resources/conf/local.properties b/src/main/resources/conf/local.properties
deleted file mode 100644
index 07d1868..0000000
--- a/src/main/resources/conf/local.properties
+++ /dev/null
@@ -1 +0,0 @@
-browser = chrome
\ No newline at end of file
diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties
index 953edb4..d41ecde 100644
--- a/src/main/resources/log4j2.properties
+++ b/src/main/resources/log4j2.properties
@@ -13,7 +13,7 @@ appender.file.fileName=${sys:user.home}/test_automation.log
appender.file.layout.type=PatternLayout
appender.file.layout.pattern=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n
-rootLogger.level = debug
+rootLogger.level = info
rootLogger.appenderRefs = stdout
rootLogger.appenderRef.stdout.ref = STDOUT
-rootLogger.appenderRef.file.ref = LOGFILE
\ No newline at end of file
+rootLogger.appenderRef.file.ref = LOGFILE
diff --git a/src/main/java/test/BaseWeb.java b/src/test/java/com/eliasnogueira/BaseWeb.java
similarity index 80%
rename from src/main/java/test/BaseWeb.java
rename to src/test/java/com/eliasnogueira/BaseWeb.java
index 7fcd600..cd5c894 100644
--- a/src/main/java/test/BaseWeb.java
+++ b/src/test/java/com/eliasnogueira/BaseWeb.java
@@ -22,22 +22,20 @@
* SOFTWARE.
*/
-package test;
+package com.eliasnogueira;
-import config.Configuration;
-import config.ConfigurationManager;
-import driver.DriverFactory;
-import driver.DriverManager;
+import com.eliasnogueira.driver.DriverManager;
+import com.eliasnogueira.driver.TargetFactory;
+import com.eliasnogueira.report.AllureManager;
import org.openqa.selenium.WebDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeSuite;
-import org.testng.annotations.Listeners;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
-import report.AllureManager;
-@Listeners({TestListener.class})
+import static com.eliasnogueira.config.ConfigurationManager.configuration;
+
public abstract class BaseWeb {
@BeforeSuite
@@ -48,12 +46,10 @@ public void beforeSuite() {
@BeforeMethod(alwaysRun = true)
@Parameters("browser")
public void preCondition(@Optional("chrome") String browser) {
- Configuration configuration = ConfigurationManager.getConfiguration();
-
- WebDriver driver = DriverFactory.createInstance(browser);
+ WebDriver driver = new TargetFactory().createInstance(browser);
DriverManager.setDriver(driver);
- DriverManager.getDriver().get(configuration.url());
+ DriverManager.getDriver().get(configuration().url());
}
@AfterMethod(alwaysRun = true)
diff --git a/src/test/java/test/BookRoomWebTest.java b/src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
similarity index 61%
rename from src/test/java/test/BookRoomWebTest.java
rename to src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
index ae404a7..7471352 100644
--- a/src/test/java/test/BookRoomWebTest.java
+++ b/src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
@@ -21,40 +21,40 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-package test;
+package com.eliasnogueira.test;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import data.BookingDataFactory;
-import model.Booking;
+import com.eliasnogueira.BaseWeb;
+import com.eliasnogueira.data.dynamic.BookingDataFactory;
+import com.eliasnogueira.page.booking.AccountPage;
+import com.eliasnogueira.page.booking.DetailPage;
+import com.eliasnogueira.page.booking.RoomPage;
import org.testng.annotations.Test;
-import page.objects.booking.AccountPage;
-import page.objects.booking.DetailPage;
-import page.objects.booking.RoomPage;
+
+import static org.assertj.core.api.Assertions.assertThat;
public class BookRoomWebTest extends BaseWeb {
@Test(description = "Book a room")
public void bookARoom() {
- Booking bookingInformation = new BookingDataFactory().createBookingData();
+ var bookingInformation = BookingDataFactory.createBookingData();
- AccountPage accountPage = new AccountPage();
- accountPage.fillEmail(bookingInformation.getEmail());
- accountPage.fillPassword(bookingInformation.getPassword());
- accountPage.selectCountry(bookingInformation.getCountry());
- accountPage.selectBudget(bookingInformation.getDailyBudget());
+ var accountPage = new AccountPage();
+ accountPage.fillEmail(bookingInformation.email());
+ accountPage.fillPassword(bookingInformation.password());
+ accountPage.selectCountry(bookingInformation.country());
+ accountPage.selectBudget(bookingInformation.dailyBudget());
accountPage.clickNewsletter();
accountPage.next();
- RoomPage roomPage = new RoomPage();
- roomPage.selectRoomType(bookingInformation.getRoomType());
+ var roomPage = new RoomPage();
+ roomPage.selectRoomType(bookingInformation.roomType().get());
roomPage.next();
- DetailPage detailPage = new DetailPage();
- detailPage.fillRoomDescription(bookingInformation.getRoomDescription());
+ var detailPage = new DetailPage();
+ detailPage.fillRoomDescription(bookingInformation.roomDescription());
detailPage.finish();
assertThat(detailPage.getAlertMessage())
- .isEqualTo("Your reservation has been made and we will contact you shortly");
+ .isEqualTo("Your reservation has been made and we will contact you shortly");
}
}
diff --git a/src/test/resources/META-INF/services/io.qameta.allure.listener.TestLifecycleListener b/src/test/resources/META-INF/services/io.qameta.allure.listener.TestLifecycleListener
new file mode 100644
index 0000000..dda40f5
--- /dev/null
+++ b/src/test/resources/META-INF/services/io.qameta.allure.listener.TestLifecycleListener
@@ -0,0 +1 @@
+com.eliasnogueira.report.AllureTestLifecycleListener
diff --git a/src/test/resources/allure.properties b/src/test/resources/allure.properties
new file mode 100644
index 0000000..80b02dd
--- /dev/null
+++ b/src/test/resources/allure.properties
@@ -0,0 +1 @@
+allure.results.directory=target/allure-results
diff --git a/src/test/resources/general.properties b/src/test/resources/general.properties
new file mode 100644
index 0000000..15bb713
--- /dev/null
+++ b/src/test/resources/general.properties
@@ -0,0 +1,17 @@
+# target execution: local, selenium-grid or testcontainers
+target = local
+
+# browser to use for local and testcontainers execution
+browser = chrome
+
+# initial URL
+url.base = https://eliasnogueira.com/external/selenium-java-architecture/
+
+# global test timeout
+timeout = 3
+
+# datafaker locale
+faker.locale = en-US
+
+# headless mode only for chrome or firefox and local execution
+headless = false
diff --git a/src/main/resources/conf/grid.properties b/src/test/resources/selenium-grid.properties
similarity index 70%
rename from src/main/resources/conf/grid.properties
rename to src/test/resources/selenium-grid.properties
index eb20844..90d2540 100644
--- a/src/main/resources/conf/grid.properties
+++ b/src/test/resources/selenium-grid.properties
@@ -1,3 +1,3 @@
# grid url and port
grid.url = localhost
-grid.port = 4444
\ No newline at end of file
+grid.port = 4444
diff --git a/src/test/resources/suites/local.xml b/src/test/resources/suites/local.xml
new file mode 100644
index 0000000..089ee73
--- /dev/null
+++ b/src/test/resources/suites/local.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/resources/suites/multi_browser.xml b/src/test/resources/suites/selenium-grid.xml
similarity index 74%
rename from src/test/resources/suites/multi_browser.xml
rename to src/test/resources/suites/selenium-grid.xml
index 9bec0be..04d9024 100644
--- a/src/test/resources/suites/multi_browser.xml
+++ b/src/test/resources/suites/selenium-grid.xml
@@ -4,15 +4,15 @@
-
+
-
+
-
\ No newline at end of file
+
diff --git a/src/test/resources/suites/suite.xml b/src/test/resources/suites/suite.xml
deleted file mode 100644
index b5bfca7..0000000
--- a/src/test/resources/suites/suite.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file