diff --git a/.github/workflows/run.yml b/.github/workflows/run.yml
index d55216e..5814b62 100644
--- a/.github/workflows/run.yml
+++ b/.github/workflows/run.yml
@@ -4,35 +4,102 @@ on:
pull_request:
branches: [ "master" ]
push:
- branches: [ "feature/WebDriverManager" ]
+ branches: [ "feature/wip" ]
+
+# Concurrency control to cancel previous runs on new commits
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
jobs:
build:
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+
+ strategy:
+ matrix:
+ java: [17]
+ os: [ubuntu-latest, ubuntu-22.04]
+ browser: [chrome]
+ fail-fast: false
- runs-on: ubuntu-latest
+ env:
+ DISPLAY: ':99'
+ MAVEN_OPTS: '-Xmx2048m'
+ BROWSER: ${{ matrix.browser }}
steps:
- - uses: actions/checkout@v4
- - name: Set up JDK 17
- uses: actions/setup-java@v4
+ - uses: actions/checkout@v4.1.7
+
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@v4.3.0
with:
- java-version: '17'
+ java-version: '${{ matrix.java }}'
distribution: 'temurin'
cache: maven
- - name: Remove Chrome
- run: sudo apt purge google-chrome-stable
- - name: Remove default Chromium
- run: sudo apt purge chromium-browser
-
- - name: Install Google Chrome # Using shell script to install Google Chrome
+
+ - name: Cache Maven dependencies
+ uses: actions/cache@v4.2.3
+ with:
+ path: |
+ ~/.m2/repository
+ ~/.m2/wrapper
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Set up Chrome
+ if: matrix.browser == 'chrome'
+ uses: browser-actions/setup-chrome@v1.7.2
+ with:
+ chrome-version: stable
+
+ - name: Start Xvfb
run: |
- chmod +x ./.github/scripts/InstallChrome.sh
- ./.github/scripts/InstallChrome.sh
-
- - run: |
- export DISPLAY=:99
- chromedriver --url-base=/wd/hub &
- sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional
+ sudo Xvfb :99 -ac -screen 0 1280x1024x24 > /dev/null 2>&1 &
+ sleep 3
+
+ - name: Validate environment
+ run: |
+ echo "Java version:"
+ java -version
+ echo "Maven version:"
+ mvn -version
+ echo "Browser: ${{ matrix.browser }}"
+ if [ "${{ matrix.browser }}" = "chrome" ]; then
+ echo "Chrome version:"
+ google-chrome --version
+ fi
+ echo "Display: $DISPLAY"
- name: Test
- run: mvn clean install -DactiveProfile=headless-github
\ No newline at end of file
+ run: mvn clean install -P headless-github -B -T 1C -Dbrowser=${{ matrix.browser }}
+ continue-on-error: false
+ timeout-minutes: 20
+
+ - name: Upload test reports
+ uses: actions/upload-artifact@v4.4.0
+ if: always()
+ with:
+ name: test-reports-java${{ matrix.java }}-${{ matrix.os }}-${{ matrix.browser }}
+ path: |
+ **/target/surefire-reports/
+ **/target/allure-results/
+ **/target/cucumber/
+ **/logs/
+ retention-days: 30
+
+ - name: Upload screenshots on failure
+ uses: actions/upload-artifact@v4.4.0
+ if: failure()
+ with:
+ name: screenshots-java${{ matrix.java }}-${{ matrix.os }}-${{ matrix.browser }}
+ path: |
+ **/target/screenshots/
+ **/target/test-output/
+ retention-days: 7
+
+ - name: Cleanup
+ if: always()
+ run: |
+ pkill -f Xvfb || true
+ pkill -f chrome || true
\ No newline at end of file
diff --git a/common/src/main/java/com/cmccarthy/common/config/TestConfiguration.java b/common/src/main/java/com/cmccarthy/common/config/TestConfiguration.java
new file mode 100644
index 0000000..1c8cd14
--- /dev/null
+++ b/common/src/main/java/com/cmccarthy/common/config/TestConfiguration.java
@@ -0,0 +1,61 @@
+package com.cmccarthy.common.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Setter
+@Getter
+@Configuration
+@ConfigurationProperties(prefix = "test")
+public class TestConfiguration {
+
+ // Main getters and setters
+ private int maxRetries = 3;
+ private int threadCount = 4;
+ private int timeoutSeconds = 30;
+ private boolean takeScreenshotOnFailure = true;
+ private boolean enableDetailedReporting = true;
+ private String defaultBrowser = "chrome";
+
+ // Parallel execution settings
+ private ParallelExecution parallelExecution = new ParallelExecution();
+
+ // API testing settings
+ private ApiConfig api = new ApiConfig();
+
+ // UI testing settings
+ private UiConfig ui = new UiConfig();
+
+ @Setter
+ @Getter
+ public static class ParallelExecution {
+ // getters and setters
+ private boolean enabled = true;
+ private int threadPoolSize = 4;
+ private int dataProviderThreadCount = 4;
+ }
+
+ @Setter
+ @Getter
+ public static class ApiConfig {
+ // getters and setters
+ private int connectionTimeout = 30000;
+ private int socketTimeout = 30000;
+ private int maxRetries = 3;
+ private boolean logRequestResponse = false;
+ }
+
+ @Setter
+ @Getter
+ public static class UiConfig {
+ // getters and setters
+ private boolean headless = true;
+ private int implicitWait = 10;
+ private int pageLoadTimeout = 30;
+ private String windowSize = "1920x1080";
+ private boolean enableVideoRecording = false;
+ }
+
+}
diff --git a/common/src/main/java/com/cmccarthy/common/service/RestService.java b/common/src/main/java/com/cmccarthy/common/service/RestService.java
index 5003856..5d13ece 100644
--- a/common/src/main/java/com/cmccarthy/common/service/RestService.java
+++ b/common/src/main/java/com/cmccarthy/common/service/RestService.java
@@ -1,6 +1,18 @@
package com.cmccarthy.common.service;
+import com.cmccarthy.common.config.TestConfiguration;
+import com.cmccarthy.common.utils.LogManager;
+import io.restassured.RestAssured;
+import io.restassured.config.HttpClientConfig;
+import io.restassured.config.RestAssuredConfig;
+import io.restassured.filter.log.RequestLoggingFilter;
+import io.restassured.filter.log.ResponseLoggingFilter;
+import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.retry.annotation.Backoff;
+import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import static io.restassured.RestAssured.given;
@@ -8,7 +20,73 @@
@Service
public class RestService {
+ @Autowired(required = false)
+ private TestConfiguration testConfiguration;
+
+ @Autowired(required = false)
+ private LogManager logManager;
+
+ @PostConstruct
+ public void init() {
+ if (testConfiguration != null) {
+ setupRestAssuredConfig();
+ }
+ }
+
+ private void setupRestAssuredConfig() {
+ TestConfiguration.ApiConfig apiConfig = testConfiguration.getApi();
+
+ RestAssured.config = RestAssuredConfig.config()
+ .httpClient(HttpClientConfig.httpClientConfig()
+ .setParam("http.connection.timeout", apiConfig.getConnectionTimeout())
+ .setParam("http.socket.timeout", apiConfig.getSocketTimeout()));
+
+ if (apiConfig.isLogRequestResponse() && logManager != null) {
+ RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter());
+ }
+ }
+
public RequestSpecification getRequestSpecification() {
- return given().header("Content-Type", "application/json");
+ return given()
+ .header("Content-Type", "application/json")
+ .accept("application/json");
+ }
+
+ @Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
+ public Response executeWithRetry(RequestSpecification request, String method, String endpoint) {
+ if (logManager != null) {
+ logManager.info("Executing " + method + " request to: " + endpoint);
+ }
+
+ Response response = switch (method.toUpperCase()) {
+ case "GET" -> request.get(endpoint);
+ case "POST" -> request.post(endpoint);
+ case "PUT" -> request.put(endpoint);
+ case "DELETE" -> request.delete(endpoint);
+ default -> throw new IllegalArgumentException("Unsupported HTTP method: " + method);
+ };
+
+ if (logManager != null) {
+ logManager.info("Response status: " + response.getStatusCode());
+ logManager.debug("Response body: " + response.getBody().asString());
+ }
+
+ return response;
+ }
+
+ public Response get(String endpoint) {
+ return executeWithRetry(getRequestSpecification(), "GET", endpoint);
+ }
+
+ public Response post(String endpoint, Object body) {
+ return executeWithRetry(getRequestSpecification().body(body), "POST", endpoint);
+ }
+
+ public Response put(String endpoint, Object body) {
+ return executeWithRetry(getRequestSpecification().body(body), "PUT", endpoint);
+ }
+
+ public Response delete(String endpoint) {
+ return executeWithRetry(getRequestSpecification(), "DELETE", endpoint);
}
}
diff --git a/common/src/main/java/com/cmccarthy/common/utils/ApplicationProperties.java b/common/src/main/java/com/cmccarthy/common/utils/ApplicationProperties.java
index a512073..d93bbad 100644
--- a/common/src/main/java/com/cmccarthy/common/utils/ApplicationProperties.java
+++ b/common/src/main/java/com/cmccarthy/common/utils/ApplicationProperties.java
@@ -1,8 +1,10 @@
package com.cmccarthy.common.utils;
+import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
+@Getter
@Component
public class ApplicationProperties {
@@ -15,31 +17,16 @@ public class ApplicationProperties {
@Value("${gridUrl}")
private String gridUrl;
- public String getWeatherAppUrl() {
- return weatherAppUrl;
- }
-
public void setWeatherAppUrl(String weatherAppUrl) {
this.weatherAppUrl = weatherAppUrl;
}
- public String getWikipediaUrl() {
- return wikipediaUrl;
- }
-
public void setWikipediaUrl(String wikipediaUrl) {
this.wikipediaUrl = wikipediaUrl;
}
- public String getBrowser() {
- return browser;
- }
-
public void setBrowser(String browser) {
this.browser = browser;
}
- public String getGridUrl() {
- return gridUrl;
- }
}
\ No newline at end of file
diff --git a/common/src/main/java/com/cmccarthy/common/utils/Constants.java b/common/src/main/java/com/cmccarthy/common/utils/Constants.java
index 86ae31b..d0df20f 100644
--- a/common/src/main/java/com/cmccarthy/common/utils/Constants.java
+++ b/common/src/main/java/com/cmccarthy/common/utils/Constants.java
@@ -10,8 +10,4 @@ public class Constants {
public static final long pollingShort = 100;
- public static String DRIVER_DIRECTORY = System.getProperty("user.dir") + "/src/test/resources/drivers";
-
- public static String COMMON_RESOURCES = System.getProperty("user.dir") + "/../common/src/main/resources";
-
}
diff --git a/common/src/main/java/com/cmccarthy/common/utils/DateTimeUtil.java b/common/src/main/java/com/cmccarthy/common/utils/DateTimeUtil.java
index 4af8537..4fa61a7 100644
--- a/common/src/main/java/com/cmccarthy/common/utils/DateTimeUtil.java
+++ b/common/src/main/java/com/cmccarthy/common/utils/DateTimeUtil.java
@@ -101,22 +101,15 @@ public static String featureDateManager(String tableDate) {
final LocalDateTime date = LocalDateTime.now();
- switch (switchType) {
- case "Day +":
- return date.plusDays(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
- case "Day -":
- return date.minusDays(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
- case "Month +":
- return date.plusMonths(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
- case "Month -":
- return date.minusMonths(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
- case "Year +":
- return date.plusYears(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
- case "Year -":
- return date.minusYears(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
- default:
- return date.format(ISO_DATE_FORMAT_NO_TIME);
- }
+ return switch (switchType) {
+ case "Day +" -> date.plusDays(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
+ case "Day -" -> date.minusDays(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
+ case "Month +" -> date.plusMonths(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
+ case "Month -" -> date.minusMonths(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
+ case "Year +" -> date.plusYears(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
+ case "Year -" -> date.minusYears(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
+ default -> date.format(ISO_DATE_FORMAT_NO_TIME);
+ };
}
return null;
}
diff --git a/common/src/main/java/com/cmccarthy/common/utils/StringUtil.java b/common/src/main/java/com/cmccarthy/common/utils/StringUtil.java
index acc2174..1ee3505 100644
--- a/common/src/main/java/com/cmccarthy/common/utils/StringUtil.java
+++ b/common/src/main/java/com/cmccarthy/common/utils/StringUtil.java
@@ -1,15 +1,33 @@
package com.cmccarthy.common.utils;
+import java.security.SecureRandom;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
-import static org.apache.commons.lang3.RandomStringUtils.random;
-
@SuppressWarnings("unused")
public class StringUtil {
private static final Random random = new Random();
+ private static final SecureRandom secureRandom = new SecureRandom();
+ private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ private static final String NUMBERS = "0123456789";
+ private static final String ALPHANUMERIC = ALPHABET + NUMBERS;
+
+ /**
+ * Generates a random string from the given character set.
+ *
+ * @param length the length of the string to generate
+ * @param charset the character set to use
+ * @return a random string
+ */
+ private static String generateRandomString(int length, String charset) {
+ StringBuilder sb = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ sb.append(charset.charAt(secureRandom.nextInt(charset.length())));
+ }
+ return sb.toString();
+ }
public static boolean getRandomBoolean() {
return random.nextBoolean();
@@ -24,27 +42,33 @@ public static int getRandomNumber(int min, int max) {
}
public static String getRandomAlphaString(int length) {
- return random(length, true, false);
+ return generateRandomString(length, ALPHABET);
}
public static String getRandomAlphaString(int min, int max) {
- return random(getRandomNumber(min, max), true, false);
+ return generateRandomString(getRandomNumber(min, max), ALPHABET);
}
public static String getRandomNumericString(int length, int min, int max) {
- return random(length, min, max, false, true);
+ // Generate numeric string with values between min and max
+ StringBuilder sb = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ int digit = ThreadLocalRandom.current().nextInt(min, max + 1) % 10;
+ sb.append(digit);
+ }
+ return sb.toString();
}
public static String getRandomNumericString(int min, int max) {
- return random(getRandomNumber(min, max), false, true);
+ return generateRandomString(getRandomNumber(min, max), NUMBERS);
}
public static String getRandomAlphaNumericString(int length) {
- return random(length, true, true);
+ return generateRandomString(length, ALPHANUMERIC);
}
public static String getRandomAlphaNumericString(int min, int max) {
- return random(getRandomNumber(min, max), true, true);
+ return generateRandomString(getRandomNumber(min, max), ALPHANUMERIC);
}
public static String getRandomAmount(String min, String max) {
diff --git a/common/src/main/resources/application-headless-github.properties b/common/src/main/resources/application-headless-github.properties
index b0280bc..f26d86e 100644
--- a/common/src/main/resources/application-headless-github.properties
+++ b/common/src/main/resources/application-headless-github.properties
@@ -2,3 +2,29 @@ weather.url.value=http://api.openweathermap.org/data/2.5/weather
wikipedia.url.value=https://www.wikipedia.org/
browser=chrome
github=true
+
+# Test Configuration
+test.max-retries=3
+test.thread-count=4
+test.timeout-seconds=30
+test.take-screenshot-on-failure=true
+test.enable-detailed-reporting=true
+test.default-browser=chrome
+
+# Parallel Execution Settings
+test.parallel-execution.enabled=true
+test.parallel-execution.thread-pool-size=4
+test.parallel-execution.data-provider-thread-count=4
+
+# API Configuration
+test.api.connection-timeout=30000
+test.api.socket-timeout=30000
+test.api.max-retries=3
+test.api.log-request-response=false
+
+# UI Configuration
+test.ui.headless=true
+test.ui.implicit-wait=10
+test.ui.page-load-timeout=30
+test.ui.window-size=1920x1080
+test.ui.enable-video-recording=false
diff --git a/common/src/main/resources/downloadDriver.sh b/common/src/main/resources/downloadDriver.sh
deleted file mode 100755
index 0a6e850..0000000
--- a/common/src/main/resources/downloadDriver.sh
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/bin/bash
-
-set -x
-export SCRIPT_DIR=../../../../wikipedia/src/test/resources
-cd $SCRIPT_DIR &&
-
-ROOT_DIR=$(pwd)
-TEST_RESOURCES=$ROOT_DIR
-FILE_EXTENSION=""
-
-echo $TEST_RESOURCES
-
-case "$OSTYPE" in
-solaris*) echo "$OSTYPE not supported" ;;
-darwin*) OS='mac64' ;;
-linux*) OS='linux64' ;;
-bsd*) echo "$OSTYPE not supported" ;;
-msys*) OS='win32' FILE_EXTENSION=".exe" ;;
-*) echo "unknown $OSTYPE" ;;
-esac
-
-if [ ! -f "$TEST_RESOURCES/drivers/chromedriver"$FILE_EXTENSION ]; then
- cd "$TEST_RESOURCES/drivers" &&
- mkdir "temp" &&
- cd "temp" &&
- curl -L -k --output driver.zip https://www.nuget.org/api/v2/package/Selenium.WebDriver.ChromeDriver/ --ssl-no-revoke &&
- unzip driver.zip &&
- cd driver/$OS &&
- cp "chromedriver$FILE_EXTENSION" "$TEST_RESOURCES/drivers" &&
- chmod +700 "$TEST_RESOURCES/drivers/chromedriver"$FILE_EXTENSION &&
- cd ../../../ &&
- rm -rf temp &&
- cd $ROOT_DIR
-fi
-
-if [ ! -f "$TEST_RESOURCES/drivers/geckodriver"$FILE_EXTENSION ]; then
- cd "$TEST_RESOURCES/drivers" &&
- mkdir "temp" &&
- cd "temp" &&
- curl -L -k --output driver.zip https://www.nuget.org/api/v2/package/Selenium.WebDriver.GeckoDriver/ --ssl-no-revoke &&
- unzip driver.zip &&
- cd driver/$OS &&
- cp "geckodriver$FILE_EXTENSION" "$TEST_RESOURCES/drivers" &&
- chmod +700 "$TEST_RESOURCES/drivers/geckodriver"$FILE_EXTENSION &&
- cd ../../../ &&
- rm -rf temp &&
- cd $ROOT_DIR
-fi
-
-#uncomment to keep bash open
-#! /bin/bash
diff --git a/pom.xml b/pom.xml
index 8dd8df1..0eff98f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,7 +34,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.5.0
+ 3.5.3
@@ -43,7 +43,7 @@
org.springframework.boot
spring-boot-dependencies
- 3.5.0
+ 3.5.3
pom
import
@@ -57,19 +57,22 @@
UTF-8
20250517
5.5.0
- 7.22.0
- 7.22.0
- 7.22.0
- 1.5.13
+ 7.25.0
+ 7.25.0
+ 7.25.0
+ 1.5.18
32.1.1
2.15.1
- 3.23.1
+ 3.26.0
1.14.0
2.29.1
4.33.0
- 1.7.0
+ 3.1.0
3.1.2
3.2.3
+ 2.19.0
+ 3.0.0
+ 4.3.0
@@ -147,6 +150,11 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
org.springframework.boot
spring-boot-starter-test
@@ -171,6 +179,33 @@
spring-boot-starter-aop
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ ${jackson.version}
+
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+ ${jakarta.annotation.version}
+
+
+
+
+ org.awaitility
+ awaitility
+ ${awaitility.version}
+ test
+
+
net.sf.jtidy
diff --git a/weather/src/test/java/com/cmccarthy/api/service/WeatherService.java b/weather/src/test/java/com/cmccarthy/api/service/WeatherService.java
index 05a6d26..3f821d4 100644
--- a/weather/src/test/java/com/cmccarthy/api/service/WeatherService.java
+++ b/weather/src/test/java/com/cmccarthy/api/service/WeatherService.java
@@ -5,6 +5,7 @@
import com.cmccarthy.common.utils.ApplicationProperties;
import com.cmccarthy.common.utils.LogManager;
import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -23,10 +24,11 @@ public class WeatherService {
private ApplicationProperties applicationProperties;
public void getWeatherForLocation(String location) {
- Response response = restService.getRequestSpecification()
- .param("q", location)
- .param("appid", "0a1b11f110d4b6cd43181d23d724cb94")
- .get(applicationProperties.getWeatherAppUrl());
+
+ final RequestSpecification requestSpecification = restService.getRequestSpecification().param("q", location)
+ .param("appid", "0a1b11f110d4b6cd43181d23d724cb94");
+
+ Response response = restService.executeWithRetry(requestSpecification, "GET", applicationProperties.getWeatherAppUrl());
stepDefinitionDataManager.addToStoredObjectMap("class", response);
diff --git a/weather/src/test/resources/suite/WeatherSuiteTest.xml b/weather/src/test/resources/suite/WeatherSuiteTest.xml
index 79361eb..3bfb9f7 100644
--- a/weather/src/test/resources/suite/WeatherSuiteTest.xml
+++ b/weather/src/test/resources/suite/WeatherSuiteTest.xml
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/wikipedia/src/test/java/com/cmccarthy/ui/config/WikipediaContextConfiguration.java b/wikipedia/src/test/java/com/cmccarthy/ui/config/WikipediaContextConfiguration.java
index 131b518..7e85ed7 100644
--- a/wikipedia/src/test/java/com/cmccarthy/ui/config/WikipediaContextConfiguration.java
+++ b/wikipedia/src/test/java/com/cmccarthy/ui/config/WikipediaContextConfiguration.java
@@ -7,7 +7,10 @@
@EnableRetry
@Configuration
-@ComponentScan({"com.cmccarthy.ui", "com.cmccarthy.common"})
+@ComponentScan({
+ "com.cmccarthy.ui",
+ "com.cmccarthy.common"
+})
@PropertySource("classpath:/application.properties")
public class WikipediaContextConfiguration {
}
diff --git a/wikipedia/src/test/java/com/cmccarthy/ui/step/Hooks.java b/wikipedia/src/test/java/com/cmccarthy/ui/step/Hooks.java
index 17446cf..c7ba190 100644
--- a/wikipedia/src/test/java/com/cmccarthy/ui/step/Hooks.java
+++ b/wikipedia/src/test/java/com/cmccarthy/ui/step/Hooks.java
@@ -14,6 +14,7 @@
@CucumberContextConfiguration
public class Hooks extends WikipediaAbstractTestDefinition {
+
@Autowired
private LogManager logManager;
@Autowired
@@ -31,9 +32,6 @@ public void beforeScenario(Scenario scenario) throws IOException {
@After
public void afterScenario(Scenario scenario) {
hookUtil.endOfTest(scenario);
- if (driverManager.getDriver() != null) {
- driverManager.getDriver().quit();
- driverManager.setDriver(null);
- }
+ driverManager.quitDriver();
}
}
\ No newline at end of file
diff --git a/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverHelper.java b/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverHelper.java
index 7845f49..d140546 100644
--- a/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverHelper.java
+++ b/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverHelper.java
@@ -39,7 +39,7 @@ public void sendKeys(WebElement element, String value) {
/**
* Clicks on an element by WebElement
*/
- @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
+ @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
public void click(WebElement element) throws NoSuchFieldException {
try {
driverWait.waitForElementToLoad(element);
@@ -53,7 +53,7 @@ public void click(WebElement element) throws NoSuchFieldException {
/**
* Clicks on an element by Locator
*/
- @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
+ @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
public void click(By locator) throws NoSuchFieldException {
try {
driverWait.waitForElementToLoad(locator);
@@ -67,7 +67,7 @@ public void click(By locator) throws NoSuchFieldException {
/**
* Clicks on an element by Locator
*/
- @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = { RetryException.class })
+ @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
public void rightClick(By locator) throws NoSuchFieldException {
driverWait.waitForElementToLoad(locator);
final WebElement element = driverManager.getDriver().findElement(locator);
@@ -81,7 +81,7 @@ public void rightClick(By locator) throws NoSuchFieldException {
}
}
- @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
+ @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
public void scrollElementIntoView(WebElement element) {
try {
driverManager.getJSExecutor().executeScript("arguments[0].scrollIntoView(true);", element);
@@ -128,7 +128,7 @@ public void clickAction(WebElement element) throws NoSuchFieldException {
/**
* Clicks on an element using Actions
*/
- @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
+ @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
public void clickAction(By locator) throws NoSuchFieldException {
driverWait.waitForElementToLoad(locator);
diff --git a/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverManager.java b/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverManager.java
index 30bdb0b..07e903e 100644
--- a/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverManager.java
+++ b/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverManager.java
@@ -1,8 +1,10 @@
package com.cmccarthy.ui.utils;
+import com.cmccarthy.common.config.TestConfiguration;
import com.cmccarthy.common.utils.ApplicationProperties;
import com.cmccarthy.common.utils.Constants;
import org.openqa.selenium.Capabilities;
+import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
@@ -38,6 +40,8 @@ public class DriverManager {
@Autowired
private ApplicationProperties applicationProperties;
@Autowired
+ private TestConfiguration testConfig;
+ @Autowired
private DriverWait driverWait;
@Autowired
private Environment environment;
@@ -50,23 +54,45 @@ public void createDriver() throws IOException {
setLocalWebDriver();
}
getDriver().manage().deleteAllCookies();//useful for AJAX pages
+
+ // Set the window size if specified in the configuration
+ if (testConfig.getUi().getWindowSize() != null && !testConfig.getUi().getWindowSize().isEmpty()) {
+ String[] dimensions = testConfig.getUi().getWindowSize().split("x");
+ if (dimensions.length == 2) {
+ try {
+ int width = Integer.parseInt(dimensions[0]);
+ int height = Integer.parseInt(dimensions[1]);
+ getDriver().manage().window().setSize(new Dimension(width, height));
+ log.info("Set browser window size to: {}x{}", width, height);
+ } catch (NumberFormatException e) {
+ log.error("Failed to parse window size from configuration: {}", testConfig.getUi().getWindowSize());
+ }
+ }
+ }
}
}
- public void setLocalWebDriver() throws IOException {
- switch (applicationProperties.getBrowser()) {
+ public void setLocalWebDriver() {
+ String browser = applicationProperties.getBrowser();
+ boolean isHeadless = testConfig.getUi().isHeadless();
+
+ switch (browser) {
case ("chrome") -> {
ChromeOptions options = new ChromeOptions();
options.addArguments("--disable-logging");
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--start-maximized");
- options.addArguments("--headless=new");
+ if (isHeadless) {
+ options.addArguments("--headless=new");
+ }
driverThreadLocal.set(new ChromeDriver(options));
}
case ("firefox") -> {
FirefoxOptions firefoxOptions = new FirefoxOptions();
- firefoxOptions.setCapability("marionette", true);
+ if (isHeadless) {
+ firefoxOptions.addArguments("--headless");
+ }
driverThreadLocal.set(new FirefoxDriver(firefoxOptions));
}
case ("ie") -> {
@@ -80,27 +106,45 @@ public void setLocalWebDriver() throws IOException {
}
case ("edge") -> {
EdgeOptions edgeOptions = new EdgeOptions();
+ if (isHeadless) {
+ edgeOptions.addArguments("--headless");
+ }
driverThreadLocal.set(new EdgeDriver(edgeOptions));
}
- default ->
- throw new NoSuchElementException("Failed to create an instance of WebDriver for: " + applicationProperties.getBrowser());
+ default -> throw new NoSuchElementException("Failed to create an instance of WebDriver for: " + browser);
}
+
+ // Configure WebDriver timeouts
+ WebDriver driver = driverThreadLocal.get();
+ driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(testConfig.getUi().getImplicitWait()));
+ driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(testConfig.getUi().getPageLoadTimeout()));
+
driverWait.getDriverWaitThreadLocal()
- .set(new WebDriverWait(driverThreadLocal.get(), Duration.ofSeconds(Constants.timeoutShort), Duration.ofSeconds(Constants.pollingShort)));
+ .set(new WebDriverWait(driver,
+ Duration.ofSeconds(testConfig.getTimeoutSeconds()),
+ Duration.ofMillis(Constants.pollingShort)));
}
private void setRemoteDriver(URL hubUrl) {
Capabilities capability;
- switch (applicationProperties.getBrowser()) {
+ String browser = applicationProperties.getBrowser();
+ boolean isHeadless = testConfig.getUi().isHeadless();
+
+ switch (browser) {
case "firefox" -> {
- capability = new FirefoxOptions();
- driverThreadLocal.set(new RemoteWebDriver(hubUrl, capability));
+ FirefoxOptions options = new FirefoxOptions();
+ if (isHeadless) {
+ options.addArguments("--headless");
+ }
+ driverThreadLocal.set(new RemoteWebDriver(hubUrl, options));
}
case "chrome" -> {
ChromeOptions options = new ChromeOptions();
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
- options.addArguments("--headless");
+ if (isHeadless) {
+ options.addArguments("--headless");
+ }
driverThreadLocal.set(new RemoteWebDriver(hubUrl, options));
}
case "ie" -> {
@@ -112,25 +156,51 @@ private void setRemoteDriver(URL hubUrl) {
driverThreadLocal.set(new RemoteWebDriver(hubUrl, capability));
}
case ("edge") -> {
- capability = new EdgeOptions();
- driverThreadLocal.set(new RemoteWebDriver(hubUrl, capability));
+ EdgeOptions options = new EdgeOptions();
+ if (isHeadless) {
+ options.addArguments("--headless");
+ }
+ driverThreadLocal.set(new RemoteWebDriver(hubUrl, options));
}
default ->
- throw new NoSuchElementException("Failed to create an instance of RemoteWebDriver for: " + applicationProperties.getBrowser());
+ throw new NoSuchElementException("Failed to create an instance of RemoteWebDriver for: " + browser);
}
+
+ // Configure WebDriver timeouts
+ WebDriver driver = driverThreadLocal.get();
+ driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(testConfig.getUi().getImplicitWait()));
+ driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(testConfig.getUi().getPageLoadTimeout()));
+
driverWait.getDriverWaitThreadLocal()
- .set(new WebDriverWait(driverThreadLocal.get(), Duration.ofSeconds(Constants.timeoutShort), Duration.ofSeconds(Constants.pollingShort)));
+ .set(new WebDriverWait(driver,
+ Duration.ofSeconds(testConfig.getTimeoutSeconds()),
+ Duration.ofMillis(Constants.pollingShort)));
}
public WebDriver getDriver() {
return driverThreadLocal.get();
}
- public void setDriver(WebDriver driver) {
- driverThreadLocal.set(driver);
- }
-
public JavascriptExecutor getJSExecutor() {
return (JavascriptExecutor) getDriver();
}
-}
+
+ /**
+ * Safely quit and clean up the driver
+ */
+ public void quitDriver() {
+ WebDriver driver = getDriver();
+ if (driver != null) {
+ try {
+ driver.quit();
+ log.info("WebDriver quit successfully");
+ } catch (Exception e) {
+ log.warn("Error while quitting WebDriver: {}", e.getMessage());
+ } finally {
+ // Remove ThreadLocal references
+ driverThreadLocal.remove();
+ driverWait.getDriverWaitThreadLocal().remove();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverWait.java b/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverWait.java
index e0c3eaf..3e2b3e9 100644
--- a/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverWait.java
+++ b/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverWait.java
@@ -17,6 +17,7 @@
import java.util.NoSuchElementException;
@Component
+@SuppressWarnings("unused")
public class DriverWait {
private static final ThreadLocal> driverWaitThreadLocal = new ThreadLocal<>();
diff --git a/wikipedia/src/test/resources/suite/WikipediaSuiteTest.xml b/wikipedia/src/test/resources/suite/WikipediaSuiteTest.xml
index f196d67..4d851eb 100644
--- a/wikipedia/src/test/resources/suite/WikipediaSuiteTest.xml
+++ b/wikipedia/src/test/resources/suite/WikipediaSuiteTest.xml
@@ -1,8 +1,8 @@
-
+
-
+