Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 87 additions & 20 deletions .github/workflows/run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +6 to +10
Copy link

Copilot AI Jul 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spring Boot may not bind @ConfigurationProperties on a scanned @Configuration class without enabling it; consider adding @EnableConfigurationProperties(TestConfiguration.class) in your test context or using @ConfigurationPropertiesScan.

Suggested change
import org.springframework.context.annotation.Configuration;
@Setter
@Getter
@Configuration
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@Setter
@Getter
@Configuration
@EnableConfigurationProperties(TestConfiguration.class)

Copilot uses AI. Check for mistakes.

@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;
}

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,92 @@
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;

@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))
Copy link

Copilot AI Jul 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retry maxAttempts is hard-coded to 3; consider using testConfiguration.getApi().getMaxRetries() to keep retry settings in sync with the centralized configuration.

Suggested change
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
@Retryable(value = {Exception.class}, maxAttemptsExpression = "#{@restService.getMaxAttempts()}", backoff = @Backoff(delay = 1000))

Copilot uses AI. Check for mistakes.

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);
}
}
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";

}
25 changes: 9 additions & 16 deletions common/src/main/java/com/cmccarthy/common/utils/DateTimeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading
Loading