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
index 380c8a6..338cdd1 100644
--- a/.github/workflows/test-execution.yml
+++ b/.github/workflows/test-execution.yml
@@ -2,30 +2,32 @@ name: Build and Test
on:
push:
branches:
- - master
+ - main
pull_request:
branches:
- - master
+ - main
jobs:
- build:
- runs-on: ubuntu-latest
- services:
- chrome:
- image: selenium/standalone-chrome
- options: --health-cmd '/opt/bin/check-grid.sh'
+ local-test:
+ runs-on: macos-latest
+
steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 11
- uses: actions/setup-java@v1
+ - uses: actions/checkout@v4
+ - name: Set up JDK 24
+ uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 24
+ distribution: oracle
+
- name: Cache Maven packages
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
- path: ~/.m2
- key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- restore-keys: ${{ runner.os }}-m2
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
- name: Build with Maven
- run: mvn -DskipTests -B package --file pom.xml
- - name: Run the tests
- run: mvn test -Pweb-execution -Dsuite=local -Dtarget=local -Dheadless=true -Dbrowser=chrome -Dtestng.dtd.http=true
\ No newline at end of file
+ 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/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 7cada51..bf76065 100644
--- a/pipeline_as_code/.gitlab-ci.yml
+++ b/.sdlc/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: maven:3.6.3-jdk-11
+image: maven:3.8.4-openjdk-17
stages:
- build
@@ -17,4 +17,4 @@ build:
test:
stage: test
script:
- - mvn test -Pweb-execution -Dsuite=local -Dtarget=local -Dtestng.dtd.http=true
\ 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 89%
rename from pipeline_as_code/Jenkinsfile
rename to .sdlc/Jenkinsfile
index 5261f50..21899b8 100644
--- a/pipeline_as_code/Jenkinsfile
+++ b/.sdlc/Jenkinsfile
@@ -12,7 +12,7 @@ node {
stage('Test Execution') {
try {
- sh "'${mvnHome}/bin/mvn' test -Pweb-execution -Dtarget=local -Dsuite=local -Dtestng.dtd.http=true"
+ 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 6234999..21a0ef8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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 3627c7e..d90fecc 100644
--- a/README.MD
+++ b/README.MD
@@ -1,38 +1,45 @@
# Lean Test Automation Architecture using Java and Selenium WebDriver
+
[](https://github.com/eliasnogueira/selenium-java-lean-test-achitecture/actions)
-## Important information
-```shell
-This current version has excluded the guava library from WebDriverManager and Allure Environment Writter
-due to a conflict with the guava version on Selenium 4
-```
+**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
-**This project delivers to you a complete lean test architecture for your web tests using the best frameworks and practices.**
+
-Local testing execution example
-
+### Parallel testing execution example with Selenium Grid
-Parallel testing execution example with Docker Selenium
-
+
## 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
* [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
+* [DataFaker](https://www.datafaker.net/) as the faker data generation strategy
* [Log4J2](https://logging.apache.org/log4j/2.x/) as the logging management strategy
-* [WebDriverManager](https://github.com/bonigarcia/webdrivermanager) as the Selenium binaries management
* [Owner](http://owner.aeonbits.org/) to minimize the code to handle the properties file
-
+* [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:
@@ -46,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 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`.
@@ -65,87 +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**.
-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.
+There are different execution types:
+
+- `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.
-When the `target` is `local` the `createDriver()` 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 `local.properties` file.
+##### 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 is based on any Selenium Grid approach to execute the tests in remote machines (local or remote/cloud grid).
-When the `target` is `remote` the `getOptions` method is used from the `BrowserFactory` to return the browser option
+
+##### 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.
-The `DriverFactory` class has an internal method `createRemoteInstance` to return a `RemoteWebDriver` instance based on
+
+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 Docker Selenium grid, the values on the `grid.properties` 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.
-Please take a look at the [Parallel Execution](#parallel-execution) section.
+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
+
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 two methods:
-* `createDriver()`: creates the browser instance for the local execution. The browser driver is automatically managed by the WebDriverManager library
-* `getOptions()`: creates a new browser Options setting some specific configurations. It's used for the remote executions
+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 `createDriver()` method used the `getOptions()` to use specific configuration, 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.
+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)
+
+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-lean-test-achitecture/blob/master/src/test/java/com/eliasnogueira/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 `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 into 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 on the log
+* `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-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/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 used by `parallel.xml` test suite file which has the `parallel="tests"` attribute and value,
+
+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
+
```
@@ -154,55 +229,80 @@ 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.
+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 not you need the following before run it in parallel:
+Please note that you need to do the following actions before running it in parallel:
+
* Docker installed
-* Pull the images for Chrome and Firefox
- * `docker pull selenium-standalog-chrome`
- * `docker pull selenium-standalog-firefox`
-* Pay attention at the `grid/config.toml` file that has comments for each specific SO
-* Start the Grid running the following command inside the `grid` folder
- * `docker-compose up`
+* 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-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/`:
-* `general.properties`: general configuration as the target execution, base url, timeout, and faker locale
+
+* `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
-* `local.properties`: browser to use in the local execution
-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.
+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-lean-test-achitecture/blob/master/src/main/java/com/eliasnogueira/data/BookingDataFactory.java)
+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-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.
+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 ses the use 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.
+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 `web-execution` created to execute the test suite `local.xml` inside `src/test/resources/suites` folder.
+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 -Pweb-execution -Dtestng.dtd.http=true
+mvn test -Pweb-execution
```
If you have more than one suite on _src/test/resources/suites_ folder you can parameterize the xml file name.
@@ -211,33 +311,36 @@ To do this you need:
* Create a property on `pom.xml` called _suite_
```xml
-
- local
-
+
+
+ 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=parallel -Dtestng.dtd.http=true
+mvn test -Pweb-execution -Dsuite=suite_name
````
### Pipeline as a code
@@ -246,4 +349,4 @@ The two files of the pipeline as a code are inside `pipeline_as_code` folder.
* 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`
\ No newline at end of file
+* 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/selenium-grid-execution.gif b/assets/selenium-grid-execution.gif
similarity index 100%
rename from selenium-grid-execution.gif
rename to assets/selenium-grid-execution.gif
diff --git a/grid/config.toml b/grid/config.toml
index 9caf674..2d961ae 100644
--- a/grid/config.toml
+++ b/grid/config.toml
@@ -2,21 +2,27 @@
# 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:4.0.0-20211013", "{\"browserName\": \"firefox\"}",
- "selenium/standalone-chrome:4.0.0-20211013", "{\"browserName\": \"chrome\"}"
+ "selenium/standalone-firefox:latest", "{\"browserName\": \"firefox\"}",
+ "selenium/standalone-chrome:latest", "{\"browserName\": \"chrome\"}"
]
+host-config-keys = ["Binds"]
+
# URL for connecting to the docker daemon
-# host.docker.internal works for macOS and Windows.
-# Linux could use --net=host in the `docker run` instruction or 172.17.0.1 in the URI below.
-# To have Docker listening through tcp on macOS, install socat and run the following command
-# socat -4 TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock
+# 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:ffmpeg-4.3.1-20211013"
+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 =
\ No newline at end of file
+#port =
diff --git a/grid/docker-compose.yml b/grid/docker-compose.yml
index 104fd1a..7163cac 100644
--- a/grid/docker-compose.yml
+++ b/grid/docker-compose.yml
@@ -1,13 +1,11 @@
-# To execute this docker-compose yml file use `docker-compose -f docker-compose-v3-dynamic-grid.yml up`
-# Add the `-d` flag at the end for detached execution
-# To stop the execution, hit Ctrl+C, and then `docker-compose -f docker-compose-v3-dynamic-grid.yml down`
-version: "3"
services:
node-docker:
- image: selenium/node-docker:4.0.0-20211013
+ 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:
@@ -16,9 +14,9 @@ services:
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
selenium-hub:
- image: selenium/hub:4.0.0-20211013
+ image: selenium/hub:latest
container_name: selenium-hub
ports:
- "4442:4442"
- "4443:4443"
- - "4444:4444"
\ No newline at end of file
+ - "4444:4444"
diff --git a/pom.xml b/pom.xml
index bc10942..d3faf76 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,35 +5,39 @@
4.0.0com.eliasnogueira
- selenium-java-lean-test-achitecture
- 2.0.0
+ selenium-java-lean-test-architecture
+ 3.8.0
- scm:git@github.com:eliasnogueira/selenium-java-lean-test-achitecture.git
- scm:git@github.com:eliasnogueira/selenium-java-lean-test-achitecture.git
+ 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
- 3.0.0-M5
- 3.9.0
-
- 1.9.6
- 4.1.2
- 7.5
- 3.22.0
- 1.0.2
- 2.17.1
- 5.0.3
+ 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.17.2
- 2.11.2
+ 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.17local
@@ -58,9 +62,9 @@
- com.github.javafaker
- javafaker
- ${javafaker.version}
+ net.datafaker
+ datafaker
+ ${datafaker.version}
@@ -87,28 +91,16 @@
${owner.version}
-
- io.github.bonigarcia
- webdrivermanager
- ${webdrivermanager.version}
-
-
- com.google.guava
- guava
-
-
-
-
io.qameta.allureallure-testng
- ${allure.version}
+ ${allure-testng.version}io.qameta.allureallure-attachments
- ${allure.version}
+ ${allure-attachments.version}
@@ -123,9 +115,28 @@
+
+ org.testcontainers
+ selenium
+ ${testcontainers.selenium.version}
+
+
+ org.apache.commons
+ commons-compress
+
+
+
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j-simple.version}
+ test
+
+
-
+
web-execution
@@ -144,6 +155,7 @@
+
@@ -158,9 +170,6 @@
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
false
-
- target/allure-results
-
@@ -186,12 +195,11 @@
maven-compiler-plugin${maven-compiler-plugin.version}
- ${java-compiler.version}
- ${java-compiler.version}
+ ${java-compiler.version}
-
\ No newline at end of file
+
diff --git a/src/main/java/com/eliasnogueira/config/Configuration.java b/src/main/java/com/eliasnogueira/config/Configuration.java
index 56b4db9..dda4233 100644
--- a/src/main/java/com/eliasnogueira/config/Configuration.java
+++ b/src/main/java/com/eliasnogueira/config/Configuration.java
@@ -32,8 +32,7 @@
@Config.Sources({
"system:properties",
"classpath:general.properties",
- "classpath:local.properties",
- "classpath:grid.properties"})
+ "classpath:selenium-grid.properties"})
public interface Configuration extends Config {
@Key("target")
diff --git a/src/main/java/com/eliasnogueira/config/ConfigurationManager.java b/src/main/java/com/eliasnogueira/config/ConfigurationManager.java
index ba842b4..ce15843 100644
--- a/src/main/java/com/eliasnogueira/config/ConfigurationManager.java
+++ b/src/main/java/com/eliasnogueira/config/ConfigurationManager.java
@@ -35,4 +35,3 @@ public static Configuration configuration() {
return ConfigCache.getOrCreate(Configuration.class);
}
}
-
diff --git a/src/main/java/com/eliasnogueira/exceptions/TargetNotValidException.java b/src/main/java/com/eliasnogueira/data/changeless/BrowserData.java
similarity index 65%
rename from src/main/java/com/eliasnogueira/exceptions/TargetNotValidException.java
rename to src/main/java/com/eliasnogueira/data/changeless/BrowserData.java
index 0dbf149..810154d 100644
--- a/src/main/java/com/eliasnogueira/exceptions/TargetNotValidException.java
+++ b/src/main/java/com/eliasnogueira/data/changeless/BrowserData.java
@@ -1,7 +1,7 @@
/*
* MIT License
*
- * Copyright (c) 2021 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,12 +22,17 @@
* SOFTWARE.
*/
-package com.eliasnogueira.exceptions;
+package com.eliasnogueira.data.changeless;
-public class TargetNotValidException extends IllegalStateException {
+public final class BrowserData {
- public TargetNotValidException(String target) {
- super(String.format("Target %s not supported. Use either local or gird", target));
+ private BrowserData() {
}
+ 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/com/eliasnogueira/data/BookingDataFactory.java b/src/main/java/com/eliasnogueira/data/dynamic/BookingDataFactory.java
similarity index 69%
rename from src/main/java/com/eliasnogueira/data/BookingDataFactory.java
rename to src/main/java/com/eliasnogueira/data/dynamic/BookingDataFactory.java
index 75eb129..c1dc321 100644
--- a/src/main/java/com/eliasnogueira/data/BookingDataFactory.java
+++ b/src/main/java/com/eliasnogueira/data/dynamic/BookingDataFactory.java
@@ -22,36 +22,34 @@
* SOFTWARE.
*/
-package com.eliasnogueira.data;
+package com.eliasnogueira.data.dynamic;
-import com.github.javafaker.Faker;
import com.eliasnogueira.enums.RoomType;
-import java.util.Locale;
-import java.util.Random;
import com.eliasnogueira.model.Booking;
-import com.eliasnogueira.model.BookingBuilder;
+import net.datafaker.Faker;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import java.util.Locale;
+
import static com.eliasnogueira.config.ConfigurationManager.configuration;
-public class BookingDataFactory {
+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() {
- faker = new Faker(new Locale(configuration().faker()));
+ private BookingDataFactory() {
}
- public Booking createBookingData() {
- Booking booking = new BookingBuilder().
+ 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();
@@ -59,15 +57,11 @@ public Booking createBookingData() {
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
index 25ecfb5..0b7f43f 100644
--- a/src/main/java/com/eliasnogueira/driver/BrowserFactory.java
+++ b/src/main/java/com/eliasnogueira/driver/BrowserFactory.java
@@ -25,8 +25,6 @@
package com.eliasnogueira.driver;
import com.eliasnogueira.exceptions.HeadlessNotSupportedException;
-import io.github.bonigarcia.wdm.WebDriverManager;
-import io.github.bonigarcia.wdm.config.DriverManagerType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
@@ -34,80 +32,107 @@
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
-import org.openqa.selenium.ie.InternetExplorerDriver;
-import org.openqa.selenium.ie.InternetExplorerOptions;
-import org.openqa.selenium.opera.OperaDriver;
-import org.openqa.selenium.opera.OperaOptions;
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 createDriver() {
- WebDriverManager.getInstance(DriverManagerType.CHROME).setup();
-
+ 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() {
- ChromeOptions chromeOptions = new ChromeOptions();
+ var chromeOptions = new ChromeOptions();
chromeOptions.addArguments(START_MAXIMIZED);
- chromeOptions.addArguments("--disable-infobars");
- chromeOptions.addArguments("--disable-notifications");
- chromeOptions.setHeadless(configuration().headless());
+ 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 createDriver() {
- WebDriverManager.getInstance(DriverManagerType.FIREFOX).setup();
-
+ 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() {
- FirefoxOptions firefoxOptions = new FirefoxOptions();
+ var firefoxOptions = new FirefoxOptions();
firefoxOptions.addArguments(START_MAXIMIZED);
- firefoxOptions.setHeadless(configuration().headless());
+
+ if (configuration().headless()) firefoxOptions.addArguments(GENERIC_HEADLESS);
return firefoxOptions;
}
}, EDGE {
@Override
- public WebDriver createDriver() {
- WebDriverManager.getInstance(DriverManagerType.EDGE).setup();
-
+ 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() {
- EdgeOptions edgeOptions = new EdgeOptions();
+ var edgeOptions = new EdgeOptions();
edgeOptions.addArguments(START_MAXIMIZED);
- edgeOptions.setHeadless(configuration().headless());
+
+ if (configuration().headless()) edgeOptions.addArguments(GENERIC_HEADLESS);
return edgeOptions;
}
}, SAFARI {
@Override
- public WebDriver createDriver() {
- WebDriverManager.getInstance(DriverManagerType.SAFARI).setup();
-
+ public WebDriver createLocalDriver() {
return new SafariDriver(getOptions());
}
+ public WebDriver createTestContainerDriver() {
+ throw new IllegalArgumentException("Browser Safari not supported on TestContainers yet");
+ }
+
@Override
public SafariOptions getOptions() {
- SafariOptions safariOptions = new SafariOptions();
+ var safariOptions = new SafariOptions();
safariOptions.setAutomaticInspection(false);
if (TRUE.equals(configuration().headless()))
@@ -115,51 +140,24 @@ public SafariOptions getOptions() {
return safariOptions;
}
- }, OPERA {
- @Override
- public WebDriver createDriver() {
- WebDriverManager.getInstance(DriverManagerType.OPERA).setup();
-
- return new OperaDriver(getOptions());
- }
-
- @Override
- public OperaOptions getOptions() {
- OperaOptions operaOptions = new OperaOptions();
- operaOptions.addArguments(START_MAXIMIZED);
- operaOptions.addArguments("--disable-infobars");
- operaOptions.addArguments("--disable-notifications");
-
- if (TRUE.equals(configuration().headless()))
- throw new HeadlessNotSupportedException(operaOptions.getBrowserName());
-
- return operaOptions;
- }
- }, IE {
- @Override
- public WebDriver createDriver() {
- WebDriverManager.getInstance(DriverManagerType.IEXPLORER).setup();
-
- return new InternetExplorerDriver(getOptions());
- }
-
- @Override
- public InternetExplorerOptions getOptions() {
- InternetExplorerOptions internetExplorerOptions = new InternetExplorerOptions();
- internetExplorerOptions.ignoreZoomSettings();
- internetExplorerOptions.takeFullPageScreenshot();
- internetExplorerOptions.introduceFlakinessByIgnoringSecurityDomains();
-
- if (TRUE.equals(configuration().headless()))
- throw new HeadlessNotSupportedException(internetExplorerOptions.getBrowserName());
-
- return internetExplorerOptions;
- }
};
- private static final String START_MAXIMIZED = "--start-maximized";
-
- public abstract WebDriver createDriver();
+ /**
+ * 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/com/eliasnogueira/driver/DriverManager.java b/src/main/java/com/eliasnogueira/driver/DriverManager.java
index f28102e..680076f 100644
--- a/src/main/java/com/eliasnogueira/driver/DriverManager.java
+++ b/src/main/java/com/eliasnogueira/driver/DriverManager.java
@@ -24,7 +24,6 @@
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.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
index 36c598c..3919b61 100644
--- a/src/main/java/com/eliasnogueira/driver/TargetFactory.java
+++ b/src/main/java/com/eliasnogueira/driver/TargetFactory.java
@@ -24,55 +24,47 @@
package com.eliasnogueira.driver;
-import com.eliasnogueira.exceptions.TargetNotValidException;
+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.URL;
+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.valueOf(configuration().target().toUpperCase());
- WebDriver webdriver;
+ Target target = Target.get(configuration().target().toUpperCase());
- switch (target) {
- case LOCAL:
- webdriver = BrowserFactory.valueOf(browser.toUpperCase()).createDriver();
- break;
- case REMOTE:
- webdriver = createRemoteInstance(BrowserFactory.valueOf(browser.toUpperCase()).getOptions());
- break;
- default:
- throw new TargetNotValidException(target.toString());
- }
- return webdriver;
+ 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 = String.format("http://%s:%s", configuration().gridUrl(), configuration().gridPort());
+ String gridURL = format("http://%s:%s", configuration().gridUrl(), configuration().gridPort());
- remoteWebDriver = new RemoteWebDriver(new URL(gridURL), capability);
+ 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(String.format("Browser: %s", capability.getBrowserName()), e);
+ logger.error("Browser: {}", capability.getBrowserName(), e);
} catch (IllegalArgumentException e) {
- logger.error(String.format("Browser %s is not valid or recognized", capability.getBrowserName()), e);
+ logger.error("Browser {} is not valid or recognized", capability.getBrowserName(), e);
}
return remoteWebDriver;
}
-
- enum Target {
- LOCAL, REMOTE
- }
}
diff --git a/src/main/java/com/eliasnogueira/enums/RoomType.java b/src/main/java/com/eliasnogueira/enums/RoomType.java
index 80432cc..d7b0980 100644
--- a/src/main/java/com/eliasnogueira/enums/RoomType.java
+++ b/src/main/java/com/eliasnogueira/enums/RoomType.java
@@ -24,9 +24,9 @@
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/com/eliasnogueira/enums/Target.java b/src/main/java/com/eliasnogueira/enums/Target.java
new file mode 100644
index 0000000..d9dfa4e
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/enums/Target.java
@@ -0,0 +1,58 @@
+/*
+ * MIT License
+ *
+ * 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
+ * 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.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("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/com/eliasnogueira/model/Booking.java b/src/main/java/com/eliasnogueira/model/Booking.java
index 812a591..5348d03 100644
--- a/src/main/java/com/eliasnogueira/model/Booking.java
+++ b/src/main/java/com/eliasnogueira/model/Booking.java
@@ -26,90 +26,56 @@
import com.eliasnogueira.enums.RoomType;
-public class Booking {
-
- private String email;
- private String country;
- private String password;
- private String dailyBudget;
- private Boolean newsletter;
- private RoomType roomType;
- private String roomDescription;
-
- public Booking(String email, String country, String password, String dailyBudget, Boolean newsletter,
- RoomType roomType, String roomDescription) {
- this.email = email;
- this.country = country;
- this.password = password;
- this.dailyBudget = dailyBudget;
- this.newsletter = newsletter;
- this.roomType = roomType;
- this.roomDescription = roomDescription;
- }
-
- public Booking() {
- }
-
- public String getEmail() {
- return this.email;
- }
-
- public String getCountry() {
- return this.country;
- }
-
- public String getPassword() {
- return this.password;
- }
-
- public String getDailyBudget() {
- return this.dailyBudget;
- }
-
- public Boolean getNewsletter() {
- return this.newsletter;
- }
-
- public RoomType getRoomType() {
- return this.roomType;
- }
-
- public String getRoomDescription() {
- return this.roomDescription;
+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);
+ }
}
-
- public void setEmail(String email) {
- this.email = email;
- }
-
- public void setCountry(String country) {
- this.country = country;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- public void setDailyBudget(String dailyBudget) {
- this.dailyBudget = dailyBudget;
- }
-
- public void setNewsletter(Boolean newsletter) {
- this.newsletter = newsletter;
- }
-
- public void setRoomType(RoomType roomType) {
- this.roomType = roomType;
- }
-
- public void setRoomDescription(String roomDescription) {
- this.roomDescription = roomDescription;
- }
-
- public String toString() {
- return "Booking(email=" + this.getEmail() + ", country=" + this.getCountry() + ", dailyBudget=" +
- this.getDailyBudget() + ", newsletter=" + this.getNewsletter() + ", roomType="
- + this.getRoomType() + ", roomDescription=" + this.getRoomDescription() + ")";
- }
-
}
diff --git a/src/main/java/com/eliasnogueira/model/BookingBuilder.java b/src/main/java/com/eliasnogueira/model/BookingBuilder.java
deleted file mode 100644
index 82da658..0000000
--- a/src/main/java/com/eliasnogueira/model/BookingBuilder.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 com.eliasnogueira.model;
-
-import com.eliasnogueira.enums.RoomType;
-
-public 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);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/eliasnogueira/page/booking/RoomPage.java b/src/main/java/com/eliasnogueira/page/booking/RoomPage.java
index 05763b0..5b14b06 100644
--- a/src/main/java/com/eliasnogueira/page/booking/RoomPage.java
+++ b/src/main/java/com/eliasnogueira/page/booking/RoomPage.java
@@ -25,7 +25,6 @@
package com.eliasnogueira.page.booking;
import com.eliasnogueira.driver.DriverManager;
-import com.eliasnogueira.enums.RoomType;
import com.eliasnogueira.page.booking.common.NavigationPage;
import io.qameta.allure.Step;
import org.openqa.selenium.By;
@@ -33,7 +32,7 @@
public class RoomPage extends NavigationPage {
@Step
- public void selectRoomType(RoomType room) {
+ public void selectRoomType(String room) {
DriverManager.getDriver().findElement(By.xpath("//h6[text()='" + room + "']")).click();
}
}
diff --git a/src/main/java/com/eliasnogueira/page/booking/common/NavigationPage.java b/src/main/java/com/eliasnogueira/page/booking/common/NavigationPage.java
index 46be648..ceafb1d 100644
--- a/src/main/java/com/eliasnogueira/page/booking/common/NavigationPage.java
+++ b/src/main/java/com/eliasnogueira/page/booking/common/NavigationPage.java
@@ -45,11 +45,6 @@ public void next() {
next.click();
}
- @Step
- 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
index 8dd5891..2180e2e 100644
--- a/src/main/java/com/eliasnogueira/report/AllureManager.java
+++ b/src/main/java/com/eliasnogueira/report/AllureManager.java
@@ -24,14 +24,14 @@
package com.eliasnogueira.report;
-import com.eliasnogueira.driver.DriverManager;
+import com.eliasnogueira.enums.Target;
import com.github.automatedowl.tools.AllureEnvironmentWriter;
import com.google.common.collect.ImmutableMap;
-import io.qameta.allure.Attachment;
-import org.openqa.selenium.TakesScreenshot;
+
+import java.util.HashMap;
+import java.util.Map;
import static com.eliasnogueira.config.ConfigurationManager.configuration;
-import static org.openqa.selenium.OutputType.BYTES;
public class AllureManager {
@@ -39,27 +39,20 @@ private AllureManager() {
}
public static void setAllureEnvironmentInformation() {
- AllureEnvironmentWriter.allureEnvironmentWriter(
- ImmutableMap.builder().
- put("Test URL", configuration().url()).
- put("Target execution", configuration().target()).
- put("Global timeout", String.valueOf(configuration().timeout())).
- put("Headless mode", String.valueOf(configuration().headless())).
- put("Faker locale", configuration().faker()).
- put("Local browser", configuration().browser()).
- put("Grid URL", configuration().gridUrl()).
- put("Grid port", configuration().gridPort()).
- build());
- }
-
- @Attachment(value = "Failed test screenshot", type = "image/png")
- public static byte[] takeScreenshotToAttachOnAllureReport() {
- return ((TakesScreenshot) DriverManager.getDriver()).getScreenshotAs(BYTES);
+ 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));
}
-
- @Attachment(value = "Browser information", type = "text/plain")
- public static String addBrowserInformationOnAllureReport() {
- return DriverManager.getInfo();
- }
-
}
diff --git a/src/main/java/com/eliasnogueira/report/AllureTestLifecycleListener.java b/src/main/java/com/eliasnogueira/report/AllureTestLifecycleListener.java
new file mode 100644
index 0000000..4d09ef2
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/report/AllureTestLifecycleListener.java
@@ -0,0 +1,56 @@
+/*
+ * MIT License
+ *
+ * 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
+ * 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.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;
+
+import static io.qameta.allure.model.Status.BROKEN;
+import static io.qameta.allure.model.Status.FAILED;
+
+/*
+ * Approach implemented using the https://github.com/biczomate/allure-testng7.5-attachment-example as reference
+ */
+public class AllureTestLifecycleListener implements TestLifecycleListener {
+
+ public AllureTestLifecycleListener() {
+ }
+
+ @Attachment(value = "Page Screenshot", type = "image/png")
+ public byte[] saveScreenshot(WebDriver driver) {
+ return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
+ }
+
+ @Override
+ public void beforeTestStop(TestResult result) {
+ if (FAILED == result.getStatus() || BROKEN == result.getStatus()) {
+ saveScreenshot(DriverManager.getDriver());
+ }
+ }
+}
diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties
index 6b21586..d41ecde 100644
--- a/src/main/resources/log4j2.properties
+++ b/src/main/resources/log4j2.properties
@@ -16,4 +16,4 @@ appender.file.layout.pattern=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} -
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/test/java/com/eliasnogueira/BaseWeb.java b/src/test/java/com/eliasnogueira/BaseWeb.java
index 678bc58..cd5c894 100644
--- a/src/test/java/com/eliasnogueira/BaseWeb.java
+++ b/src/test/java/com/eliasnogueira/BaseWeb.java
@@ -31,13 +31,11 @@
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 static com.eliasnogueira.config.ConfigurationManager.configuration;
-@Listeners({TestListener.class})
public abstract class BaseWeb {
@BeforeSuite
diff --git a/src/test/java/com/eliasnogueira/TestListener.java b/src/test/java/com/eliasnogueira/TestListener.java
deleted file mode 100644
index a0fa483..0000000
--- a/src/test/java/com/eliasnogueira/TestListener.java
+++ /dev/null
@@ -1,79 +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 com.eliasnogueira;
-
-import com.eliasnogueira.report.AllureManager;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.testng.ITestContext;
-import org.testng.ITestListener;
-import org.testng.ITestResult;
-
-public class TestListener implements ITestListener {
-
- private static final Logger logger = LogManager.getLogger(TestListener.class);
-
- @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) {
- logger.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) {
- logger.error(iTestResult.getTestClass().getName());
- logger.error(iTestResult.getThrowable());
-
- AllureManager.takeScreenshotToAttachOnAllureReport();
- }
-}
diff --git a/src/test/java/com/eliasnogueira/test/BookRoomWebTest.java b/src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
index ee2dab1..7471352 100644
--- a/src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
+++ b/src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
@@ -24,8 +24,7 @@
package com.eliasnogueira.test;
import com.eliasnogueira.BaseWeb;
-import com.eliasnogueira.data.BookingDataFactory;
-import com.eliasnogueira.model.Booking;
+import com.eliasnogueira.data.dynamic.BookingDataFactory;
import com.eliasnogueira.page.booking.AccountPage;
import com.eliasnogueira.page.booking.DetailPage;
import com.eliasnogueira.page.booking.RoomPage;
@@ -37,22 +36,22 @@ 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())
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
index 947dd63..15bb713 100644
--- a/src/test/resources/general.properties
+++ b/src/test/resources/general.properties
@@ -1,14 +1,17 @@
-# target execution: local or remote
+# target execution: local, selenium-grid or testcontainers
target = local
+# browser to use for local and testcontainers execution
+browser = chrome
+
# initial URL
-url.base = http://eliasnogueira.com/external/selenium-java-architecture/
+url.base = https://eliasnogueira.com/external/selenium-java-architecture/
# global test timeout
timeout = 3
-# javafaker locale
-faker.locale = pt-BR
+# datafaker locale
+faker.locale = en-US
# headless mode only for chrome or firefox and local execution
-headless = false
\ No newline at end of file
+headless = false
diff --git a/src/test/resources/local.properties b/src/test/resources/local.properties
deleted file mode 100644
index 07d1868..0000000
--- a/src/test/resources/local.properties
+++ /dev/null
@@ -1 +0,0 @@
-browser = chrome
\ No newline at end of file
diff --git a/src/test/resources/grid.properties b/src/test/resources/selenium-grid.properties
similarity index 70%
rename from src/test/resources/grid.properties
rename to src/test/resources/selenium-grid.properties
index eb20844..90d2540 100644
--- a/src/test/resources/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
index fa5f07a..089ee73 100644
--- a/src/test/resources/suites/local.xml
+++ b/src/test/resources/suites/local.xml
@@ -1,11 +1,11 @@
-
+
-
+
-
\ No newline at end of file
+
diff --git a/src/test/resources/suites/parallel.xml b/src/test/resources/suites/selenium-grid.xml
similarity index 98%
rename from src/test/resources/suites/parallel.xml
rename to src/test/resources/suites/selenium-grid.xml
index cb1bd32..04d9024 100644
--- a/src/test/resources/suites/parallel.xml
+++ b/src/test/resources/suites/selenium-grid.xml
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+