diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..9f39b7b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,23 @@
+---
+name: Bug report
+about: Use this to log an issue/bug/improvement
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the issue**
+A clear and concise description of the problem
+
+**Steps**
+Steps to reproduce the unexpected behaviour:
+1. Go to '...'
+2. Click on '....'
+3. See the error
+
+**Expected behaviour**
+A clear and concise description of what you expected to happen.
+
+**Additional information**
+Any other relevant information
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..bbcbbe7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..8043827
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+- package-ecosystem: maven
+ directory: "/"
+ schedule:
+ interval: daily
+ time: '04:00'
+ open-pull-requests-limit: 10
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..ee2c216
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,25 @@
+**IMPORTANT: Please do not create a Pull Request without creating an issue first.**
+
+*Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request.*
+
+Please provide enough information so that others can review your pull request:
+
+
+
+Explain the **details** for making this change. What existing problem does the pull request solve?
+
+
+
+**Test plan (required)**
+
+Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI.
+
+
+
+**Code formatting**
+
+
+
+**Closing issues**
+
+Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if such).
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/.gitignore b/.gitignore
index 5381bfd..6cf5705 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.settings/
+grid/assets
# Intellij
.idea/
@@ -6,4 +7,7 @@
# Maven
logs/
-target/
\ No newline at end of file
+target/
+
+# Allure
+.allure
\ No newline at end of file
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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..879dea0
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at elias.nogueira@gmail.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..21a0ef8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+# 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-lean-test-achitecture/issues).
+
+Select _Bug report_ if tou want log an issue or _Feature request_ if you want to see something new.
+
+Do not forget to add a _label_ on the issue or feature.
+
+## I have enough knowledge in development to colaborate
+Excellent! Thank you to help me out!
+
+You're going to need a few things first:
+* JDK 17+
+* [Configure your IDE](https://projectlombok.org/setup/overview) in order to support Lombok.
+
+## Send a pull request
+Please add an explanation about the pull request.
+If there is or you created an issue/feature, please add the link.
+
+Pull requests without explanations will be rejected, so please add:
+* the reason about you're sending the pull request
+* the benefit that your pull request will bring to the application
diff --git a/README.MD b/README.MD
index 8c9160e..d90fecc 100644
--- a/README.MD
+++ b/README.MD
@@ -1,115 +1,352 @@
-The purpose of this project is to have a basic project with minimum viable testing architecture to automated web tests using Selenium WebDriver and Java as the programming language.
+# 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 8 as the programming language
-* TestNG as the UnitTest framework to support the test creation
+* [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
+* [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
+* [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:
-* Use of Page Objects patters
-* Parallel execution
-* BaseTest
-* TestListner
-* Logging
-* Configuration through a properties file
-* yaml templates to create a Selenium Grid infrastructure on OpenShift
+* [Page Objects pattern](#page-objects-pattern)
+* [Execution types](#execution-types)
+* [BaseTest](#basetest)
+* [TestListener](#testlistener)
+* [Logging](#logging)
+* [Configuration files](#configuration-files)
+* [Parallel execution](#parallel-execution)
+* [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.
-### Use of Page Objects patters
+### Page Objects pattern
-### Parallel execution
+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.
+Also, it sets the timeout from the `timeout` property value located on `general.properties` file.
+
+All the Page Object classes should extend the `AbstractPageObject`.
+It also tries to remove the `driver` object from the Page Object class as much as possible.
+
+> **Important information**
+>
+> 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 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 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
+
+##### 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
+
+##### 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 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.
+
+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 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
-### TestListner
+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 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 in the test class.
+
+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 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
-### Configuration through a properties file
+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.
+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 project use the a property file to configure basic mutable items, like:
+### Parallel execution
-* base.url: the main app URL
-* grid.url: target grid url
-* grid.port: target grid port
-* log.directory = the name of the log folder
-* log.dateformat = the data format for the log filename
+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:
-The property file is inside _conf_ folder. This folder has three sub-folders:
+```xml
-* dev:
-* test:
-* prod:
+
+```
-You can set different values for the properties keys based on your environment.
-When you run a test without inform the parameter `env`, the property file used will be _conf/dev/config.properties_.
+You can define any parallel strategy.
-To change the environment, just use `-Denv=env_name` where _env_name_ is the name of the folder inside _conf_ folder.
+It can be an excellent combination together with the grid strategy.
-E.g: running all tests using test configuration values
+#### Execution with Docker Selenium Distributed
-``` bash
-mvn test -Denv=test
+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
+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, 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-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.
+
+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.
-
-#### Jenkins pipeline
+The two files of the pipeline as a code are inside `pipeline_as_code` folder.
-You can you _Jenkisfile_ on this directory and refer _pipeline_as_code/Jenkinsfile_ on XXXXXX item on Jenkins.
-This pipeline works on
+* 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/assets/example_filed_test_with_report.gif b/assets/example_filed_test_with_report.gif
new file mode 100644
index 0000000..3afab95
Binary files /dev/null and b/assets/example_filed_test_with_report.gif differ
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 f988931..d3faf76 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,35 +5,41 @@
4.0.0com.eliasnogueira
- selenium-java-bootstrap
- 1.1
+ selenium-java-lean-test-architecture
+ 3.8.0
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.8.1
-
- 8
- 8
-
-
-
-
+
+ scm:git@github.com:eliasnogueira/selenium-java-lean-test-architecture.git
+ scm:git@github.com:eliasnogueira/selenium-java-lean-test-architecture.git
+
+
- 3.141.59
- 7.1.0
- 3.14.0
- 4.0.9
- 1.0.3
- 1.0.1
- 2.13.0
- 1.18.10
- 3.7.1
-
- multi_browser
+ 24
+ UTF-8
+ UTF-8
+ 3.5.3
+ 3.14.0
+
+ 1.9.24
+ 4.35.0
+ 7.11.0
+ 3.27.4
+ 2.4.4
+ 2.25.1
+ 1.0.12
+ 2.33.0
+ 2.29.1
+ 2.29.1
+ 2.15.2
+ 1.0.0
+
+ https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline
+
+ 1.21.3
+ 2.0.17
+
+ local
@@ -43,12 +49,6 @@
${selenium.version}
-
- org.seleniumhq.selenium
- selenium-server
- ${selenium.version}
-
-
org.testngtestng
@@ -62,57 +62,81 @@
- com.aventstack
- extentreports
- ${extentreports.version}
+ net.datafaker
+ datafaker
+ ${datafaker.version}
- com.github.javafaker
- javafaker
- ${javafaker.version}
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}org.apache.logging.log4j
- log4j-api
+ log4j-core${log4j.version}org.apache.logging.log4j
- log4j-core
+ log4j-slf4j-impl${log4j.version}
- org.projectlombok
- lombok
- ${lombok.version}
- provided
+ org.aeonbits.owner
+ owner
+ ${owner.version}
- com.aventstack
- extentreports-testng-adapter
- ${extentreports-adapter.version}
+ io.qameta.allure
+ allure-testng
+ ${allure-testng.version}
- org.aeonbits.owner
- owner
- 1.0.10
+ io.qameta.allure
+ allure-attachments
+ ${allure-attachments.version}
- io.github.bonigarcia
- webdrivermanager
- ${webdrivermanager.version}
+ com.github.automatedowl
+ allure-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
@@ -121,7 +145,7 @@
org.apache.maven.pluginsmaven-surefire-plugin
- 2.12.4
+ ${maven-surefire-plugin.version}src/test/resources/suites/${suite}.xml
@@ -131,6 +155,51 @@
+
-
\ No newline at end of file
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+
+ -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
+
+ false
+
+
+
+ org.aspectj
+ aspectjweaver
+ ${aspectj.version}
+
+
+
+
+ io.qameta.allure
+ allure-maven
+ ${allure-maven.version}
+
+ ${allure.version}
+
+ ${allure.cmd.download.url}/${allure.version}/allure-commandline-${allure.version}.zip
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ ${java-compiler.version}
+
+
+
+
+
+
+
diff --git a/src/main/java/com/eliasnogueira/config/Configuration.java b/src/main/java/com/eliasnogueira/config/Configuration.java
new file mode 100644
index 0000000..dda4233
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/config/Configuration.java
@@ -0,0 +1,61 @@
+/*
+ * 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.config;
+
+import org.aeonbits.owner.Config;
+import org.aeonbits.owner.Config.LoadPolicy;
+import org.aeonbits.owner.Config.LoadType;
+
+@LoadPolicy(LoadType.MERGE)
+@Config.Sources({
+ "system:properties",
+ "classpath:general.properties",
+ "classpath:selenium-grid.properties"})
+public interface Configuration extends Config {
+
+ @Key("target")
+ String target();
+
+ @Key("browser")
+ String browser();
+
+ @Key("headless")
+ Boolean headless();
+
+ @Key("url.base")
+ String url();
+
+ @Key("timeout")
+ int timeout();
+
+ @Key("grid.url")
+ String gridUrl();
+
+ @Key("grid.port")
+ String gridPort();
+
+ @Key("faker.locale")
+ String faker();
+}
diff --git a/src/main/java/driver/DriverFactory.java b/src/main/java/com/eliasnogueira/config/ConfigurationManager.java
similarity index 56%
rename from src/main/java/driver/DriverFactory.java
rename to src/main/java/com/eliasnogueira/config/ConfigurationManager.java
index 6b3d661..ce15843 100644
--- a/src/main/java/driver/DriverFactory.java
+++ b/src/main/java/com/eliasnogueira/config/ConfigurationManager.java
@@ -22,40 +22,16 @@
* SOFTWARE.
*/
-package driver;
+package com.eliasnogueira.config;
-import config.Configuration;
-import driver.local.LocalDriver;
-import driver.remote.RemoteDriver;
-import lombok.extern.log4j.Log4j2;
import org.aeonbits.owner.ConfigCache;
-import org.openqa.selenium.WebDriver;
-@Log4j2
-public class DriverFactory {
+public class ConfigurationManager {
-
- public static WebDriver createInstance(String browser) {
- Configuration configuration = ConfigCache.getOrCreate(Configuration.class);
- Target target = Target.valueOf(configuration.target().toUpperCase());
- WebDriver webdriver;
-
- switch (target) {
-
- case LOCAL:
- webdriver = new LocalDriver().createInstance(browser);
- break;
- case GRID:
- webdriver = new RemoteDriver().createInstance(browser);
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + target);
- }
-
- return webdriver;
+ private ConfigurationManager() {
}
- enum Target {
- LOCAL, GRID
+ public static Configuration configuration() {
+ return ConfigCache.getOrCreate(Configuration.class);
}
}
diff --git a/src/main/java/com/eliasnogueira/data/changeless/BrowserData.java b/src/main/java/com/eliasnogueira/data/changeless/BrowserData.java
new file mode 100644
index 0000000..810154d
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/data/changeless/BrowserData.java
@@ -0,0 +1,38 @@
+/*
+ * 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.data.changeless;
+
+public final class BrowserData {
+
+ 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/dynamic/BookingDataFactory.java b/src/main/java/com/eliasnogueira/data/dynamic/BookingDataFactory.java
new file mode 100644
index 0000000..c1dc321
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/data/dynamic/BookingDataFactory.java
@@ -0,0 +1,67 @@
+/*
+ * 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.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 java.util.Locale;
+
+import static com.eliasnogueira.config.ConfigurationManager.configuration;
+
+public final class BookingDataFactory {
+
+ private static final Faker faker = new Faker(new Locale.Builder().setLanguageTag(configuration().faker()).build());
+ private static final Logger logger = LogManager.getLogger(BookingDataFactory.class);
+
+ private BookingDataFactory() {
+ }
+
+ 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(faker.options().option(RoomType.class)).
+ roomDescription(faker.lorem().paragraph()).
+ build();
+
+ logger.info(booking);
+ return booking;
+ }
+
+ private static String returnRandomCountry() {
+ return faker.options().option("Belgium", "Brazil", "Netherlands");
+ }
+
+ 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 84%
rename from src/main/java/driver/DriverManager.java
rename to src/main/java/com/eliasnogueira/driver/DriverManager.java
index cc10ae6..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;
@@ -32,10 +31,10 @@ public class DriverManager {
private static final ThreadLocal driver = new ThreadLocal<>();
- private DriverManager() {}
+ private DriverManager() {}
public static WebDriver getDriver() {
- return driver.get();
+ return driver.get();
}
public static void setDriver(WebDriver driver) {
@@ -44,13 +43,15 @@ public static void setDriver(WebDriver driver) {
public static void quit() {
DriverManager.driver.get().quit();
+ driver.remove();
}
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/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/exceptions/HeadlessNotSupportedException.java b/src/main/java/com/eliasnogueira/exceptions/HeadlessNotSupportedException.java
new file mode 100644
index 0000000..67e6a0e
--- /dev/null
+++ b/src/main/java/com/eliasnogueira/exceptions/HeadlessNotSupportedException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.exceptions;
+
+public class HeadlessNotSupportedException extends IllegalStateException {
+
+ 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 6338d65..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 driver.DriverManager;
-import org.aeonbits.owner.ConfigCache;
-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 = ConfigCache.getOrCreate(Configuration.class);
- 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 91%
rename from src/main/java/page/objects/booking/AccountPage.java
rename to src/main/java/com/eliasnogueira/page/booking/AccountPage.java
index a540cb4..aae5331 100644
--- a/src/main/java/page/objects/booking/AccountPage.java
+++ b/src/main/java/com/eliasnogueira/page/booking/AccountPage.java
@@ -22,13 +22,13 @@
* SOFTWARE.
*/
-package page.objects.booking;
+package com.eliasnogueira.page.booking;
-import driver.DriverManager;
+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 {
@@ -47,26 +47,27 @@ public class AccountPage extends NavigationPage {
@FindBy(css = ".check")
private WebElement newsletter;
- public AccountPage() {
- DriverManager.getDriver().switchTo().frame("result");
- }
-
+ @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 85%
rename from src/main/java/page/objects/booking/DetailPage.java
rename to src/main/java/com/eliasnogueira/page/booking/DetailPage.java
index 8831451..2ba247f 100644
--- a/src/main/java/page/objects/booking/DetailPage.java
+++ b/src/main/java/com/eliasnogueira/page/booking/DetailPage.java
@@ -22,16 +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 org.openqa.selenium.support.ui.ExpectedCondition;
-import org.openqa.selenium.support.ui.ExpectedConditions;
-import org.openqa.selenium.support.ui.WebDriverWait;
-import page.objects.booking.common.NavigationPage;
public class DetailPage extends NavigationPage {
@@ -41,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/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/java/config/Configuration.java b/src/main/java/config/Configuration.java
deleted file mode 100644
index bf627cd..0000000
--- a/src/main/java/config/Configuration.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package config;
-
-import org.aeonbits.owner.Config;
-
-@Config.Sources({"classpath:conf/${env}.properties"})
-public interface Configuration extends Config {
-
- String target();
-
- @Key("url.base")
- String url();
-
- String timeout();
-
- @Key("grid.url")
- String gridUrl();
-
- @Key("grid.port")
- String gridPort();
-
- @Key("faker.locale")
- String faker();
-}
diff --git a/src/main/java/data/BookingDataFactory.java b/src/main/java/data/BookingDataFactory.java
deleted file mode 100644
index a608134..0000000
--- a/src/main/java/data/BookingDataFactory.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package data;
-
-import com.aventstack.extentreports.service.ExtentTestManager;
-import com.github.javafaker.Faker;
-import config.Configuration;
-import enums.RoomType;
-import java.util.Locale;
-import java.util.Random;
-import lombok.extern.log4j.Log4j2;
-import model.Booking;
-import org.aeonbits.owner.ConfigCache;
-
-@Log4j2
-public class BookingDataFactory {
-
- private final Faker faker;
-
- public BookingDataFactory() {
- Configuration configuration = ConfigCache.getOrCreate(Configuration.class);
- faker = new Faker(new Locale(configuration.faker()));
- }
-
- public Booking createBookingData() {
- Booking booking = Booking.builder().
- email(faker.internet().emailAddress()).
- country(returnRandomCountry()).
- password(faker.internet().password()).
- dailyBudget(returnDailyBudget()).
- newsletter(faker.bool().bool()).
- roomType(RoomType.getRandom()).
- roomDescription(faker.lorem().paragraph()).
- build();
-
- log.info(booking);
- ExtentTestManager.getTest().info(booking.toString());
- 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 String returnRandomItemOnArray(String[] array) {
- return array[(new Random().nextInt(array.length))];
- }
-}
diff --git a/src/main/java/driver/IDriver.java b/src/main/java/driver/IDriver.java
deleted file mode 100644
index a91f2e4..0000000
--- a/src/main/java/driver/IDriver.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package driver;
-
-import org.openqa.selenium.WebDriver;
-
-public interface IDriver {
-
- WebDriver createInstance(String browser);
-}
diff --git a/src/main/java/driver/local/LocalDriver.java b/src/main/java/driver/local/LocalDriver.java
deleted file mode 100644
index aab4054..0000000
--- a/src/main/java/driver/local/LocalDriver.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package driver.local;
-
-import driver.IDriver;
-import io.github.bonigarcia.wdm.DriverManagerType;
-import io.github.bonigarcia.wdm.WebDriverManager;
-import lombok.extern.log4j.Log4j2;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.chrome.ChromeDriver;
-import org.openqa.selenium.edge.EdgeDriver;
-import org.openqa.selenium.firefox.FirefoxDriver;
-import org.openqa.selenium.ie.InternetExplorerDriver;
-import org.openqa.selenium.opera.OperaDriver;
-
-@Log4j2
-public class LocalDriver implements IDriver {
-
- @Override
- public WebDriver createInstance(String browser) {
- WebDriver driver = null;
-
- try {
- DriverManagerType driverManagerType = DriverManagerType.valueOf(browser.toUpperCase());
- Class extends WebDriver> driverClass = driverResolver(driverManagerType);
- WebDriverManager.getInstance(driverManagerType).setup();
- driver = driverClass.newInstance();
- } catch (InstantiationException | IllegalAccessException e) {
- log.error("Problem during instantiation the driver", e);
- }
- return driver;
- }
-
- private Class driverResolver(DriverManagerType driverManagerType) {
- Class clazz;
-
- switch (driverManagerType) {
- case CHROME:
- clazz = ChromeDriver.class;
- break;
- case FIREFOX:
- clazz = FirefoxDriver.class;
- break;
- case OPERA:
- clazz = OperaDriver.class;
- break;
- case EDGE:
- clazz = EdgeDriver.class;
- break;
- case PHANTOMJS:
- case SELENIUM_SERVER_STANDALONE:
- throw new IllegalStateException("Not supported: " + driverManagerType);
- case IEXPLORER:
- clazz = InternetExplorerDriver.class;
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + driverManagerType);
- }
- return clazz;
- }
-}
diff --git a/src/main/java/driver/remote/RemoteDriver.java b/src/main/java/driver/remote/RemoteDriver.java
deleted file mode 100644
index 4acaeb3..0000000
--- a/src/main/java/driver/remote/RemoteDriver.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package driver.remote;
-
-import config.Configuration;
-import driver.IDriver;
-import io.github.bonigarcia.wdm.DriverManagerType;
-import lombok.extern.log4j.Log4j2;
-import org.aeonbits.owner.ConfigCache;
-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;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-@Log4j2
-public class RemoteDriver implements IDriver {
-
- @Override
- public WebDriver createInstance(String browser) {
- RemoteWebDriver remoteWebDriver = null;
- Configuration configuration = ConfigCache.getOrCreate(Configuration.class);
- 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/model/Booking.java b/src/main/java/model/Booking.java
deleted file mode 100644
index d894b91..0000000
--- a/src/main/java/model/Booking.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package model;
-
-import enums.RoomType;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.ToString;
-
-@Data
-@Builder
-@AllArgsConstructor
-@NoArgsConstructor
-@ToString
-public class Booking {
-
- String email;
- String country;
- @ToString.Exclude String password;
- String dailyBudget;
- Boolean newsletter;
- RoomType roomType;
- String roomDescription;
-}
diff --git a/src/main/java/test/TestListener.java b/src/main/java/test/TestListener.java
deleted file mode 100644
index 344afb4..0000000
--- a/src/main/java/test/TestListener.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 test;
-
-import com.aventstack.extentreports.service.ExtentTestManager;
-import driver.DriverManager;
-import lombok.extern.log4j.Log4j2;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-import org.testng.ITestContext;
-import org.testng.ITestListener;
-import org.testng.ITestResult;
-
-@Log4j2
-public class TestListener implements ITestListener {
-
- @Override
- public void onTestStart(ITestResult result) {
- ExtentTestManager.getTest().info(DriverManager.getInfo());
- }
-
- @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());
-
- String screenshot = ((TakesScreenshot) DriverManager.getDriver()).getScreenshotAs(OutputType.BASE64);
- ExtentTestManager.getTest().addScreenCaptureFromBase64String(screenshot);
- }
-
-}
diff --git a/src/main/resources/conf/dev.properties b/src/main/resources/conf/dev.properties
deleted file mode 100644
index b41ede6..0000000
--- a/src/main/resources/conf/dev.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# target execution: local or grid
-target = local
-
-# initial URL
-url.base = https://codepen.io/eliasnogueira/full/PooMNpX
-
-# global test timeout
-timeout = 3
-
-# grid url and port
-grid.url = localhost
-grid.port = 4444
-
-# javafaker locale
-faker.locale = pt-BR
diff --git a/src/main/resources/conf/prod.properties b/src/main/resources/conf/prod.properties
deleted file mode 100644
index 7f7d0f7..0000000
--- a/src/main/resources/conf/prod.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# target execution: local or grid
-target=local
-
-# initial URL
-url.base = https://codepen.io/eliasnogueira/full/PooMNpX
-
-# global test timeout
-timeout = 5
-
-# grid url and port
-grid.url = 192.168.116.10
-grid.port = 4444
-
-# javafaker locale
-faker.locale = pt-BR
\ No newline at end of file
diff --git a/src/main/resources/conf/test.properties b/src/main/resources/conf/test.properties
deleted file mode 100644
index 7208e64..0000000
--- a/src/main/resources/conf/test.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# target execution: local or grid
-target= grid
-
-# initial URL
-url.base = https://codepen.io/eliasnogueira/full/PooMNpX
-
-# global test timeout
-timeout = 5
-
-# grid url and port
-grid.url = localhost
-grid.port = 4444
-
-# javafaker locale
-faker.locale = pt-BR
\ 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 63%
rename from src/main/java/test/BaseWeb.java
rename to src/test/java/com/eliasnogueira/BaseWeb.java
index d18fb41..cd5c894 100644
--- a/src/main/java/test/BaseWeb.java
+++ b/src/test/java/com/eliasnogueira/BaseWeb.java
@@ -22,39 +22,37 @@
* SOFTWARE.
*/
-package test;
-
-import com.aventstack.extentreports.testng.listener.ExtentITestListenerClassAdapter;
-import config.Configuration;
-import driver.DriverFactory;
-import driver.DriverManager;
-import org.aeonbits.owner.ConfigCache;
-import org.aeonbits.owner.ConfigFactory;
+package com.eliasnogueira;
+
+import com.eliasnogueira.driver.DriverManager;
+import com.eliasnogueira.driver.TargetFactory;
+import com.eliasnogueira.report.AllureManager;
import org.openqa.selenium.WebDriver;
-import org.testng.annotations.*;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+
+import static com.eliasnogueira.config.ConfigurationManager.configuration;
-@Listeners({ExtentITestListenerClassAdapter.class, TestListener.class})
public abstract class BaseWeb {
@BeforeSuite
- @Parameters("environment")
- public void setConfiguration(@Optional("dev") String environment) {
- String env = System.getenv("environment");
- ConfigFactory.setProperty("env", env == null ? environment : env);
+ public void beforeSuite() {
+ AllureManager.setAllureEnvironmentInformation();
}
- @BeforeMethod
+ @BeforeMethod(alwaysRun = true)
@Parameters("browser")
public void preCondition(@Optional("chrome") String browser) {
- Configuration configuration = ConfigCache.getOrCreate(Configuration.class);
-
- 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
+ @AfterMethod(alwaysRun = true)
public void postCondition() {
DriverManager.quit();
}
diff --git a/src/test/java/test/BookRoomWebTest.java b/src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
similarity index 62%
rename from src/test/java/test/BookRoomWebTest.java
rename to src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
index 1985689..7471352 100644
--- a/src/test/java/test/BookRoomWebTest.java
+++ b/src/test/java/com/eliasnogueira/test/BookRoomWebTest.java
@@ -21,37 +21,37 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-package test;
+package com.eliasnogueira.test;
-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.*;
+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())
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/extent.properties b/src/test/resources/extent.properties
deleted file mode 100644
index eeedfe6..0000000
--- a/src/test/resources/extent.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-extent.reporter.html.start=true
-extent.reporter.html.out=target/report/execution_report.html
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/test/resources/selenium-grid.properties b/src/test/resources/selenium-grid.properties
new file mode 100644
index 0000000..90d2540
--- /dev/null
+++ b/src/test/resources/selenium-grid.properties
@@ -0,0 +1,3 @@
+# grid url and port
+grid.url = localhost
+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 70%
rename from src/test/resources/suites/multi_browser.xml
rename to src/test/resources/suites/selenium-grid.xml
index 49d0088..04d9024 100644
--- a/src/test/resources/suites/multi_browser.xml
+++ b/src/test/resources/suites/selenium-grid.xml
@@ -1,20 +1,18 @@
-
-
-
+
-
+
-
\ No newline at end of file
+