diff --git a/.doc/selenium-docker-aws-jenkins.png b/.doc/selenium-docker-aws-jenkins.png
new file mode 100644
index 0000000..03c6e1f
Binary files /dev/null and b/.doc/selenium-docker-aws-jenkins.png differ
diff --git a/03-automation-framework/dependencies.md b/03-automation-framework/dependencies.md
index c5de236..4743689 100644
--- a/03-automation-framework/dependencies.md
+++ b/03-automation-framework/dependencies.md
@@ -1,3 +1,8 @@
+# Application URLs
+
+- [Flight Reservation](https://d1uh9e7cu07ukd.cloudfront.net/selenium-docker/reservation-app/index.html)
+- [Vendor Portal](https://d1uh9e7cu07ukd.cloudfront.net/selenium-docker/vendor-app/index.html)
+
# Maven Dependencies
```
diff --git a/04-selenium-grid/docker-images.md b/04-selenium-grid/docker-images.md
index a374946..b732e65 100644
--- a/04-selenium-grid/docker-images.md
+++ b/04-selenium-grid/docker-images.md
@@ -1,5 +1,14 @@
# Docker Images
+## First check your CPU architecture
+
+- MAC/Linux users
+`uname -m`
+- Windows Users
+`echo %PROCESSOR_ARCHITECTURE%`
+
+If you see "arm", then use ARM images.
+
Use this docker images for creating selenium grid
| Image | ARM | Others |
@@ -8,7 +17,9 @@ Use this docker images for creating selenium grid
| Chrome | seleniarm/node-chromium:4.10 | selenium/node-chrome:4.10 |
| Firefox | seleniarm/node-firefox:4.10 | selenium/node-firefox:4.10 |
-## Docker Hub Reference:
+## References:
- [Selenium](https://hub.docker.com/u/selenium)
-- [Seleniarm](https://hub.docker.com/u/seleniarm)
\ No newline at end of file
+- [Seleniarm](https://hub.docker.com/u/seleniarm)
+- [Seleniarm to Selenium Namespace](https://github.com/SeleniumHQ/docker-selenium/issues/1847)
+- [Edge does not work in arm](https://techcommunity.microsoft.com/t5/discussions/edge-for-linux-arm64/m-p/1532272)
diff --git a/06-jenkins-ci-cd/selenium-docker b/06-jenkins-ci-cd/selenium-docker
deleted file mode 160000
index d9df057..0000000
--- a/06-jenkins-ci-cd/selenium-docker
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit d9df057c4770ff590ec008ff34cd8873e8939e32
diff --git a/06-jenkins-ci-cd/selenium-docker/.gitignore b/06-jenkins-ci-cd/selenium-docker/.gitignore
new file mode 100644
index 0000000..6ce9f50
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/.gitignore
@@ -0,0 +1,49 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Java template
+# Compiled class file
+**/*.class
+
+# Log file
+**/*.log
+
+# BlueJ files
+**/*.ctxt
+
+# Mobile Tools for Java (J2ME)
+**/.mtj.tmp/
+
+# Package Files #
+**/*.jar
+**/*.war
+**/*.nar
+**/*.ear
+**/*.zip
+**/*.tar.gz
+**/*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+**/hs_err_pid*
+
+### Maven template
+**/target/
+**/pom.xml.tag
+**/pom.xml.releaseBackup
+**/pom.xml.versionsBackup
+**/pom.xml.next
+**/release.properties
+**/dependency-reduced-pom.xml
+**/buildNumber.properties
+**/.mvn/timing.properties
+**/.mvn/wrapper/maven-wrapper.jar
+
+**/*.iml
+
+**/.idea/
+
+
+**/HELP.md
+**/.mvn/
+**/mvnw
+**/mvnw.cmd
+**/node_modules/
+
diff --git a/06-jenkins-ci-cd/selenium-docker/Dockerfile b/06-jenkins-ci-cd/selenium-docker/Dockerfile
new file mode 100644
index 0000000..a32e382
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/Dockerfile
@@ -0,0 +1,14 @@
+FROM java47
+
+# Install curl jq
+RUN apk add curl jq
+
+# workspace
+WORKDIR /home/selenium-docker
+
+# Add the required files
+ADD target/docker-resources ./
+ADD runner.sh runner.sh
+
+# Start the runner.sh
+ENTRYPOINT sh runner.sh
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/Jenkinsfile b/06-jenkins-ci-cd/selenium-docker/Jenkinsfile
new file mode 100644
index 0000000..19a4d0f
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/Jenkinsfile
@@ -0,0 +1,39 @@
+pipeline{
+
+ agent any
+
+ stages{
+
+ stage('Build Jar'){
+ steps{
+ sh 'mvn clean package -DskipTests'
+ }
+ }
+
+ stage('Build Image'){
+ steps{
+ sh 'docker build -t=vinsdocker/selenium:latest .'
+ }
+ }
+
+ stage('Push Image'){
+ environment{
+ DOCKER_HUB = credentials('dockerhub-creds')
+ }
+ steps{
+ sh 'echo ${DOCKER_HUB_PSW} | docker login -u ${DOCKER_HUB_USR} --password-stdin'
+ sh 'docker push vinsdocker/selenium:latest'
+ sh "docker tag vinsdocker/selenium:latest vinsdocker/selenium:${env.BUILD_NUMBER}"
+ sh "docker push vinsdocker/selenium:${env.BUILD_NUMBER}"
+ }
+ }
+
+ }
+
+ post {
+ always {
+ sh 'docker logout'
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/pom.xml b/06-jenkins-ci-cd/selenium-docker/pom.xml
new file mode 100644
index 0000000..a2b5017
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/pom.xml
@@ -0,0 +1,159 @@
+
+
+
+ 4.0.0
+
+ com.vinsguru
+ selenium-docker
+ 1.0-SNAPSHOT
+
+
+ 4.11.0
+ 1.4.8
+ 5.4.0
+ 7.8.0
+ 2.12.0
+
+ 3.11.0
+ 3.6.0
+ 3.3.0
+ 3.1.2
+ 3.3.1
+
+ ${project.build.directory}/docker-resources
+
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ ${selenium.java.version}
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ ${webdriver.manager.version}
+ test
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+ test
+
+
+
+ org.testng
+ testng
+ ${testng.version}
+ test
+
+
+
+ selenium-docker
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven.compiler.version}
+
+ 17
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven.surefire.version}
+
+
+
+ firefox
+ true
+
+
+ src/test/resources/test-suites/vendor-portal.xml
+ src/test/resources/test-suites/flight-reservation.xml
+
+ 4
+ target/test-output
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ ${maven.dependency.version}
+
+
+ copy-dependencies
+ prepare-package
+
+ copy-dependencies
+
+
+
+ ${package.directory}/libs
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven.jar.version}
+
+ ${package.directory}/libs
+
+
+
+
+ test-jar
+
+
+ **/*.class
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${maven.resources.plugin}
+
+
+ copy-resources
+ prepare-package
+
+ copy-resources
+
+
+ ${package.directory}
+
+
+ src/test/resources
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/runner.sh b/06-jenkins-ci-cd/selenium-docker/runner.sh
new file mode 100644
index 0000000..84c888d
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/runner.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+#-------------------------------------------------------------------
+# This script expects the following environment variables
+# HUB_HOST
+# BROWSER
+# THREAD_COUNT
+# TEST_SUITE
+#-------------------------------------------------------------------
+
+# Let's print what we have received
+echo "-------------------------------------------"
+echo "HUB_HOST : ${HUB_HOST:-hub}"
+echo "BROWSER : ${BROWSER:-chrome}"
+echo "THREAD_COUNT : ${THREAD_COUNT:-1}"
+echo "TEST_SUITE : ${TEST_SUITE}"
+echo "-------------------------------------------"
+
+# Do not start the tests immediately. Hub has to be ready with browser nodes
+echo "Checking if hub is ready..!"
+count=0
+while [ "$( curl -s http://${HUB_HOST:-hub}:4444/status | jq -r .value.ready )" != "true" ]
+do
+ count=$((count+1))
+ echo "Attempt: ${count}"
+ if [ "$count" -ge 30 ]
+ then
+ echo "**** HUB IS NOT READY WITHIN 30 SECONDS ****"
+ exit 1
+ fi
+ sleep 1
+done
+
+# At this point, selenium grid should be up!
+echo "Selenium Grid is up and running. Running the test...."
+
+# Start the java command
+java -cp 'libs/*' \
+ -Dselenium.grid.enabled=true \
+ -Dselenium.grid.hubHost="${HUB_HOST:-hub}" \
+ -Dbrowser="${BROWSER:-chrome}" \
+ org.testng.TestNG \
+ -threadcount "${THREAD_COUNT:-1}" \
+ test-suites/"${TEST_SUITE}"
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/AbstractPage.java b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/AbstractPage.java
new file mode 100644
index 0000000..56e702e
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/AbstractPage.java
@@ -0,0 +1,22 @@
+package com.vinsguru.pages;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.time.Duration;
+
+public abstract class AbstractPage {
+
+ protected final WebDriver driver;
+ protected final WebDriverWait wait;
+
+ public AbstractPage(WebDriver driver){
+ this.driver = driver;
+ this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+ PageFactory.initElements(driver, this);
+ }
+
+ public abstract boolean isAt();
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightConfirmationPage.java b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightConfirmationPage.java
new file mode 100644
index 0000000..f38810c
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightConfirmationPage.java
@@ -0,0 +1,39 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FlightConfirmationPage extends AbstractPage {
+
+ private static final Logger log = LoggerFactory.getLogger(FlightConfirmationPage.class);
+
+ @FindBy(css = "#flights-confirmation-section .card-body .row:nth-child(1) .col:nth-child(2)")
+ private WebElement flightConfirmationElement;
+
+ @FindBy(css = "#flights-confirmation-section .card-body .row:nth-child(3) .col:nth-child(2)")
+ private WebElement totalPriceElement;
+
+ public FlightConfirmationPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.flightConfirmationElement));
+ return this.flightConfirmationElement.isDisplayed();
+ }
+
+ public String getPrice(){
+ String confirmation = this.flightConfirmationElement.getText();
+ String price = this.totalPriceElement.getText();
+ log.info("Flight confirmation# : {}", confirmation);
+ log.info("Total price : {}", price);
+ return price;
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSearchPage.java b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSearchPage.java
new file mode 100644
index 0000000..a26ff1a
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSearchPage.java
@@ -0,0 +1,37 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.Select;
+
+public class FlightsSearchPage extends AbstractPage {
+
+ @FindBy(id = "passengers")
+ private WebElement passengerSelect;
+
+ @FindBy(id = "search-flights")
+ private WebElement searchFlightsButton;
+
+ public FlightsSearchPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.passengerSelect));
+ return this.passengerSelect.isDisplayed();
+ }
+
+ public void selectPassengers(String noOfPassengers){
+ Select passengers = new Select(this.passengerSelect);
+ passengers.selectByValue(noOfPassengers);
+ }
+
+ public void searchFlights(){
+ this.searchFlightsButton.click();
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSelectionPage.java b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSelectionPage.java
new file mode 100644
index 0000000..09b5227
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSelectionPage.java
@@ -0,0 +1,43 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class FlightsSelectionPage extends AbstractPage {
+
+ @FindBy(name = "departure-flight")
+ private List departureFlightsOptions;
+
+ @FindBy(name = "arrival-flight")
+ private List arrivalFlightsOptions;
+
+ @FindBy(id = "confirm-flights")
+ private WebElement confirmFlightsButton;
+
+ public FlightsSelectionPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.confirmFlightsButton));
+ return this.confirmFlightsButton.isDisplayed();
+ }
+
+ public void selectFlights(){
+ int random = ThreadLocalRandom.current().nextInt(0, departureFlightsOptions.size());
+ this.departureFlightsOptions.get(random).click();
+ this.arrivalFlightsOptions.get(random).click();
+ }
+
+ public void confirmFlights(){
+ this.confirmFlightsButton.click();
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationConfirmationPage.java b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationConfirmationPage.java
new file mode 100644
index 0000000..e52c174
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationConfirmationPage.java
@@ -0,0 +1,35 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+public class RegistrationConfirmationPage extends AbstractPage {
+
+ @FindBy(id = "go-to-flights-search")
+ private WebElement goToFlightsSearchButton;
+
+ @FindBy(css = "#registration-confirmation-section p b")
+ private WebElement firstNameElement;
+
+ public RegistrationConfirmationPage(WebDriver driver){
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.goToFlightsSearchButton));
+ return this.goToFlightsSearchButton.isDisplayed();
+ }
+
+ public String getFirstName(){
+ return this.firstNameElement.getText();
+ }
+
+ public void goToFlightsSearch(){
+ this.goToFlightsSearchButton.click();
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationPage.java b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationPage.java
new file mode 100644
index 0000000..0934a10
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationPage.java
@@ -0,0 +1,69 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+public class RegistrationPage extends AbstractPage {
+
+ @FindBy(id = "firstName")
+ private WebElement firstNameInput;
+
+ @FindBy(id = "lastName")
+ private WebElement lastNameInput;
+
+ @FindBy(id = "email")
+ private WebElement emailInput;
+
+ @FindBy(id = "password")
+ private WebElement passwordInput;
+
+ @FindBy(name = "street")
+ private WebElement streetInput;
+
+ @FindBy(name = "city")
+ private WebElement cityInput;
+
+ @FindBy(name = "zip")
+ private WebElement zipInput;
+
+ @FindBy(id = "register-btn")
+ private WebElement registerButton;
+
+ public RegistrationPage(WebDriver driver){
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.firstNameInput));
+ return this.firstNameInput.isDisplayed();
+ }
+
+ public void goTo(String url){
+ this.driver.get(url);
+ }
+
+ public void enterUserDetails(String firstName, String lastName){
+ this.firstNameInput.sendKeys(firstName);
+ this.lastNameInput.sendKeys(lastName);
+ }
+
+ public void enterUserCredentials(String email, String password){
+ this.emailInput.sendKeys(email);
+ this.passwordInput.sendKeys(password);
+ }
+
+ public void enterAddress(String street, String city, String zip){
+ this.streetInput.sendKeys(street);
+ this.cityInput.sendKeys(city);
+ this.zipInput.sendKeys(zip);
+ }
+
+ public void register(){
+ this.registerButton.click();
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/DashboardPage.java b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/DashboardPage.java
new file mode 100644
index 0000000..297f892
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/DashboardPage.java
@@ -0,0 +1,102 @@
+package com.vinsguru.pages.vendorportal;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DashboardPage extends AbstractPage {
+
+ private static final Logger log = LoggerFactory.getLogger(DashboardPage.class);
+
+ @FindBy(id = "monthly-earning")
+ private WebElement monthlyEarningElement;
+
+ @FindBy(id = "annual-earning")
+ private WebElement annualEarningElement;
+
+ @FindBy(id = "profit-margin")
+ private WebElement profitMarginElement;
+
+ @FindBy(id = "available-inventory")
+ private WebElement availableInventoryElement;
+
+ @FindBy(css = "#dataTable_filter input")
+ private WebElement searchInput;
+
+ @FindBy(id = "dataTable_info")
+ private WebElement searchResultsCountElement;
+
+ @FindBy(css = "img.img-profile")
+ private WebElement userProfilePictureElement;
+
+ // prefer id / name / css
+ @FindBy(linkText = "Logout")
+ private WebElement logoutLink;
+
+ @FindBy(css = "#logoutModal a")
+ private WebElement modalLogoutButton;
+
+ public DashboardPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.monthlyEarningElement));
+ return this.monthlyEarningElement.isDisplayed();
+ }
+
+ public String getMonthlyEarning(){
+ return this.monthlyEarningElement.getText();
+ }
+
+ public String getAnnualEarning(){
+ return this.annualEarningElement.getText();
+ }
+
+ public String getProfitMargin(){
+ return this.profitMarginElement.getText();
+ }
+
+ public String getAvailableInventory(){
+ return this.availableInventoryElement.getText();
+ }
+
+ public void searchOrderHistoryBy(String keyword){
+ this.searchInput.sendKeys(keyword);
+ }
+
+ /*
+ Showing 1 to 10 of 32 entries (filtered from 99 total entries)
+ arr[0] = "Showing"
+ arr[1] = "1"
+ arr[2] = "to"
+ arr[3] = "10"
+ arr[4] = "of"
+ arr[5] = "32"
+ ...
+ ...
+ */
+ public int getSearchResultsCount(){
+ String resultsText = this.searchResultsCountElement.getText();
+ String[] arr = resultsText.split(" ");
+ // if we do not have 5th item, it would throw exception.
+ // that's fine. we would want our test to fail anyway in that case!
+ int count = Integer.parseInt(arr[5]);
+ log.info("Results count: {}", count);
+ return count;
+ }
+
+ public void logout(){
+ this.userProfilePictureElement.click();
+ this.wait.until(ExpectedConditions.visibilityOf(this.logoutLink));
+ this.logoutLink.click();
+ this.wait.until(ExpectedConditions.visibilityOf(this.modalLogoutButton));
+ this.modalLogoutButton.click();
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/LoginPage.java b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/LoginPage.java
new file mode 100644
index 0000000..2e1b5c8
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/LoginPage.java
@@ -0,0 +1,40 @@
+package com.vinsguru.pages.vendorportal;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+public class LoginPage extends AbstractPage {
+
+ @FindBy(id = "username")
+ private WebElement usernameInput;
+
+ @FindBy(id = "password")
+ private WebElement passwordInput;
+
+ @FindBy(id = "login")
+ private WebElement loginButton;
+
+ public LoginPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.loginButton));
+ return this.loginButton.isDisplayed();
+ }
+
+ public void goTo(String url){
+ this.driver.get(url);
+ }
+
+ public void login(String username, String password){
+ this.usernameInput.sendKeys(username);
+ this.passwordInput.sendKeys(password);
+ this.loginButton.click();
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/main/resources/logback.xml b/06-jenkins-ci-cd/selenium-docker/src/main/resources/logback.xml
new file mode 100644
index 0000000..28aa43e
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/main/resources/logback.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level [%15.15t] %cyan(%-30.30logger{30}) : %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/listener/TestListener.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/listener/TestListener.java
new file mode 100644
index 0000000..68c1812
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/listener/TestListener.java
@@ -0,0 +1,21 @@
+package com.vinsguru.listener;
+
+import com.vinsguru.util.Constants;
+import org.openqa.selenium.OutputType;
+import org.openqa.selenium.TakesScreenshot;
+import org.testng.ITestListener;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+
+public class TestListener implements ITestListener {
+
+ @Override
+ public void onTestFailure(ITestResult result) {
+ TakesScreenshot driver = (TakesScreenshot) result.getTestContext().getAttribute(Constants.DRIVER);
+ String screenshot = driver.getScreenshotAs(OutputType.BASE64);
+ String htmlImageFormat = "
";
+ String htmlImage = String.format(htmlImageFormat, screenshot);
+ Reporter.log(htmlImage);
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/AbstractTest.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/AbstractTest.java
new file mode 100644
index 0000000..7b2a10e
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/AbstractTest.java
@@ -0,0 +1,64 @@
+package com.vinsguru.tests;
+
+import com.vinsguru.listener.TestListener;
+import com.vinsguru.util.Config;
+import com.vinsguru.util.Constants;
+import io.github.bonigarcia.wdm.WebDriverManager;
+import org.openqa.selenium.Capabilities;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.firefox.FirefoxOptions;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.ITestContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Listeners;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+@Listeners({TestListener.class})
+public abstract class AbstractTest {
+
+ private static final Logger log = LoggerFactory.getLogger(AbstractTest.class);
+
+ protected WebDriver driver;
+
+ @BeforeSuite
+ public void setupConfig(){
+ Config.initialize();
+ }
+
+ @BeforeTest
+ public void setDriver(ITestContext ctx) throws MalformedURLException {
+ this.driver = Boolean.parseBoolean(Config.get(Constants.GRID_ENABLED)) ? getRemoteDriver() : getLocalDriver();
+ ctx.setAttribute(Constants.DRIVER, this.driver);
+ }
+
+ private WebDriver getRemoteDriver() throws MalformedURLException {
+ Capabilities capabilities = new ChromeOptions();
+ if(Constants.FIREFOX.equalsIgnoreCase(Config.get(Constants.BROWSER))){
+ capabilities = new FirefoxOptions();
+ }
+ String urlFormat = Config.get(Constants.GRID_URL_FORMAT);
+ String hubHost = Config.get(Constants.GRID_HUB_HOST);
+ String url = String.format(urlFormat, hubHost);
+ log.info("grid url: {}", url);
+ return new RemoteWebDriver(new URL(url), capabilities);
+ }
+
+ private WebDriver getLocalDriver(){
+ WebDriverManager.chromedriver().setup();
+ return new ChromeDriver();
+ }
+
+ @AfterTest
+ public void quitDriver(){
+ this.driver.quit();
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/FlightReservationTest.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/FlightReservationTest.java
new file mode 100644
index 0000000..9069254
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/FlightReservationTest.java
@@ -0,0 +1,67 @@
+package com.vinsguru.tests.flightreservation;
+
+import com.vinsguru.pages.flightreservation.*;
+import com.vinsguru.tests.AbstractTest;
+import com.vinsguru.tests.flightreservation.model.FlightReservationTestData;
+import com.vinsguru.util.Config;
+import com.vinsguru.util.Constants;
+import com.vinsguru.util.JsonUtil;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+public class FlightReservationTest extends AbstractTest {
+
+ private FlightReservationTestData testData;
+
+ @BeforeTest
+ @Parameters("testDataPath")
+ public void setParameters(String testDataPath){
+ this.testData = JsonUtil.getTestData(testDataPath, FlightReservationTestData.class);
+ }
+
+ @Test
+ public void userRegistrationTest(){
+ RegistrationPage registrationPage = new RegistrationPage(driver);
+ registrationPage.goTo(Config.get(Constants.FLIGHT_RESERVATION_URL));
+ Assert.assertTrue(registrationPage.isAt());
+
+ registrationPage.enterUserDetails(testData.firstName(), testData.lastName());
+ registrationPage.enterUserCredentials(testData.email(), testData.password());
+ registrationPage.enterAddress(testData.street(), testData.city(), testData.zip());
+ registrationPage.register();
+ }
+
+ @Test(dependsOnMethods = "userRegistrationTest")
+ public void registrationConfirmationTest(){
+ RegistrationConfirmationPage registrationConfirmationPage = new RegistrationConfirmationPage(driver);
+ Assert.assertTrue(registrationConfirmationPage.isAt());
+ Assert.assertEquals(registrationConfirmationPage.getFirstName(), testData.firstName());
+ registrationConfirmationPage.goToFlightsSearch();
+ }
+
+ @Test(dependsOnMethods = "registrationConfirmationTest")
+ public void flightsSearchTest(){
+ FlightsSearchPage flightsSearchPage = new FlightsSearchPage(driver);
+ Assert.assertTrue(flightsSearchPage.isAt());
+ flightsSearchPage.selectPassengers(testData.passengersCount());
+ flightsSearchPage.searchFlights();
+ }
+
+ @Test(dependsOnMethods = "flightsSearchTest")
+ public void flightsSelectionTest(){
+ FlightsSelectionPage flightsSelectionPage = new FlightsSelectionPage(driver);
+ Assert.assertTrue(flightsSelectionPage.isAt());
+ flightsSelectionPage.selectFlights();
+ flightsSelectionPage.confirmFlights();
+ }
+
+ @Test(dependsOnMethods = "flightsSelectionTest")
+ public void flightReservationConfirmationTest(){
+ FlightConfirmationPage flightConfirmationPage = new FlightConfirmationPage(driver);
+ Assert.assertTrue(flightConfirmationPage.isAt());
+ Assert.assertEquals(flightConfirmationPage.getPrice(), testData.expectedPrice());
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/model/FlightReservationTestData.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/model/FlightReservationTestData.java
new file mode 100644
index 0000000..ce9675a
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/model/FlightReservationTestData.java
@@ -0,0 +1,12 @@
+package com.vinsguru.tests.flightreservation.model;
+
+public record FlightReservationTestData(String firstName,
+ String lastName,
+ String email,
+ String password,
+ String street,
+ String city,
+ String zip,
+ String passengersCount,
+ String expectedPrice) {
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/VendorPortalTest.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/VendorPortalTest.java
new file mode 100644
index 0000000..8644a19
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/VendorPortalTest.java
@@ -0,0 +1,57 @@
+package com.vinsguru.tests.vendorportal;
+
+import com.vinsguru.pages.vendorportal.DashboardPage;
+import com.vinsguru.pages.vendorportal.LoginPage;
+import com.vinsguru.tests.AbstractTest;
+import com.vinsguru.tests.vendorportal.model.VendorPortalTestData;
+import com.vinsguru.util.Config;
+import com.vinsguru.util.Constants;
+import com.vinsguru.util.JsonUtil;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+public class VendorPortalTest extends AbstractTest {
+
+ private LoginPage loginPage;
+ private DashboardPage dashboardPage;
+ private VendorPortalTestData testData;
+
+ @BeforeTest
+ @Parameters("testDataPath")
+ public void setPageObjects(String testDataPath){
+ this.loginPage = new LoginPage(driver);
+ this.dashboardPage = new DashboardPage(driver);
+ this.testData = JsonUtil.getTestData(testDataPath, VendorPortalTestData.class);
+ }
+
+ @Test
+ public void loginTest(){
+ loginPage.goTo(Config.get(Constants.VENDOR_PORTAL_URL));
+ Assert.assertTrue(loginPage.isAt());
+ loginPage.login(testData.username(), testData.password());
+ }
+
+ @Test(dependsOnMethods = "loginTest")
+ public void dashboardTest(){
+ Assert.assertTrue(dashboardPage.isAt());
+
+ // finance metrics
+ Assert.assertEquals(dashboardPage.getMonthlyEarning(), testData.monthlyEarning());
+ Assert.assertEquals(dashboardPage.getAnnualEarning(), testData.annualEarning());
+ Assert.assertEquals(dashboardPage.getProfitMargin(), testData.profitMargin());
+ Assert.assertEquals(dashboardPage.getAvailableInventory(), testData.availableInventory());
+
+ // order history search
+ dashboardPage.searchOrderHistoryBy(testData.searchKeyword());
+ Assert.assertEquals(dashboardPage.getSearchResultsCount(), testData.searchResultsCount());
+ }
+
+ @Test(dependsOnMethods = "dashboardTest")
+ public void logoutTest(){
+ dashboardPage.logout();
+ Assert.assertTrue(loginPage.isAt());
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/model/VendorPortalTestData.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/model/VendorPortalTestData.java
new file mode 100644
index 0000000..aaf4473
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/model/VendorPortalTestData.java
@@ -0,0 +1,92 @@
+package com.vinsguru.tests.vendorportal.model;
+
+public record VendorPortalTestData(String username,
+ String password,
+ String monthlyEarning,
+ String annualEarning,
+ String profitMargin,
+ String availableInventory,
+ String searchKeyword,
+ int searchResultsCount) {
+}
+
+/*
+public class VendorPortalTestData {
+
+ private String username;
+ private String password;
+ private String monthlyEarning;
+ private String annualEarning;
+ private String profitMargin;
+ private String availableInventory;
+ private String searchKeyword;
+ private int searchResultsCount;
+
+ public VendorPortalTestData() {
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getMonthlyEarning() {
+ return monthlyEarning;
+ }
+
+ public void setMonthlyEarning(String monthlyEarning) {
+ this.monthlyEarning = monthlyEarning;
+ }
+
+ public String getAnnualEarning() {
+ return annualEarning;
+ }
+
+ public void setAnnualEarning(String annualEarning) {
+ this.annualEarning = annualEarning;
+ }
+
+ public String getProfitMargin() {
+ return profitMargin;
+ }
+
+ public void setProfitMargin(String profitMargin) {
+ this.profitMargin = profitMargin;
+ }
+
+ public String getAvailableInventory() {
+ return availableInventory;
+ }
+
+ public void setAvailableInventory(String availableInventory) {
+ this.availableInventory = availableInventory;
+ }
+
+ public String getSearchKeyword() {
+ return searchKeyword;
+ }
+
+ public void setSearchKeyword(String searchKeyword) {
+ this.searchKeyword = searchKeyword;
+ }
+
+ public int getSearchResultsCount() {
+ return searchResultsCount;
+ }
+
+ public void setSearchResultsCount(int searchResultsCount) {
+ this.searchResultsCount = searchResultsCount;
+ }
+}
+*/
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/Config.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/Config.java
new file mode 100644
index 0000000..1c42d27
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/Config.java
@@ -0,0 +1,52 @@
+package com.vinsguru.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+public class Config {
+
+ private static final Logger log = LoggerFactory.getLogger(Config.class);
+ private static final String DEFAULT_PROPERTIES = "config/default.properties";
+ private static Properties properties;
+
+ public static void initialize(){
+
+ // load default properties
+ properties = loadProperties();
+
+ // check for any override
+ for(String key: properties.stringPropertyNames()){
+ if(System.getProperties().containsKey(key)){
+ properties.setProperty(key, System.getProperty(key));
+ }
+ }
+
+ // print
+ log.info("Test Properties");
+ log.info("-----------------");
+ for(String key: properties.stringPropertyNames()){
+ log.info("{}={}", key, properties.getProperty(key));
+ }
+ log.info("-----------------");
+
+ }
+
+ public static String get(String key){
+ return properties.getProperty(key);
+ }
+
+ private static Properties loadProperties(){
+ Properties properties = new Properties();
+ try(InputStream stream = ResourceLoader.getResource(DEFAULT_PROPERTIES)){
+ properties.load(stream);
+ }catch (Exception e){
+ log.error("unable to read the property file {}", DEFAULT_PROPERTIES, e);
+ }
+ return properties;
+ }
+
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/Constants.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/Constants.java
new file mode 100644
index 0000000..edd8c2d
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/Constants.java
@@ -0,0 +1,17 @@
+package com.vinsguru.util;
+
+public class Constants {
+
+ public static final String GRID_ENABLED = "selenium.grid.enabled";
+ public static final String GRID_URL_FORMAT = "selenium.grid.urlFormat";
+ public static final String GRID_HUB_HOST = "selenium.grid.hubHost";
+
+ public static final String BROWSER = "browser";
+ public static final String CHROME = "chrome";
+ public static final String FIREFOX = "firefox";
+ public static final String DRIVER = "driver";
+
+ public static final String FLIGHT_RESERVATION_URL = "flightReservation.url";
+ public static final String VENDOR_PORTAL_URL = "vendorPortal.url";
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/JsonUtil.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/JsonUtil.java
new file mode 100644
index 0000000..1f53d36
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/JsonUtil.java
@@ -0,0 +1,25 @@
+package com.vinsguru.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vinsguru.tests.vendorportal.model.VendorPortalTestData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+public class JsonUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ public static T getTestData(String path, Class type){
+ try(InputStream stream = ResourceLoader.getResource(path)){
+ return mapper.readValue(stream, type);
+ }catch (Exception e){
+ log.error("unable to read test data {}", path, e);
+ }
+ return null;
+ }
+
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/ResourceLoader.java b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/ResourceLoader.java
new file mode 100644
index 0000000..be14a26
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/java/com/vinsguru/util/ResourceLoader.java
@@ -0,0 +1,29 @@
+package com.vinsguru.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+
+/*
+ A simple utility to read file.
+ first we check the classpath. if found, it is used.
+ if not, then we check the filesystem
+ */
+public class ResourceLoader {
+
+ private static final Logger log = LoggerFactory.getLogger(ResourceLoader.class);
+
+ public static InputStream getResource(String path) throws Exception {
+ log.info("reading resource from location: {}", path);
+ InputStream stream = ResourceLoader.class.getClassLoader().getResourceAsStream(path);
+ if(Objects.nonNull(stream)){
+ return stream;
+ }
+ return Files.newInputStream(Path.of(path));
+ }
+
+}
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/config/default.properties b/06-jenkins-ci-cd/selenium-docker/src/test/resources/config/default.properties
new file mode 100644
index 0000000..f7cef6e
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/config/default.properties
@@ -0,0 +1,11 @@
+# selenium grid
+selenium.grid.enabled=false
+selenium.grid.urlFormat=http://%s:4444/wd/hub
+selenium.grid.hubHost=localhost
+
+# browser
+browser=chrome
+
+# application
+flightReservation.url=https://d1uh9e7cu07ukd.cloudfront.net/selenium-docker/reservation-app/index.html
+vendorPortal.url=https://d1uh9e7cu07ukd.cloudfront.net/selenium-docker/vendor-app/index.html
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-1.json b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-1.json
new file mode 100644
index 0000000..af109c9
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-1.json
@@ -0,0 +1,11 @@
+{
+ "firstName": "michael",
+ "lastName": "jackson",
+ "email": "mj@pop.com",
+ "password": "mj",
+ "street": "123 main street",
+ "city": "atlanta",
+ "zip": "30001",
+ "passengersCount": "1",
+ "expectedPrice": "$584 USD"
+}
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-2.json b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-2.json
new file mode 100644
index 0000000..fdc436b
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-2.json
@@ -0,0 +1,11 @@
+{
+ "firstName": "marshall",
+ "lastName": "mathers",
+ "email": "eminem@hiphop.com",
+ "password": "m&m",
+ "street": "456 main street",
+ "city": "detroit",
+ "zip": "40001",
+ "passengersCount": "2",
+ "expectedPrice": "$1169 USD"
+}
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-3.json b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-3.json
new file mode 100644
index 0000000..f7432d9
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-3.json
@@ -0,0 +1,11 @@
+{
+ "firstName": "cheb",
+ "lastName": "khaled",
+ "email": "khaled@rai.com",
+ "password": "didi",
+ "street": "101 non main street",
+ "city": "las vegas",
+ "zip": "50001",
+ "passengersCount": "3",
+ "expectedPrice": "$1753 USD"
+}
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-4.json b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-4.json
new file mode 100644
index 0000000..81b8f13
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-4.json
@@ -0,0 +1,11 @@
+{
+ "firstName": "dr",
+ "lastName": "dre",
+ "email": "dr@dre.com",
+ "password": "dre",
+ "street": "455 main street",
+ "city": "detroit",
+ "zip": "40001",
+ "passengersCount": "4",
+ "expectedPrice": "$2338 USD"
+}
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/john.json b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/john.json
new file mode 100644
index 0000000..83bfa62
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/john.json
@@ -0,0 +1,10 @@
+{
+ "username": "john",
+ "password": "john",
+ "monthlyEarning": "$3,453",
+ "annualEarning": "$34,485",
+ "profitMargin": "-16%",
+ "availableInventory": "67",
+ "searchKeyword": "2024/01/01",
+ "searchResultsCount": 0
+}
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/mike.json b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/mike.json
new file mode 100644
index 0000000..2ad690e
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/mike.json
@@ -0,0 +1,10 @@
+{
+ "username": "mike",
+ "password": "mike",
+ "monthlyEarning": "$55,000",
+ "annualEarning": "$563,300",
+ "profitMargin": "80%",
+ "availableInventory": "45",
+ "searchKeyword": "miami",
+ "searchResultsCount": 10
+}
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/sam.json b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/sam.json
new file mode 100644
index 0000000..b82ffc2
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-data/vendor-portal/sam.json
@@ -0,0 +1,10 @@
+{
+ "username": "sam",
+ "password": "sam",
+ "monthlyEarning": "$40,000",
+ "annualEarning": "$215,000",
+ "profitMargin": "50%",
+ "availableInventory": "18",
+ "searchKeyword": "adams",
+ "searchResultsCount": 8
+}
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-suites/flight-reservation.xml b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-suites/flight-reservation.xml
new file mode 100644
index 0000000..3f0f879
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-suites/flight-reservation.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-suites/vendor-portal.xml b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-suites/vendor-portal.xml
new file mode 100644
index 0000000..f335e13
--- /dev/null
+++ b/06-jenkins-ci-cd/selenium-docker/src/test/resources/test-suites/vendor-portal.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/.gitignore b/08-final-projects/selenium-docker/.gitignore
new file mode 100644
index 0000000..6ce9f50
--- /dev/null
+++ b/08-final-projects/selenium-docker/.gitignore
@@ -0,0 +1,49 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Java template
+# Compiled class file
+**/*.class
+
+# Log file
+**/*.log
+
+# BlueJ files
+**/*.ctxt
+
+# Mobile Tools for Java (J2ME)
+**/.mtj.tmp/
+
+# Package Files #
+**/*.jar
+**/*.war
+**/*.nar
+**/*.ear
+**/*.zip
+**/*.tar.gz
+**/*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+**/hs_err_pid*
+
+### Maven template
+**/target/
+**/pom.xml.tag
+**/pom.xml.releaseBackup
+**/pom.xml.versionsBackup
+**/pom.xml.next
+**/release.properties
+**/dependency-reduced-pom.xml
+**/buildNumber.properties
+**/.mvn/timing.properties
+**/.mvn/wrapper/maven-wrapper.jar
+
+**/*.iml
+
+**/.idea/
+
+
+**/HELP.md
+**/.mvn/
+**/mvnw
+**/mvnw.cmd
+**/node_modules/
+
diff --git a/08-final-projects/selenium-docker/Dockerfile b/08-final-projects/selenium-docker/Dockerfile
new file mode 100644
index 0000000..a32e382
--- /dev/null
+++ b/08-final-projects/selenium-docker/Dockerfile
@@ -0,0 +1,14 @@
+FROM java47
+
+# Install curl jq
+RUN apk add curl jq
+
+# workspace
+WORKDIR /home/selenium-docker
+
+# Add the required files
+ADD target/docker-resources ./
+ADD runner.sh runner.sh
+
+# Start the runner.sh
+ENTRYPOINT sh runner.sh
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/Jenkinsfile b/08-final-projects/selenium-docker/Jenkinsfile
new file mode 100644
index 0000000..19a4d0f
--- /dev/null
+++ b/08-final-projects/selenium-docker/Jenkinsfile
@@ -0,0 +1,39 @@
+pipeline{
+
+ agent any
+
+ stages{
+
+ stage('Build Jar'){
+ steps{
+ sh 'mvn clean package -DskipTests'
+ }
+ }
+
+ stage('Build Image'){
+ steps{
+ sh 'docker build -t=vinsdocker/selenium:latest .'
+ }
+ }
+
+ stage('Push Image'){
+ environment{
+ DOCKER_HUB = credentials('dockerhub-creds')
+ }
+ steps{
+ sh 'echo ${DOCKER_HUB_PSW} | docker login -u ${DOCKER_HUB_USR} --password-stdin'
+ sh 'docker push vinsdocker/selenium:latest'
+ sh "docker tag vinsdocker/selenium:latest vinsdocker/selenium:${env.BUILD_NUMBER}"
+ sh "docker push vinsdocker/selenium:${env.BUILD_NUMBER}"
+ }
+ }
+
+ }
+
+ post {
+ always {
+ sh 'docker logout'
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/pom.xml b/08-final-projects/selenium-docker/pom.xml
new file mode 100644
index 0000000..a2b5017
--- /dev/null
+++ b/08-final-projects/selenium-docker/pom.xml
@@ -0,0 +1,159 @@
+
+
+
+ 4.0.0
+
+ com.vinsguru
+ selenium-docker
+ 1.0-SNAPSHOT
+
+
+ 4.11.0
+ 1.4.8
+ 5.4.0
+ 7.8.0
+ 2.12.0
+
+ 3.11.0
+ 3.6.0
+ 3.3.0
+ 3.1.2
+ 3.3.1
+
+ ${project.build.directory}/docker-resources
+
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ ${selenium.java.version}
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ ${webdriver.manager.version}
+ test
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+ test
+
+
+
+ org.testng
+ testng
+ ${testng.version}
+ test
+
+
+
+ selenium-docker
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven.compiler.version}
+
+ 17
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven.surefire.version}
+
+
+
+ firefox
+ true
+
+
+ src/test/resources/test-suites/vendor-portal.xml
+ src/test/resources/test-suites/flight-reservation.xml
+
+ 4
+ target/test-output
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ ${maven.dependency.version}
+
+
+ copy-dependencies
+ prepare-package
+
+ copy-dependencies
+
+
+
+ ${package.directory}/libs
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven.jar.version}
+
+ ${package.directory}/libs
+
+
+
+
+ test-jar
+
+
+ **/*.class
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ ${maven.resources.plugin}
+
+
+ copy-resources
+ prepare-package
+
+ copy-resources
+
+
+ ${package.directory}
+
+
+ src/test/resources
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/runner.sh b/08-final-projects/selenium-docker/runner.sh
new file mode 100644
index 0000000..84c888d
--- /dev/null
+++ b/08-final-projects/selenium-docker/runner.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+#-------------------------------------------------------------------
+# This script expects the following environment variables
+# HUB_HOST
+# BROWSER
+# THREAD_COUNT
+# TEST_SUITE
+#-------------------------------------------------------------------
+
+# Let's print what we have received
+echo "-------------------------------------------"
+echo "HUB_HOST : ${HUB_HOST:-hub}"
+echo "BROWSER : ${BROWSER:-chrome}"
+echo "THREAD_COUNT : ${THREAD_COUNT:-1}"
+echo "TEST_SUITE : ${TEST_SUITE}"
+echo "-------------------------------------------"
+
+# Do not start the tests immediately. Hub has to be ready with browser nodes
+echo "Checking if hub is ready..!"
+count=0
+while [ "$( curl -s http://${HUB_HOST:-hub}:4444/status | jq -r .value.ready )" != "true" ]
+do
+ count=$((count+1))
+ echo "Attempt: ${count}"
+ if [ "$count" -ge 30 ]
+ then
+ echo "**** HUB IS NOT READY WITHIN 30 SECONDS ****"
+ exit 1
+ fi
+ sleep 1
+done
+
+# At this point, selenium grid should be up!
+echo "Selenium Grid is up and running. Running the test...."
+
+# Start the java command
+java -cp 'libs/*' \
+ -Dselenium.grid.enabled=true \
+ -Dselenium.grid.hubHost="${HUB_HOST:-hub}" \
+ -Dbrowser="${BROWSER:-chrome}" \
+ org.testng.TestNG \
+ -threadcount "${THREAD_COUNT:-1}" \
+ test-suites/"${TEST_SUITE}"
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/AbstractPage.java b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/AbstractPage.java
new file mode 100644
index 0000000..56e702e
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/AbstractPage.java
@@ -0,0 +1,22 @@
+package com.vinsguru.pages;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.time.Duration;
+
+public abstract class AbstractPage {
+
+ protected final WebDriver driver;
+ protected final WebDriverWait wait;
+
+ public AbstractPage(WebDriver driver){
+ this.driver = driver;
+ this.wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+ PageFactory.initElements(driver, this);
+ }
+
+ public abstract boolean isAt();
+
+}
diff --git a/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightConfirmationPage.java b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightConfirmationPage.java
new file mode 100644
index 0000000..f38810c
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightConfirmationPage.java
@@ -0,0 +1,39 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FlightConfirmationPage extends AbstractPage {
+
+ private static final Logger log = LoggerFactory.getLogger(FlightConfirmationPage.class);
+
+ @FindBy(css = "#flights-confirmation-section .card-body .row:nth-child(1) .col:nth-child(2)")
+ private WebElement flightConfirmationElement;
+
+ @FindBy(css = "#flights-confirmation-section .card-body .row:nth-child(3) .col:nth-child(2)")
+ private WebElement totalPriceElement;
+
+ public FlightConfirmationPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.flightConfirmationElement));
+ return this.flightConfirmationElement.isDisplayed();
+ }
+
+ public String getPrice(){
+ String confirmation = this.flightConfirmationElement.getText();
+ String price = this.totalPriceElement.getText();
+ log.info("Flight confirmation# : {}", confirmation);
+ log.info("Total price : {}", price);
+ return price;
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSearchPage.java b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSearchPage.java
new file mode 100644
index 0000000..a26ff1a
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSearchPage.java
@@ -0,0 +1,37 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.Select;
+
+public class FlightsSearchPage extends AbstractPage {
+
+ @FindBy(id = "passengers")
+ private WebElement passengerSelect;
+
+ @FindBy(id = "search-flights")
+ private WebElement searchFlightsButton;
+
+ public FlightsSearchPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.passengerSelect));
+ return this.passengerSelect.isDisplayed();
+ }
+
+ public void selectPassengers(String noOfPassengers){
+ Select passengers = new Select(this.passengerSelect);
+ passengers.selectByValue(noOfPassengers);
+ }
+
+ public void searchFlights(){
+ this.searchFlightsButton.click();
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSelectionPage.java b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSelectionPage.java
new file mode 100644
index 0000000..09b5227
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/FlightsSelectionPage.java
@@ -0,0 +1,43 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class FlightsSelectionPage extends AbstractPage {
+
+ @FindBy(name = "departure-flight")
+ private List departureFlightsOptions;
+
+ @FindBy(name = "arrival-flight")
+ private List arrivalFlightsOptions;
+
+ @FindBy(id = "confirm-flights")
+ private WebElement confirmFlightsButton;
+
+ public FlightsSelectionPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.confirmFlightsButton));
+ return this.confirmFlightsButton.isDisplayed();
+ }
+
+ public void selectFlights(){
+ int random = ThreadLocalRandom.current().nextInt(0, departureFlightsOptions.size());
+ this.departureFlightsOptions.get(random).click();
+ this.arrivalFlightsOptions.get(random).click();
+ }
+
+ public void confirmFlights(){
+ this.confirmFlightsButton.click();
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationConfirmationPage.java b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationConfirmationPage.java
new file mode 100644
index 0000000..e52c174
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationConfirmationPage.java
@@ -0,0 +1,35 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+public class RegistrationConfirmationPage extends AbstractPage {
+
+ @FindBy(id = "go-to-flights-search")
+ private WebElement goToFlightsSearchButton;
+
+ @FindBy(css = "#registration-confirmation-section p b")
+ private WebElement firstNameElement;
+
+ public RegistrationConfirmationPage(WebDriver driver){
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.goToFlightsSearchButton));
+ return this.goToFlightsSearchButton.isDisplayed();
+ }
+
+ public String getFirstName(){
+ return this.firstNameElement.getText();
+ }
+
+ public void goToFlightsSearch(){
+ this.goToFlightsSearchButton.click();
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationPage.java b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationPage.java
new file mode 100644
index 0000000..0934a10
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/flightreservation/RegistrationPage.java
@@ -0,0 +1,69 @@
+package com.vinsguru.pages.flightreservation;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+public class RegistrationPage extends AbstractPage {
+
+ @FindBy(id = "firstName")
+ private WebElement firstNameInput;
+
+ @FindBy(id = "lastName")
+ private WebElement lastNameInput;
+
+ @FindBy(id = "email")
+ private WebElement emailInput;
+
+ @FindBy(id = "password")
+ private WebElement passwordInput;
+
+ @FindBy(name = "street")
+ private WebElement streetInput;
+
+ @FindBy(name = "city")
+ private WebElement cityInput;
+
+ @FindBy(name = "zip")
+ private WebElement zipInput;
+
+ @FindBy(id = "register-btn")
+ private WebElement registerButton;
+
+ public RegistrationPage(WebDriver driver){
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.firstNameInput));
+ return this.firstNameInput.isDisplayed();
+ }
+
+ public void goTo(String url){
+ this.driver.get(url);
+ }
+
+ public void enterUserDetails(String firstName, String lastName){
+ this.firstNameInput.sendKeys(firstName);
+ this.lastNameInput.sendKeys(lastName);
+ }
+
+ public void enterUserCredentials(String email, String password){
+ this.emailInput.sendKeys(email);
+ this.passwordInput.sendKeys(password);
+ }
+
+ public void enterAddress(String street, String city, String zip){
+ this.streetInput.sendKeys(street);
+ this.cityInput.sendKeys(city);
+ this.zipInput.sendKeys(zip);
+ }
+
+ public void register(){
+ this.registerButton.click();
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/DashboardPage.java b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/DashboardPage.java
new file mode 100644
index 0000000..297f892
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/DashboardPage.java
@@ -0,0 +1,102 @@
+package com.vinsguru.pages.vendorportal;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DashboardPage extends AbstractPage {
+
+ private static final Logger log = LoggerFactory.getLogger(DashboardPage.class);
+
+ @FindBy(id = "monthly-earning")
+ private WebElement monthlyEarningElement;
+
+ @FindBy(id = "annual-earning")
+ private WebElement annualEarningElement;
+
+ @FindBy(id = "profit-margin")
+ private WebElement profitMarginElement;
+
+ @FindBy(id = "available-inventory")
+ private WebElement availableInventoryElement;
+
+ @FindBy(css = "#dataTable_filter input")
+ private WebElement searchInput;
+
+ @FindBy(id = "dataTable_info")
+ private WebElement searchResultsCountElement;
+
+ @FindBy(css = "img.img-profile")
+ private WebElement userProfilePictureElement;
+
+ // prefer id / name / css
+ @FindBy(linkText = "Logout")
+ private WebElement logoutLink;
+
+ @FindBy(css = "#logoutModal a")
+ private WebElement modalLogoutButton;
+
+ public DashboardPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.monthlyEarningElement));
+ return this.monthlyEarningElement.isDisplayed();
+ }
+
+ public String getMonthlyEarning(){
+ return this.monthlyEarningElement.getText();
+ }
+
+ public String getAnnualEarning(){
+ return this.annualEarningElement.getText();
+ }
+
+ public String getProfitMargin(){
+ return this.profitMarginElement.getText();
+ }
+
+ public String getAvailableInventory(){
+ return this.availableInventoryElement.getText();
+ }
+
+ public void searchOrderHistoryBy(String keyword){
+ this.searchInput.sendKeys(keyword);
+ }
+
+ /*
+ Showing 1 to 10 of 32 entries (filtered from 99 total entries)
+ arr[0] = "Showing"
+ arr[1] = "1"
+ arr[2] = "to"
+ arr[3] = "10"
+ arr[4] = "of"
+ arr[5] = "32"
+ ...
+ ...
+ */
+ public int getSearchResultsCount(){
+ String resultsText = this.searchResultsCountElement.getText();
+ String[] arr = resultsText.split(" ");
+ // if we do not have 5th item, it would throw exception.
+ // that's fine. we would want our test to fail anyway in that case!
+ int count = Integer.parseInt(arr[5]);
+ log.info("Results count: {}", count);
+ return count;
+ }
+
+ public void logout(){
+ this.userProfilePictureElement.click();
+ this.wait.until(ExpectedConditions.visibilityOf(this.logoutLink));
+ this.logoutLink.click();
+ this.wait.until(ExpectedConditions.visibilityOf(this.modalLogoutButton));
+ this.modalLogoutButton.click();
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/LoginPage.java b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/LoginPage.java
new file mode 100644
index 0000000..2e1b5c8
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/java/com/vinsguru/pages/vendorportal/LoginPage.java
@@ -0,0 +1,40 @@
+package com.vinsguru.pages.vendorportal;
+
+import com.vinsguru.pages.AbstractPage;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+public class LoginPage extends AbstractPage {
+
+ @FindBy(id = "username")
+ private WebElement usernameInput;
+
+ @FindBy(id = "password")
+ private WebElement passwordInput;
+
+ @FindBy(id = "login")
+ private WebElement loginButton;
+
+ public LoginPage(WebDriver driver) {
+ super(driver);
+ }
+
+ @Override
+ public boolean isAt() {
+ this.wait.until(ExpectedConditions.visibilityOf(this.loginButton));
+ return this.loginButton.isDisplayed();
+ }
+
+ public void goTo(String url){
+ this.driver.get(url);
+ }
+
+ public void login(String username, String password){
+ this.usernameInput.sendKeys(username);
+ this.passwordInput.sendKeys(password);
+ this.loginButton.click();
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/main/resources/logback.xml b/08-final-projects/selenium-docker/src/main/resources/logback.xml
new file mode 100644
index 0000000..28aa43e
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/main/resources/logback.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level [%15.15t] %cyan(%-30.30logger{30}) : %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/listener/TestListener.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/listener/TestListener.java
new file mode 100644
index 0000000..68c1812
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/listener/TestListener.java
@@ -0,0 +1,21 @@
+package com.vinsguru.listener;
+
+import com.vinsguru.util.Constants;
+import org.openqa.selenium.OutputType;
+import org.openqa.selenium.TakesScreenshot;
+import org.testng.ITestListener;
+import org.testng.ITestResult;
+import org.testng.Reporter;
+
+public class TestListener implements ITestListener {
+
+ @Override
+ public void onTestFailure(ITestResult result) {
+ TakesScreenshot driver = (TakesScreenshot) result.getTestContext().getAttribute(Constants.DRIVER);
+ String screenshot = driver.getScreenshotAs(OutputType.BASE64);
+ String htmlImageFormat = "
";
+ String htmlImage = String.format(htmlImageFormat, screenshot);
+ Reporter.log(htmlImage);
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/AbstractTest.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/AbstractTest.java
new file mode 100644
index 0000000..7b2a10e
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/AbstractTest.java
@@ -0,0 +1,64 @@
+package com.vinsguru.tests;
+
+import com.vinsguru.listener.TestListener;
+import com.vinsguru.util.Config;
+import com.vinsguru.util.Constants;
+import io.github.bonigarcia.wdm.WebDriverManager;
+import org.openqa.selenium.Capabilities;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.firefox.FirefoxOptions;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.ITestContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Listeners;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+@Listeners({TestListener.class})
+public abstract class AbstractTest {
+
+ private static final Logger log = LoggerFactory.getLogger(AbstractTest.class);
+
+ protected WebDriver driver;
+
+ @BeforeSuite
+ public void setupConfig(){
+ Config.initialize();
+ }
+
+ @BeforeTest
+ public void setDriver(ITestContext ctx) throws MalformedURLException {
+ this.driver = Boolean.parseBoolean(Config.get(Constants.GRID_ENABLED)) ? getRemoteDriver() : getLocalDriver();
+ ctx.setAttribute(Constants.DRIVER, this.driver);
+ }
+
+ private WebDriver getRemoteDriver() throws MalformedURLException {
+ Capabilities capabilities = new ChromeOptions();
+ if(Constants.FIREFOX.equalsIgnoreCase(Config.get(Constants.BROWSER))){
+ capabilities = new FirefoxOptions();
+ }
+ String urlFormat = Config.get(Constants.GRID_URL_FORMAT);
+ String hubHost = Config.get(Constants.GRID_HUB_HOST);
+ String url = String.format(urlFormat, hubHost);
+ log.info("grid url: {}", url);
+ return new RemoteWebDriver(new URL(url), capabilities);
+ }
+
+ private WebDriver getLocalDriver(){
+ WebDriverManager.chromedriver().setup();
+ return new ChromeDriver();
+ }
+
+ @AfterTest
+ public void quitDriver(){
+ this.driver.quit();
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/FlightReservationTest.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/FlightReservationTest.java
new file mode 100644
index 0000000..9069254
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/FlightReservationTest.java
@@ -0,0 +1,67 @@
+package com.vinsguru.tests.flightreservation;
+
+import com.vinsguru.pages.flightreservation.*;
+import com.vinsguru.tests.AbstractTest;
+import com.vinsguru.tests.flightreservation.model.FlightReservationTestData;
+import com.vinsguru.util.Config;
+import com.vinsguru.util.Constants;
+import com.vinsguru.util.JsonUtil;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+public class FlightReservationTest extends AbstractTest {
+
+ private FlightReservationTestData testData;
+
+ @BeforeTest
+ @Parameters("testDataPath")
+ public void setParameters(String testDataPath){
+ this.testData = JsonUtil.getTestData(testDataPath, FlightReservationTestData.class);
+ }
+
+ @Test
+ public void userRegistrationTest(){
+ RegistrationPage registrationPage = new RegistrationPage(driver);
+ registrationPage.goTo(Config.get(Constants.FLIGHT_RESERVATION_URL));
+ Assert.assertTrue(registrationPage.isAt());
+
+ registrationPage.enterUserDetails(testData.firstName(), testData.lastName());
+ registrationPage.enterUserCredentials(testData.email(), testData.password());
+ registrationPage.enterAddress(testData.street(), testData.city(), testData.zip());
+ registrationPage.register();
+ }
+
+ @Test(dependsOnMethods = "userRegistrationTest")
+ public void registrationConfirmationTest(){
+ RegistrationConfirmationPage registrationConfirmationPage = new RegistrationConfirmationPage(driver);
+ Assert.assertTrue(registrationConfirmationPage.isAt());
+ Assert.assertEquals(registrationConfirmationPage.getFirstName(), testData.firstName());
+ registrationConfirmationPage.goToFlightsSearch();
+ }
+
+ @Test(dependsOnMethods = "registrationConfirmationTest")
+ public void flightsSearchTest(){
+ FlightsSearchPage flightsSearchPage = new FlightsSearchPage(driver);
+ Assert.assertTrue(flightsSearchPage.isAt());
+ flightsSearchPage.selectPassengers(testData.passengersCount());
+ flightsSearchPage.searchFlights();
+ }
+
+ @Test(dependsOnMethods = "flightsSearchTest")
+ public void flightsSelectionTest(){
+ FlightsSelectionPage flightsSelectionPage = new FlightsSelectionPage(driver);
+ Assert.assertTrue(flightsSelectionPage.isAt());
+ flightsSelectionPage.selectFlights();
+ flightsSelectionPage.confirmFlights();
+ }
+
+ @Test(dependsOnMethods = "flightsSelectionTest")
+ public void flightReservationConfirmationTest(){
+ FlightConfirmationPage flightConfirmationPage = new FlightConfirmationPage(driver);
+ Assert.assertTrue(flightConfirmationPage.isAt());
+ Assert.assertEquals(flightConfirmationPage.getPrice(), testData.expectedPrice());
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/model/FlightReservationTestData.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/model/FlightReservationTestData.java
new file mode 100644
index 0000000..ce9675a
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/flightreservation/model/FlightReservationTestData.java
@@ -0,0 +1,12 @@
+package com.vinsguru.tests.flightreservation.model;
+
+public record FlightReservationTestData(String firstName,
+ String lastName,
+ String email,
+ String password,
+ String street,
+ String city,
+ String zip,
+ String passengersCount,
+ String expectedPrice) {
+}
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/VendorPortalTest.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/VendorPortalTest.java
new file mode 100644
index 0000000..8644a19
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/VendorPortalTest.java
@@ -0,0 +1,57 @@
+package com.vinsguru.tests.vendorportal;
+
+import com.vinsguru.pages.vendorportal.DashboardPage;
+import com.vinsguru.pages.vendorportal.LoginPage;
+import com.vinsguru.tests.AbstractTest;
+import com.vinsguru.tests.vendorportal.model.VendorPortalTestData;
+import com.vinsguru.util.Config;
+import com.vinsguru.util.Constants;
+import com.vinsguru.util.JsonUtil;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+public class VendorPortalTest extends AbstractTest {
+
+ private LoginPage loginPage;
+ private DashboardPage dashboardPage;
+ private VendorPortalTestData testData;
+
+ @BeforeTest
+ @Parameters("testDataPath")
+ public void setPageObjects(String testDataPath){
+ this.loginPage = new LoginPage(driver);
+ this.dashboardPage = new DashboardPage(driver);
+ this.testData = JsonUtil.getTestData(testDataPath, VendorPortalTestData.class);
+ }
+
+ @Test
+ public void loginTest(){
+ loginPage.goTo(Config.get(Constants.VENDOR_PORTAL_URL));
+ Assert.assertTrue(loginPage.isAt());
+ loginPage.login(testData.username(), testData.password());
+ }
+
+ @Test(dependsOnMethods = "loginTest")
+ public void dashboardTest(){
+ Assert.assertTrue(dashboardPage.isAt());
+
+ // finance metrics
+ Assert.assertEquals(dashboardPage.getMonthlyEarning(), testData.monthlyEarning());
+ Assert.assertEquals(dashboardPage.getAnnualEarning(), testData.annualEarning());
+ Assert.assertEquals(dashboardPage.getProfitMargin(), testData.profitMargin());
+ Assert.assertEquals(dashboardPage.getAvailableInventory(), testData.availableInventory());
+
+ // order history search
+ dashboardPage.searchOrderHistoryBy(testData.searchKeyword());
+ Assert.assertEquals(dashboardPage.getSearchResultsCount(), testData.searchResultsCount());
+ }
+
+ @Test(dependsOnMethods = "dashboardTest")
+ public void logoutTest(){
+ dashboardPage.logout();
+ Assert.assertTrue(loginPage.isAt());
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/model/VendorPortalTestData.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/model/VendorPortalTestData.java
new file mode 100644
index 0000000..aaf4473
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/tests/vendorportal/model/VendorPortalTestData.java
@@ -0,0 +1,92 @@
+package com.vinsguru.tests.vendorportal.model;
+
+public record VendorPortalTestData(String username,
+ String password,
+ String monthlyEarning,
+ String annualEarning,
+ String profitMargin,
+ String availableInventory,
+ String searchKeyword,
+ int searchResultsCount) {
+}
+
+/*
+public class VendorPortalTestData {
+
+ private String username;
+ private String password;
+ private String monthlyEarning;
+ private String annualEarning;
+ private String profitMargin;
+ private String availableInventory;
+ private String searchKeyword;
+ private int searchResultsCount;
+
+ public VendorPortalTestData() {
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getMonthlyEarning() {
+ return monthlyEarning;
+ }
+
+ public void setMonthlyEarning(String monthlyEarning) {
+ this.monthlyEarning = monthlyEarning;
+ }
+
+ public String getAnnualEarning() {
+ return annualEarning;
+ }
+
+ public void setAnnualEarning(String annualEarning) {
+ this.annualEarning = annualEarning;
+ }
+
+ public String getProfitMargin() {
+ return profitMargin;
+ }
+
+ public void setProfitMargin(String profitMargin) {
+ this.profitMargin = profitMargin;
+ }
+
+ public String getAvailableInventory() {
+ return availableInventory;
+ }
+
+ public void setAvailableInventory(String availableInventory) {
+ this.availableInventory = availableInventory;
+ }
+
+ public String getSearchKeyword() {
+ return searchKeyword;
+ }
+
+ public void setSearchKeyword(String searchKeyword) {
+ this.searchKeyword = searchKeyword;
+ }
+
+ public int getSearchResultsCount() {
+ return searchResultsCount;
+ }
+
+ public void setSearchResultsCount(int searchResultsCount) {
+ this.searchResultsCount = searchResultsCount;
+ }
+}
+*/
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/Config.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/Config.java
new file mode 100644
index 0000000..1c42d27
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/Config.java
@@ -0,0 +1,52 @@
+package com.vinsguru.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+public class Config {
+
+ private static final Logger log = LoggerFactory.getLogger(Config.class);
+ private static final String DEFAULT_PROPERTIES = "config/default.properties";
+ private static Properties properties;
+
+ public static void initialize(){
+
+ // load default properties
+ properties = loadProperties();
+
+ // check for any override
+ for(String key: properties.stringPropertyNames()){
+ if(System.getProperties().containsKey(key)){
+ properties.setProperty(key, System.getProperty(key));
+ }
+ }
+
+ // print
+ log.info("Test Properties");
+ log.info("-----------------");
+ for(String key: properties.stringPropertyNames()){
+ log.info("{}={}", key, properties.getProperty(key));
+ }
+ log.info("-----------------");
+
+ }
+
+ public static String get(String key){
+ return properties.getProperty(key);
+ }
+
+ private static Properties loadProperties(){
+ Properties properties = new Properties();
+ try(InputStream stream = ResourceLoader.getResource(DEFAULT_PROPERTIES)){
+ properties.load(stream);
+ }catch (Exception e){
+ log.error("unable to read the property file {}", DEFAULT_PROPERTIES, e);
+ }
+ return properties;
+ }
+
+
+}
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/Constants.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/Constants.java
new file mode 100644
index 0000000..edd8c2d
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/Constants.java
@@ -0,0 +1,17 @@
+package com.vinsguru.util;
+
+public class Constants {
+
+ public static final String GRID_ENABLED = "selenium.grid.enabled";
+ public static final String GRID_URL_FORMAT = "selenium.grid.urlFormat";
+ public static final String GRID_HUB_HOST = "selenium.grid.hubHost";
+
+ public static final String BROWSER = "browser";
+ public static final String CHROME = "chrome";
+ public static final String FIREFOX = "firefox";
+ public static final String DRIVER = "driver";
+
+ public static final String FLIGHT_RESERVATION_URL = "flightReservation.url";
+ public static final String VENDOR_PORTAL_URL = "vendorPortal.url";
+
+}
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/JsonUtil.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/JsonUtil.java
new file mode 100644
index 0000000..1f53d36
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/JsonUtil.java
@@ -0,0 +1,25 @@
+package com.vinsguru.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vinsguru.tests.vendorportal.model.VendorPortalTestData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+public class JsonUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ public static T getTestData(String path, Class type){
+ try(InputStream stream = ResourceLoader.getResource(path)){
+ return mapper.readValue(stream, type);
+ }catch (Exception e){
+ log.error("unable to read test data {}", path, e);
+ }
+ return null;
+ }
+
+
+}
diff --git a/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/ResourceLoader.java b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/ResourceLoader.java
new file mode 100644
index 0000000..be14a26
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/java/com/vinsguru/util/ResourceLoader.java
@@ -0,0 +1,29 @@
+package com.vinsguru.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+
+/*
+ A simple utility to read file.
+ first we check the classpath. if found, it is used.
+ if not, then we check the filesystem
+ */
+public class ResourceLoader {
+
+ private static final Logger log = LoggerFactory.getLogger(ResourceLoader.class);
+
+ public static InputStream getResource(String path) throws Exception {
+ log.info("reading resource from location: {}", path);
+ InputStream stream = ResourceLoader.class.getClassLoader().getResourceAsStream(path);
+ if(Objects.nonNull(stream)){
+ return stream;
+ }
+ return Files.newInputStream(Path.of(path));
+ }
+
+}
diff --git a/08-final-projects/selenium-docker/src/test/resources/config/default.properties b/08-final-projects/selenium-docker/src/test/resources/config/default.properties
new file mode 100644
index 0000000..f7cef6e
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/config/default.properties
@@ -0,0 +1,11 @@
+# selenium grid
+selenium.grid.enabled=false
+selenium.grid.urlFormat=http://%s:4444/wd/hub
+selenium.grid.hubHost=localhost
+
+# browser
+browser=chrome
+
+# application
+flightReservation.url=https://d1uh9e7cu07ukd.cloudfront.net/selenium-docker/reservation-app/index.html
+vendorPortal.url=https://d1uh9e7cu07ukd.cloudfront.net/selenium-docker/vendor-app/index.html
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-1.json b/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-1.json
new file mode 100644
index 0000000..af109c9
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-1.json
@@ -0,0 +1,11 @@
+{
+ "firstName": "michael",
+ "lastName": "jackson",
+ "email": "mj@pop.com",
+ "password": "mj",
+ "street": "123 main street",
+ "city": "atlanta",
+ "zip": "30001",
+ "passengersCount": "1",
+ "expectedPrice": "$584 USD"
+}
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-2.json b/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-2.json
new file mode 100644
index 0000000..fdc436b
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-2.json
@@ -0,0 +1,11 @@
+{
+ "firstName": "marshall",
+ "lastName": "mathers",
+ "email": "eminem@hiphop.com",
+ "password": "m&m",
+ "street": "456 main street",
+ "city": "detroit",
+ "zip": "40001",
+ "passengersCount": "2",
+ "expectedPrice": "$1169 USD"
+}
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-3.json b/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-3.json
new file mode 100644
index 0000000..f7432d9
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-3.json
@@ -0,0 +1,11 @@
+{
+ "firstName": "cheb",
+ "lastName": "khaled",
+ "email": "khaled@rai.com",
+ "password": "didi",
+ "street": "101 non main street",
+ "city": "las vegas",
+ "zip": "50001",
+ "passengersCount": "3",
+ "expectedPrice": "$1753 USD"
+}
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-4.json b/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-4.json
new file mode 100644
index 0000000..81b8f13
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-data/flight-reservation/passenger-4.json
@@ -0,0 +1,11 @@
+{
+ "firstName": "dr",
+ "lastName": "dre",
+ "email": "dr@dre.com",
+ "password": "dre",
+ "street": "455 main street",
+ "city": "detroit",
+ "zip": "40001",
+ "passengersCount": "4",
+ "expectedPrice": "$2338 USD"
+}
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/john.json b/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/john.json
new file mode 100644
index 0000000..83bfa62
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/john.json
@@ -0,0 +1,10 @@
+{
+ "username": "john",
+ "password": "john",
+ "monthlyEarning": "$3,453",
+ "annualEarning": "$34,485",
+ "profitMargin": "-16%",
+ "availableInventory": "67",
+ "searchKeyword": "2024/01/01",
+ "searchResultsCount": 0
+}
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/mike.json b/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/mike.json
new file mode 100644
index 0000000..2ad690e
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/mike.json
@@ -0,0 +1,10 @@
+{
+ "username": "mike",
+ "password": "mike",
+ "monthlyEarning": "$55,000",
+ "annualEarning": "$563,300",
+ "profitMargin": "80%",
+ "availableInventory": "45",
+ "searchKeyword": "miami",
+ "searchResultsCount": 10
+}
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/sam.json b/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/sam.json
new file mode 100644
index 0000000..b82ffc2
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-data/vendor-portal/sam.json
@@ -0,0 +1,10 @@
+{
+ "username": "sam",
+ "password": "sam",
+ "monthlyEarning": "$40,000",
+ "annualEarning": "$215,000",
+ "profitMargin": "50%",
+ "availableInventory": "18",
+ "searchKeyword": "adams",
+ "searchResultsCount": 8
+}
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-suites/flight-reservation.xml b/08-final-projects/selenium-docker/src/test/resources/test-suites/flight-reservation.xml
new file mode 100644
index 0000000..3f0f879
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-suites/flight-reservation.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/08-final-projects/selenium-docker/src/test/resources/test-suites/vendor-portal.xml b/08-final-projects/selenium-docker/src/test/resources/test-suites/vendor-portal.xml
new file mode 100644
index 0000000..f335e13
--- /dev/null
+++ b/08-final-projects/selenium-docker/src/test/resources/test-suites/vendor-portal.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..0439488
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,6 @@
+# Scalable Test Automation With Docker, Jenkins & AWS
+
+This repository contains all the source code for my course in Udemy
+
+
+