Skip to content

Commit 1ec2ede

Browse files
authored
Enhanced CI/CD, Configuration Management & Dependency Updates (#13)
## Summary of Changes The changes include significant improvements to the Spring Cucumber TestNG parallel test harness: ### 🔧 **GitHub Actions Workflow Updates** (run.yml) - **Enhanced CI/CD Pipeline**: Updated from single OS to matrix strategy with multiple OS versions - **Concurrency Control**: Added automatic cancellation of previous runs on new commits - **Browser Management**: Improved Chrome setup using dedicated browser actions - **Environment Validation**: Added comprehensive environment checks - **Artifact Management**: Enhanced test report and screenshot upload with proper retention policies - **Resource Cleanup**: Added proper cleanup of background processes ### 📦 **Dependency Updates** (pom.xml) - **Spring Boot**: Upgraded from 3.5.0 to 3.5.3 - **Cucumber**: Updated from 7.22.0 to 7.25.0 - **Logback**: Updated from 1.5.13 to 1.5.18 - **AssertJ**: Updated from 3.23.1 to 3.26.0 - **Maven Plugins**: Updated remote resources plugin from 1.7.0 to 3.1.0 - **New Dependencies**: Added Jackson 2.19.0, Jakarta Annotations 3.0.0, Awaitility 4.3.0 ### 🏗️ **New Configuration System** - **TestConfiguration Class**: New centralized configuration management with properties for: - Parallel execution settings - API configuration (timeouts, retries) - UI configuration (headless mode, window size, timeouts) - **Enhanced Properties**: New application properties for comprehensive test configuration ### 🔄 **Service Layer Improvements** - **RestService**: Enhanced with retry mechanisms, configurable timeouts, and better error handling - **WeatherService**: Added proper request specification handling - **Application Properties**: Simplified with Lombok annotations ### 🧪 **Test Infrastructure Enhancements** - **Driver Management**: - Configurable headless/headed mode - Dynamic window sizing - Improved remote driver setup - Better resource cleanup - **Parallel Execution**: Enhanced TestNG suite configuration for better parallel test execution - **Retry Logic**: Improved retry mechanisms with proper spacing and formatting ### 🧹 **Code Quality Improvements** - **Modern Java**: Replaced traditional switch statements with modern switch expressions - **Security**: Enhanced random string generation with SecureRandom - **Lombok Integration**: Reduced boilerplate code with proper getter/setter annotations - **Error Handling**: Better exception handling and logging throughout ### 🗑️ **Cleanup** - **Removed Legacy Scripts**: Deleted old driver download scripts - **Code Formatting**: Fixed spacing and formatting issues in retry annotations ### 📊 **Testing Improvements** - **Suite Configuration**: Updated TestNG suites with better parallel execution settings - **Hook Management**: Streamlined test hooks and scenario management - **Driver Lifecycle**: Improved WebDriver creation, management, and cleanup
1 parent 9656b0e commit 1ec2ede

File tree

18 files changed

+451
-161
lines changed

18 files changed

+451
-161
lines changed

.github/workflows/run.yml

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,102 @@ on:
44
pull_request:
55
branches: [ "master" ]
66
push:
7-
branches: [ "feature/WebDriverManager" ]
7+
branches: [ "feature/wip" ]
8+
9+
# Concurrency control to cancel previous runs on new commits
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
813

914
jobs:
1015
build:
16+
runs-on: ${{ matrix.os }}
17+
timeout-minutes: 30
18+
19+
strategy:
20+
matrix:
21+
java: [17]
22+
os: [ubuntu-latest, ubuntu-22.04]
23+
browser: [chrome]
24+
fail-fast: false
1125

12-
runs-on: ubuntu-latest
26+
env:
27+
DISPLAY: ':99'
28+
MAVEN_OPTS: '-Xmx2048m'
29+
BROWSER: ${{ matrix.browser }}
1330

1431
steps:
15-
- uses: actions/checkout@v4
16-
- name: Set up JDK 17
17-
uses: actions/setup-java@v4
32+
- uses: actions/checkout@v4.1.7
33+
34+
- name: Set up JDK ${{ matrix.java }}
35+
uses: actions/setup-java@v4.3.0
1836
with:
19-
java-version: '17'
37+
java-version: '${{ matrix.java }}'
2038
distribution: 'temurin'
2139
cache: maven
22-
- name: Remove Chrome
23-
run: sudo apt purge google-chrome-stable
24-
- name: Remove default Chromium
25-
run: sudo apt purge chromium-browser
26-
27-
- name: Install Google Chrome # Using shell script to install Google Chrome
40+
41+
- name: Cache Maven dependencies
42+
uses: actions/cache@v4.2.3
43+
with:
44+
path: |
45+
~/.m2/repository
46+
~/.m2/wrapper
47+
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
48+
restore-keys: |
49+
${{ runner.os }}-maven-
50+
- name: Set up Chrome
51+
if: matrix.browser == 'chrome'
52+
uses: browser-actions/setup-chrome@v1.7.2
53+
with:
54+
chrome-version: stable
55+
56+
- name: Start Xvfb
2857
run: |
29-
chmod +x ./.github/scripts/InstallChrome.sh
30-
./.github/scripts/InstallChrome.sh
31-
32-
- run: |
33-
export DISPLAY=:99
34-
chromedriver --url-base=/wd/hub &
35-
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional
58+
sudo Xvfb :99 -ac -screen 0 1280x1024x24 > /dev/null 2>&1 &
59+
sleep 3
60+
61+
- name: Validate environment
62+
run: |
63+
echo "Java version:"
64+
java -version
65+
echo "Maven version:"
66+
mvn -version
67+
echo "Browser: ${{ matrix.browser }}"
68+
if [ "${{ matrix.browser }}" = "chrome" ]; then
69+
echo "Chrome version:"
70+
google-chrome --version
71+
fi
72+
echo "Display: $DISPLAY"
3673
3774
- name: Test
38-
run: mvn clean install -DactiveProfile=headless-github
75+
run: mvn clean install -P headless-github -B -T 1C -Dbrowser=${{ matrix.browser }}
76+
continue-on-error: false
77+
timeout-minutes: 20
78+
79+
- name: Upload test reports
80+
uses: actions/upload-artifact@v4.4.0
81+
if: always()
82+
with:
83+
name: test-reports-java${{ matrix.java }}-${{ matrix.os }}-${{ matrix.browser }}
84+
path: |
85+
**/target/surefire-reports/
86+
**/target/allure-results/
87+
**/target/cucumber/
88+
**/logs/
89+
retention-days: 30
90+
91+
- name: Upload screenshots on failure
92+
uses: actions/upload-artifact@v4.4.0
93+
if: failure()
94+
with:
95+
name: screenshots-java${{ matrix.java }}-${{ matrix.os }}-${{ matrix.browser }}
96+
path: |
97+
**/target/screenshots/
98+
**/target/test-output/
99+
retention-days: 7
100+
101+
- name: Cleanup
102+
if: always()
103+
run: |
104+
pkill -f Xvfb || true
105+
pkill -f chrome || true
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.cmccarthy.common.config;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
import org.springframework.boot.context.properties.ConfigurationProperties;
6+
import org.springframework.context.annotation.Configuration;
7+
8+
@Setter
9+
@Getter
10+
@Configuration
11+
@ConfigurationProperties(prefix = "test")
12+
public class TestConfiguration {
13+
14+
// Main getters and setters
15+
private int maxRetries = 3;
16+
private int threadCount = 4;
17+
private int timeoutSeconds = 30;
18+
private boolean takeScreenshotOnFailure = true;
19+
private boolean enableDetailedReporting = true;
20+
private String defaultBrowser = "chrome";
21+
22+
// Parallel execution settings
23+
private ParallelExecution parallelExecution = new ParallelExecution();
24+
25+
// API testing settings
26+
private ApiConfig api = new ApiConfig();
27+
28+
// UI testing settings
29+
private UiConfig ui = new UiConfig();
30+
31+
@Setter
32+
@Getter
33+
public static class ParallelExecution {
34+
// getters and setters
35+
private boolean enabled = true;
36+
private int threadPoolSize = 4;
37+
private int dataProviderThreadCount = 4;
38+
}
39+
40+
@Setter
41+
@Getter
42+
public static class ApiConfig {
43+
// getters and setters
44+
private int connectionTimeout = 30000;
45+
private int socketTimeout = 30000;
46+
private int maxRetries = 3;
47+
private boolean logRequestResponse = false;
48+
}
49+
50+
@Setter
51+
@Getter
52+
public static class UiConfig {
53+
// getters and setters
54+
private boolean headless = true;
55+
private int implicitWait = 10;
56+
private int pageLoadTimeout = 30;
57+
private String windowSize = "1920x1080";
58+
private boolean enableVideoRecording = false;
59+
}
60+
61+
}
Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,92 @@
11
package com.cmccarthy.common.service;
22

3+
import com.cmccarthy.common.config.TestConfiguration;
4+
import com.cmccarthy.common.utils.LogManager;
5+
import io.restassured.RestAssured;
6+
import io.restassured.config.HttpClientConfig;
7+
import io.restassured.config.RestAssuredConfig;
8+
import io.restassured.filter.log.RequestLoggingFilter;
9+
import io.restassured.filter.log.ResponseLoggingFilter;
10+
import io.restassured.response.Response;
311
import io.restassured.specification.RequestSpecification;
12+
import jakarta.annotation.PostConstruct;
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.retry.annotation.Backoff;
15+
import org.springframework.retry.annotation.Retryable;
416
import org.springframework.stereotype.Service;
517

618
import static io.restassured.RestAssured.given;
719

820
@Service
921
public class RestService {
1022

23+
@Autowired(required = false)
24+
private TestConfiguration testConfiguration;
25+
26+
@Autowired(required = false)
27+
private LogManager logManager;
28+
29+
@PostConstruct
30+
public void init() {
31+
if (testConfiguration != null) {
32+
setupRestAssuredConfig();
33+
}
34+
}
35+
36+
private void setupRestAssuredConfig() {
37+
TestConfiguration.ApiConfig apiConfig = testConfiguration.getApi();
38+
39+
RestAssured.config = RestAssuredConfig.config()
40+
.httpClient(HttpClientConfig.httpClientConfig()
41+
.setParam("http.connection.timeout", apiConfig.getConnectionTimeout())
42+
.setParam("http.socket.timeout", apiConfig.getSocketTimeout()));
43+
44+
if (apiConfig.isLogRequestResponse() && logManager != null) {
45+
RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter());
46+
}
47+
}
48+
1149
public RequestSpecification getRequestSpecification() {
12-
return given().header("Content-Type", "application/json");
50+
return given()
51+
.header("Content-Type", "application/json")
52+
.accept("application/json");
53+
}
54+
55+
@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
56+
public Response executeWithRetry(RequestSpecification request, String method, String endpoint) {
57+
if (logManager != null) {
58+
logManager.info("Executing " + method + " request to: " + endpoint);
59+
}
60+
61+
Response response = switch (method.toUpperCase()) {
62+
case "GET" -> request.get(endpoint);
63+
case "POST" -> request.post(endpoint);
64+
case "PUT" -> request.put(endpoint);
65+
case "DELETE" -> request.delete(endpoint);
66+
default -> throw new IllegalArgumentException("Unsupported HTTP method: " + method);
67+
};
68+
69+
if (logManager != null) {
70+
logManager.info("Response status: " + response.getStatusCode());
71+
logManager.debug("Response body: " + response.getBody().asString());
72+
}
73+
74+
return response;
75+
}
76+
77+
public Response get(String endpoint) {
78+
return executeWithRetry(getRequestSpecification(), "GET", endpoint);
79+
}
80+
81+
public Response post(String endpoint, Object body) {
82+
return executeWithRetry(getRequestSpecification().body(body), "POST", endpoint);
83+
}
84+
85+
public Response put(String endpoint, Object body) {
86+
return executeWithRetry(getRequestSpecification().body(body), "PUT", endpoint);
87+
}
88+
89+
public Response delete(String endpoint) {
90+
return executeWithRetry(getRequestSpecification(), "DELETE", endpoint);
1391
}
1492
}
Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.cmccarthy.common.utils;
22

3+
import lombok.Getter;
34
import org.springframework.beans.factory.annotation.Value;
45
import org.springframework.stereotype.Component;
56

7+
@Getter
68
@Component
79
public class ApplicationProperties {
810

@@ -15,31 +17,16 @@ public class ApplicationProperties {
1517
@Value("${gridUrl}")
1618
private String gridUrl;
1719

18-
public String getWeatherAppUrl() {
19-
return weatherAppUrl;
20-
}
21-
2220
public void setWeatherAppUrl(String weatherAppUrl) {
2321
this.weatherAppUrl = weatherAppUrl;
2422
}
2523

26-
public String getWikipediaUrl() {
27-
return wikipediaUrl;
28-
}
29-
3024
public void setWikipediaUrl(String wikipediaUrl) {
3125
this.wikipediaUrl = wikipediaUrl;
3226
}
3327

34-
public String getBrowser() {
35-
return browser;
36-
}
37-
3828
public void setBrowser(String browser) {
3929
this.browser = browser;
4030
}
4131

42-
public String getGridUrl() {
43-
return gridUrl;
44-
}
4532
}

common/src/main/java/com/cmccarthy/common/utils/Constants.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,4 @@ public class Constants {
1010

1111
public static final long pollingShort = 100;
1212

13-
public static String DRIVER_DIRECTORY = System.getProperty("user.dir") + "/src/test/resources/drivers";
14-
15-
public static String COMMON_RESOURCES = System.getProperty("user.dir") + "/../common/src/main/resources";
16-
1713
}

common/src/main/java/com/cmccarthy/common/utils/DateTimeUtil.java

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -101,22 +101,15 @@ public static String featureDateManager(String tableDate) {
101101

102102
final LocalDateTime date = LocalDateTime.now();
103103

104-
switch (switchType) {
105-
case "Day +":
106-
return date.plusDays(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
107-
case "Day -":
108-
return date.minusDays(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
109-
case "Month +":
110-
return date.plusMonths(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
111-
case "Month -":
112-
return date.minusMonths(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
113-
case "Year +":
114-
return date.plusYears(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
115-
case "Year -":
116-
return date.minusYears(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
117-
default:
118-
return date.format(ISO_DATE_FORMAT_NO_TIME);
119-
}
104+
return switch (switchType) {
105+
case "Day +" -> date.plusDays(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
106+
case "Day -" -> date.minusDays(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
107+
case "Month +" -> date.plusMonths(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
108+
case "Month -" -> date.minusMonths(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
109+
case "Year +" -> date.plusYears(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
110+
case "Year -" -> date.minusYears(dateValue).format(ISO_DATE_FORMAT_NO_TIME);
111+
default -> date.format(ISO_DATE_FORMAT_NO_TIME);
112+
};
120113
}
121114
return null;
122115
}

0 commit comments

Comments
 (0)