├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── scripts
│ └── InstallChrome.sh
└── workflows
│ └── run.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── common
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── cmccarthy
│ │ └── common
│ │ ├── service
│ │ ├── RestService.java
│ │ └── StepDefinitionDataManager.java
│ │ └── utils
│ │ ├── ApplicationProperties.java
│ │ ├── Constants.java
│ │ ├── CustomEventEvaluator.java
│ │ ├── DateTimeUtil.java
│ │ ├── HookUtil.java
│ │ ├── LogManager.java
│ │ └── StringUtil.java
│ └── resources
│ ├── allure.properties
│ ├── application-cloud-provider.properties
│ ├── application-dev.properties
│ ├── application-headless-github.properties
│ ├── application-prod.properties
│ ├── application-uat.properties
│ ├── application.properties
│ ├── demo
│ ├── allure-report.png
│ └── extent-report.jpg
│ ├── downloadDriver.sh
│ ├── extent.properties
│ ├── extent.xml
│ └── logback.xml
├── pom.xml
├── weather
├── pom.xml
└── src
│ └── test
│ ├── java
│ └── com
│ │ └── cmccarthy
│ │ └── api
│ │ ├── WeatherRunnerTest.java
│ │ ├── config
│ │ ├── WeatherAbstractTestDefinition.java
│ │ └── WeatherContextConfiguration.java
│ │ ├── model
│ │ └── response
│ │ │ ├── Clouds.java
│ │ │ ├── Coord.java
│ │ │ ├── LocationWeatherRootResponse.java
│ │ │ ├── Main.java
│ │ │ ├── Sys.java
│ │ │ ├── Weather.java
│ │ │ └── Wind.java
│ │ ├── service
│ │ └── WeatherService.java
│ │ └── step
│ │ ├── Hooks.java
│ │ └── WeatherStep.java
│ └── resources
│ ├── cucumber.properties
│ ├── feature
│ ├── WeatherTest.feature
│ └── WeatherTest2.feature
│ └── suite
│ └── WeatherSuiteTest.xml
└── wikipedia
├── pom.xml
└── src
└── test
├── java
└── com
│ └── cmccarthy
│ └── ui
│ ├── WikipediaParallelRunnerTest.java
│ ├── annotations
│ └── PageObject.java
│ ├── config
│ ├── WikipediaAbstractTestDefinition.java
│ └── WikipediaContextConfiguration.java
│ ├── page
│ ├── AbstractPage.java
│ ├── WikipediaCommonPage.java
│ └── WikipediaHomePage.java
│ ├── step
│ ├── AbstractStep.java
│ ├── CommonPageSteps.java
│ ├── Hooks.java
│ └── WikipediaPageSteps.java
│ └── utils
│ ├── DriverHelper.java
│ ├── DriverManager.java
│ ├── DriverWait.java
│ └── expectedConditions
│ ├── ClickabilityOfElement.java
│ ├── ClickabilityOfElementByLocator.java
│ ├── InvisibilityOfElement.java
│ ├── InvisibilityOfElementByLocator.java
│ ├── VisibilityOfElement.java
│ └── VisibilityOfElementByLocator.java
└── resources
├── cucumber.properties
├── feature
├── WikipediaTest.feature
└── WikipediaTest2.feature
└── suite
└── WikipediaSuiteTest.xml
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/scripts/InstallChrome.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | sudo apt-get install xvfb
3 |
4 | sudo apt-get install unzip
5 |
6 | wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
7 | sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
8 | sudo apt-get update
9 | sudo apt install google-chrome-stable
--------------------------------------------------------------------------------
/.github/workflows/run.yml:
--------------------------------------------------------------------------------
1 | name: run
2 |
3 | on:
4 | pull_request:
5 | branches: [ "master" ]
6 | push:
7 | branches: [ "feature/WebDriverManager" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - name: Set up JDK 17
17 | uses: actions/setup-java@v4
18 | with:
19 | java-version: '17'
20 | distribution: 'temurin'
21 | 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
28 | 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
36 |
37 | - name: Test
38 | run: mvn clean install -DactiveProfile=headless-github
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | screenshots/
2 | *.log
3 | target/
4 | test-output/
5 | .idea
6 | *.iml
7 | .allure
8 | .iml
9 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributor Guidelines
2 |
3 | Thank you for considering contributing to our project! We appreciate your interest in improving our repository and welcome any contributions you may have.
4 |
5 | To ensure a smooth and collaborative experience for everyone, we have outlined some guidelines for contributing to this project. Please take a moment to review these guidelines before making your contribution.
6 |
7 | ## How to Contribute
8 |
9 | 1. **Fork the Repository**: Fork the repository to your own GitHub account.
10 |
11 | 2. **Clone the Repository**: Clone the forked repository to your local machine using the following command:
12 | ```
13 | git clone https://github.com/your-username/repository.git
14 | ```
15 |
16 | 3. **Create a Branch**: Create a new branch to work on your feature or bug fix using a descriptive name:
17 | ```
18 | git checkout -b feature/your-feature
19 | ```
20 |
21 | 4. **Make Changes**: Make your changes within the created branch. Ensure that your code follows the existing code style and conventions.
22 |
23 | 5. **Commit Changes**: Commit your changes with a descriptive commit message:
24 | ```
25 | git commit -am 'Add a brief description of your changes'
26 | ```
27 |
28 | 6. **Push Changes**: Push your changes to your forked repository:
29 | ```
30 | git push origin feature/your-feature
31 | ```
32 |
33 | 7. **Submit a Pull Request (PR)**: Once you have pushed your changes to your forked repository, navigate to the original repository and submit a pull request. Be sure to include a detailed description of the changes you have made.
34 |
35 | 8. **Code Review**: Your pull request will undergo review by the maintainers. Be prepared to address any feedback or suggestions.
36 |
37 | 9. **Merge**: Once your pull request has been approved and all feedback has been addressed, it will be merged into the main branch.
38 |
39 |
40 | ## Reporting Bugs
41 |
42 | If you encounter any bugs or issues while using our project, please report them by opening an issue in the GitHub repository. Include as much detail as possible, including steps to reproduce the issue.
43 |
44 | ## Feature Requests
45 |
46 | We welcome suggestions for new features or improvements. If you have an idea for enhancing our project, please open an issue in the GitHub repository to discuss it.
47 |
48 | ## Additional Resources
49 |
50 | - [GitHub Flow Guide](https://guides.github.com/introduction/flow/)
51 | - [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/)
52 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
53 | - [Markdown Syntax Guide](https://www.markdownguide.org/basic-syntax/)
54 |
55 | Thank you for your contributions!
56 |
57 | *(Please replace "your-username" and "repository" with your actual GitHub username and repository name in the commands above.)*
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 cmccarthyIrl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cucumber Automated Testing Framework
2 |
3 | [](https://github.com/cmccarthyIrl/spring-cucumber-testng-parallel-test-harness/actions/workflows/run.yml)
4 |
5 | # Index
6 |
42 |
43 | # Maven
44 |
45 | The Framework uses [Spring Boot Test](https://spring.io/guides/gs/testing-web/), [Cucumber](https://cucumber.io/)
46 | , [Rest Assured](https://rest-assured.io/) and [Selenium](https://www.selenium.dev/) client implementations.
47 |
48 | Spring ``:
49 |
50 | ```xml
51 |
52 |
53 | ...
54 |
55 | org.springframework.amqp
56 | spring-rabbit
57 | ${spring-rabbit.version}
58 |
59 |
60 | org.springframework.boot
61 | spring-boot-starter-test
62 |
63 |
64 | org.springframework
65 | spring-test
66 |
67 | ...
68 |
69 | ```
70 |
71 | Cucumber & Rest Assured ``:
72 |
73 | ```xml
74 |
75 |
76 | ...
77 |
78 | io.rest-assured
79 | rest-assured
80 | ${restassured.version}
81 |
82 |
83 | io.cucumber
84 | cucumber-java
85 | ${cucumber.version}
86 |
87 |
88 | io.cucumber
89 | cucumber-spring
90 | ${cucumber.version}
91 |
92 |
93 | io.cucumber
94 | cucumber-testng
95 | ${cucumber.version}
96 |
97 | ...
98 |
99 | ```
100 |
101 | Selenium ``:
102 |
103 | ```xml
104 |
105 |
106 | ...
107 |
108 | org.seleniumhq.selenium
109 | selenium-java
110 | ${selenium-version}
111 |
112 |
113 | org.seleniumhq.selenium
114 | selenium-server
115 | ${selenium-version}
116 |
117 | ...
118 |
119 | ```
120 |
121 | # Quickstart
122 |
123 | - [Intellij IDE](https://www.jetbrains.com/idea/) - `Recommended`
124 | - [Java JDK 17](https://jdk.java.net/java-se-ri/11)
125 | - [Apache Maven](https://maven.apache.org/docs/3.6.3/release-notes.html)
126 |
127 | # TestNG
128 |
129 | By using the [TestNG Framework](https://junit.org/junit4/) we can utilize the [Cucumber Framework](https://cucumber.io/)
130 | and the `@CucumberOptions` Annotation Type to execute the `*.feature` file tests
131 |
132 | > Right click the `WikipediParallelRunner` class and select `Run`
133 |
134 | ```java
135 |
136 | @CucumberOptions(
137 | features = {
138 | "src/test/resources/feature"
139 | },
140 | plugin = {
141 | "pretty",
142 | "json:target/cucumber/report.json",
143 | "com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:"
144 | })
145 | public class WikipediaParallelRunnerTest extends AbstractTestNGCucumberTests {
146 |
147 | @Override
148 | @DataProvider(parallel = true)
149 | public Object[][] scenarios() {
150 | return super.scenarios();
151 | }
152 |
153 | }
154 | ```
155 |
156 | # Command Line
157 |
158 | Normally you will use your IDE to run a `*.feature` file directly or via the `*Test.java` class. With the `Test` class,
159 | we can run tests from the command-line as well.
160 |
161 | Note that the `mvn test` command only runs test classes that follow the `*Test.java` naming convention.
162 |
163 | You can run a single test or a suite or tests like so :
164 |
165 | ```
166 | mvn test -Dtest=WikipediaParallelRunnerTest
167 | ```
168 |
169 | Note that the `mvn clean install` command runs all test Classes that follow the `*Test.java` naming convention
170 |
171 | ```
172 | mvn clean install
173 | ```
174 |
175 | # IDE Support
176 |
177 | To minimize the discrepancies between IDE versions and Locales the `` is set to `UTF-8`
178 |
179 | ```xml
180 |
181 |
182 | ...
183 | UTF-8
184 | UTF-8
185 | ...
186 |
187 | ```
188 |
189 | # Java JDK
190 |
191 | The Java version to use is defined in the `maven-compiler-plugin`
192 |
193 | ```xml
194 |
195 |
196 | ...
197 |
198 |
199 | ...
200 |
201 | org.apache.maven.plugins
202 | maven-compiler-plugin
203 |
204 | 17
205 | 17
206 |
207 |
208 | ...
209 |
210 |
211 | ...
212 |
213 | ```
214 |
215 | # Configuration
216 |
217 | The `AbstractTestDefinition` class is responsible for specifying each Step class as `@SpringBootTest` and
218 | its `@ContextConfiguration`
219 |
220 | ```java
221 |
222 | @ContextConfiguration(classes = {FrameworkContextConfiguration.class})
223 | @SpringBootTest
224 | public class AbstractTestDefinition {
225 | }
226 | ```
227 |
228 | The `FrameworkContextConfiguration` class is responsible for specifying the Spring `@Configuration`, modules to scan,
229 | properties to use etc
230 |
231 | ```java
232 |
233 | @EnableRetry
234 | @Configuration
235 | @ComponentScan({
236 | "com.cmccarthy.api", "com.cmccarthy.common",
237 | })
238 | @PropertySource("application.properties")
239 | public class FrameworkContextConfiguration {
240 | }
241 | ```
242 |
243 | # Environment Switching
244 |
245 | There is only one thing you need to do to switch the environment - which is to set `` property in the
246 | Master POM.
247 |
248 | > By default, the value of `spring.profiles.active` is defined in the `application.properties` file which inherits its
249 | > value from the Master POM property ``
250 |
251 | ```xml
252 |
253 |
254 | ...
255 |
256 | prod
257 |
258 | true
259 |
260 |
261 | prod
262 |
263 |
264 | ...
265 |
266 | ```
267 |
268 | You can then specify the profile to use when running Maven from the command line like so:
269 |
270 | ```
271 | mvn clean install -DactiveProfile=github-headless
272 | ```
273 |
274 | Below is an example of the `application.properties` file.
275 |
276 | ```properties
277 | spring.profiles.active=@activatedProperties@
278 | ```
279 |
280 | # Extent Reports
281 |
282 | The Framework uses [Extent Reports Framework](https://extentreports.com/) to generate the HTML Test Reports
283 |
284 | The example below is a report generated automatically by Extent Reports open-source library.
285 |
286 |
287 |
288 | # Allure Reports
289 |
290 | The Framework uses [Allure Reports](https://docs.qameta.io/allure/) to generate the HTML Test Reports
291 |
292 | The example below is a report generated by Allure Reports open-source library.
293 |
294 |
295 |
296 | To generate the above report navigate to the root directory of the module under test and execute the following command
297 |
298 | `mvn allure:serve` or `mvn allure:generate` (for an offline report)
299 |
300 | # Logging
301 |
302 | The Framework uses [Log4j2](https://logging.apache.org/log4j/2.x/) You can instantiate the logging service in any Class
303 | like so
304 |
305 | ```java
306 | private final Logger logger=LoggerFactory.getLogger(WikipediaPageSteps.class);
307 | ```
308 |
309 | you can then use the logger like so :
310 |
311 | ```java
312 | logger.info("This is a info message");
313 | logger.warn("This is a warning message");
314 | logger.debug("This is a info message");
315 | logger.error("This is a error message");
316 | ```
317 |
318 | # Before / After Hooks
319 |
320 | The [Logback](http://logback.qos.ch/) logging service is initialized from the `Hooks.class`
321 |
322 | As the Cucumber Hooks are implemented by all steps we can configure the `@CucumberContextConfiguration` like so :
323 |
324 | ```java
325 |
326 | @CucumberContextConfiguration
327 | public class Hooks extends AbstractTestDefinition {
328 |
329 | private static boolean initialized = false;
330 | private static final Object lock = new Object();
331 |
332 | @Autowired
333 | private HookUtil hookUtil;
334 | @Autowired
335 | private DriverManager driverManager;
336 |
337 | @Before
338 | public void beforeScenario(Scenario scenario) {
339 | synchronized (lock) {
340 | if (!initialized) {
341 | if (!driverManager.isDriverExisting()) {
342 | driverManager.downloadDriver();
343 | }
344 | initialized = true;
345 | }
346 | }
347 | driverManager.createDriver();
348 | }
349 |
350 | @After
351 | public void afterScenario(Scenario scenario) {
352 | hookUtil.endOfTest(scenario);
353 | WebDriverRunner.closeWebDriver();
354 | }
355 | }
356 | ```
357 |
358 | # JSON Transforms
359 |
360 | [Rest Assured IO](https://rest-assured.io/) is used to map the `Response` Objects to their respective `POJO` Classes
361 |
362 | ```xml
363 |
364 |
365 | io.rest-assured
366 | rest-assured
367 | 3.0.0
368 |
369 | ```
370 |
371 | # Troubleshooting
372 |
373 | - Execute the following commands to resolve any dependency issues
374 | 1. `cd ~/install directory path/spring-cucumber-testng-parallel-test-harness`
375 | 2. `mvn clean install -DskipTests`
376 |
377 | # Contributing
378 |
379 | Spotted a mistake? Questions? Suggestions?
380 |
381 | [Open an Issue](https://github.com/cmccarthyIrl/spring-cucumber-testng-parallel-test-harness/issues)
382 |
383 |
384 |
--------------------------------------------------------------------------------
/common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spring-cucumber-testng-parallel-test-harness
7 | spring-cucumber-testng-parallel-test-harness
8 | 1.0-SNAPSHOT
9 | ../pom.xml
10 |
11 |
12 | 4.0.0
13 | 1.0-SNAPSHOT
14 | common
15 |
16 |
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/service/RestService.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.service;
2 |
3 | import io.restassured.specification.RequestSpecification;
4 | import org.springframework.stereotype.Service;
5 |
6 | import static io.restassured.RestAssured.given;
7 |
8 | @Service
9 | public class RestService {
10 |
11 | public RequestSpecification getRequestSpecification() {
12 | return given().header("Content-Type", "application/json");
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/service/StepDefinitionDataManager.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.service;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | @Service
9 | public class StepDefinitionDataManager {
10 |
11 | private final ThreadLocal> storedObjectMap = new ThreadLocal<>();
12 |
13 | public Map getStoredObjectMap() {
14 | return storedObjectMap.get();
15 | }
16 |
17 | public void addToStoredObjectMap(String value, Object object) {
18 | final Map tempObjectMap;
19 | if (getStoredObjectMap() == null) {
20 | tempObjectMap = new HashMap<>();
21 | } else {
22 | tempObjectMap = getStoredObjectMap();
23 | tempObjectMap.remove(value);
24 | }
25 | tempObjectMap.put(value, object);
26 | storedObjectMap.set(tempObjectMap);
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/utils/ApplicationProperties.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.utils;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.stereotype.Component;
5 |
6 | @Component
7 | public class ApplicationProperties {
8 |
9 | @Value("${weather.url.value}")
10 | private String weatherAppUrl;
11 | @Value("${wikipedia.url.value}")
12 | private String wikipediaUrl;
13 | @Value("${browser}")
14 | private String browser;
15 | @Value("${gridUrl}")
16 | private String gridUrl;
17 |
18 | public String getWeatherAppUrl() {
19 | return weatherAppUrl;
20 | }
21 |
22 | public void setWeatherAppUrl(String weatherAppUrl) {
23 | this.weatherAppUrl = weatherAppUrl;
24 | }
25 |
26 | public String getWikipediaUrl() {
27 | return wikipediaUrl;
28 | }
29 |
30 | public void setWikipediaUrl(String wikipediaUrl) {
31 | this.wikipediaUrl = wikipediaUrl;
32 | }
33 |
34 | public String getBrowser() {
35 | return browser;
36 | }
37 |
38 | public void setBrowser(String browser) {
39 | this.browser = browser;
40 | }
41 |
42 | public String getGridUrl() {
43 | return gridUrl;
44 | }
45 | }
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/utils/Constants.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.utils;
2 |
3 | public class Constants {
4 |
5 | public static final long timeoutLong = 30;
6 |
7 | public static final long pollingLong = 200;
8 |
9 | public static final long timeoutShort = 10;
10 |
11 | public static final long pollingShort = 100;
12 |
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 |
17 | }
18 |
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/utils/CustomEventEvaluator.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.utils;
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent;
4 | import ch.qos.logback.core.boolex.EventEvaluatorBase;
5 |
6 | public class CustomEventEvaluator extends EventEvaluatorBase {
7 |
8 | @Override
9 | public boolean evaluate(ILoggingEvent event) {
10 | // Check if logger name contains 'com.cmccarthy'
11 | return event.getLoggerName().contains("com.cmccarthy");
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/utils/DateTimeUtil.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.utils;
2 |
3 | import java.time.*;
4 | import java.time.format.DateTimeFormatter;
5 |
6 | import static java.time.OffsetDateTime.now;
7 |
8 | @SuppressWarnings("unused")
9 | public class DateTimeUtil {
10 |
11 | private static final DateTimeFormatter ISO_DATE_TIME_FORMAT = DateTimeFormatter
12 | .ofPattern("dd/MM/yyyy HH:mm:ss");
13 |
14 | private static final DateTimeFormatter ISO_DATE_FORMAT_NO_TIME = DateTimeFormatter
15 | .ofPattern("dd/MM/yyyy");
16 |
17 | private static final DateTimeFormatter ISO_DATE_FORMAT_LONG_NO_TIME = DateTimeFormatter
18 | .ofPattern("d MMM yyyy");
19 |
20 | private static final DateTimeFormatter ISO_DATE_FORMAT_LONG_MILI = DateTimeFormatter
21 | .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
22 |
23 | private static final DateTimeFormatter ISO_DATE_FORMAT_SHORT_NO_TIME = DateTimeFormatter
24 | .ofPattern("yyyy-MM-dd");
25 |
26 | public static OffsetDateTime getEffectiveDate() {
27 | return getOffsetDateTime(2018, 1, 1);
28 | }
29 |
30 | public static OffsetDateTime getExpirationDate() {
31 | return getOffsetDateTime(3000, 1, 1);
32 | }
33 |
34 | public static String getNowUnixTimestampDate() {
35 | return String.valueOf(System.currentTimeMillis() / 1000L);
36 | }
37 |
38 | public static OffsetDateTime getOffsetDateTime(int year, int month, int dayOfMonth) {
39 | return OffsetDateTime
40 | .of(LocalDate.of(year, month, dayOfMonth), LocalTime.of(0, 0), ZoneOffset.UTC);
41 | }
42 |
43 | public static String localDateNow() {
44 | return OffsetDateTime.of(now().toLocalDateTime(), ZoneOffset.UTC)
45 | .format(ISO_DATE_FORMAT_NO_TIME);
46 | }
47 |
48 | public static OffsetDateTime getDateToday() {
49 | return OffsetDateTime.of(now().toLocalDateTime(), ZoneOffset.UTC);
50 | }
51 |
52 | public static String getLongDateStringFromDateString(String dateString) {
53 | LocalDate date = LocalDate.parse(dateString, ISO_DATE_FORMAT_NO_TIME);
54 | return ISO_DATE_FORMAT_LONG_NO_TIME.format(date);
55 | }
56 |
57 | public static String getShortDateStringFromDateString(String dateString) {
58 | LocalDate date = LocalDate.parse(dateString, ISO_DATE_FORMAT_NO_TIME);
59 | return ISO_DATE_FORMAT_SHORT_NO_TIME.format(date);
60 | }
61 |
62 | public static String getLongDateMiliString() {
63 | return ISO_DATE_FORMAT_LONG_MILI.format(getDateToday());
64 | }
65 |
66 | public static OffsetDateTime localDateTimeNow() {
67 | return OffsetDateTime.of(now().toLocalDateTime(), ZoneOffset.UTC);
68 | }
69 |
70 | /**
71 | * @return - get month date as 01-2020 String
72 | */
73 | public static String getMonthYearNumericalString() {
74 | return now().toString().split("-")[1] + "/" + now().getYear();
75 | }
76 |
77 | /**
78 | * @return next week day as dd/MM/yyyy
79 | */
80 | public static String getNextDayOfWeekNumericalFormat() {
81 | LocalDateTime date = LocalDateTime.now();
82 | do {
83 | date = date.plusDays(1);
84 | } while (date.getDayOfWeek().getValue() >= 5);
85 | return date.format(ISO_DATE_FORMAT_NO_TIME);
86 | }
87 |
88 | public static String featureDateManager(String tableDate) {
89 | if (tableDate != null) {
90 |
91 | final String[] dateTableData = tableDate.split("\\s+");
92 | String switchType;
93 | int dateValue = 0;
94 |
95 | if (dateTableData.length > 1) {
96 | switchType = dateTableData[0] + " " + dateTableData[1];
97 | dateValue = Integer.parseInt(dateTableData[2]);
98 | } else {
99 | switchType = "Day";
100 | }
101 |
102 | final LocalDateTime date = LocalDateTime.now();
103 |
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 | }
120 | }
121 | return null;
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/utils/HookUtil.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.utils;
2 |
3 | import io.cucumber.java.Scenario;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Service;
6 |
7 | import java.nio.charset.StandardCharsets;
8 |
9 | @Service
10 | public class HookUtil {
11 |
12 | @Autowired
13 | LogManager logManager;
14 |
15 | public void endOfTest(Scenario scenario) {
16 |
17 | if (scenario.getStatus() != null) {
18 | if (scenario.isFailed()) {
19 | String filename = scenario.getName().replaceAll("\\s+", "_");
20 | final String featureError = scenario.getId().replaceAll("\\s+", "_").replaceAll(":", "_").split("\\.")[1];
21 | filename = filename + "_" + featureError;
22 | scenario.attach(filename.getBytes(StandardCharsets.UTF_8), "image/png", filename);
23 | }
24 | }
25 |
26 | logManager.info("");
27 | logManager.info("==========================================================================");
28 | logManager.info("================================Test " + scenario.getStatus().toString() + "===============================");
29 | logManager.info("==========================================================================");
30 | logManager.info("");
31 | }
32 |
33 |
34 | }
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/utils/LogManager.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.utils;
2 |
3 | import ch.qos.logback.classic.Level;
4 | import ch.qos.logback.classic.Logger;
5 | import ch.qos.logback.classic.LoggerContext;
6 | import ch.qos.logback.classic.PatternLayout;
7 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
8 | import ch.qos.logback.classic.spi.ILoggingEvent;
9 | import ch.qos.logback.core.ConsoleAppender;
10 | import ch.qos.logback.core.FileAppender;
11 | import ch.qos.logback.core.filter.EvaluatorFilter;
12 | import ch.qos.logback.core.pattern.color.ANSIConstants;
13 | import ch.qos.logback.core.pattern.color.ForegroundCompositeConverterBase;
14 | import org.slf4j.LoggerFactory;
15 | import org.springframework.stereotype.Service;
16 |
17 | @Service
18 | public class LogManager extends ForegroundCompositeConverterBase {
19 |
20 | @Override
21 | protected String getForegroundColorCode(ILoggingEvent event) {
22 | Level level = event.getLevel();
23 | return switch (level.toInt()) {
24 | case Level.ERROR_INT -> ANSIConstants.BOLD + ANSIConstants.RED_FG;
25 | case Level.WARN_INT -> ANSIConstants.RED_FG;
26 | case Level.INFO_INT -> ANSIConstants.CYAN_FG;
27 | default -> ANSIConstants.DEFAULT_FG;
28 | };
29 | }
30 |
31 | private final static ThreadLocal logFactory = new ThreadLocal<>();
32 |
33 | public void createNewLogger(String className) {
34 | LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
35 |
36 | // Use the custom evaluator instead of JaninoEventEvaluator
37 | CustomEventEvaluator customEventEvaluator = new CustomEventEvaluator();
38 | customEventEvaluator.setContext(loggerContext);
39 |
40 | EvaluatorFilter evaluatorFilter = new EvaluatorFilter<>();
41 | evaluatorFilter.setEvaluator(customEventEvaluator);
42 |
43 | FileAppender fileAppender = fileAppender(className, loggerContext, evaluatorFilter);
44 | fileAppender.start();
45 |
46 | ConsoleAppender consoleAppender = consoleAppender(loggerContext, evaluatorFilter);
47 | consoleAppender.start();
48 |
49 | Logger logger = (Logger) LoggerFactory.getLogger(className);
50 | logger.addAppender(fileAppender);
51 | logger.addAppender(consoleAppender);
52 |
53 | logger.setLevel(Level.DEBUG);
54 | logger.setAdditive(false);
55 | logFactory.set(logger);
56 | }
57 |
58 | private FileAppender fileAppender(String className, LoggerContext loggerContext, EvaluatorFilter evaluatorFilter) {
59 | PatternLayout filePattern = new PatternLayout();
60 | filePattern.setContext(loggerContext);
61 | filePattern.setPattern("[%d{ISO8601}] %-5level [%logger{100}]: %msg%n%throwable");
62 | filePattern.start();
63 |
64 | PatternLayoutEncoder encoder = new PatternLayoutEncoder();
65 | encoder.setPattern(filePattern.getPattern());
66 | encoder.setContext(loggerContext);
67 | encoder.start();
68 |
69 | FileAppender fileAppender = new FileAppender<>();
70 | fileAppender.setFile("logs/" + className + ".log");
71 | fileAppender.setAppend(false);
72 | fileAppender.setEncoder(encoder);
73 | fileAppender.setContext(loggerContext);
74 | fileAppender.addFilter(evaluatorFilter);
75 |
76 | return fileAppender;
77 | }
78 |
79 | private ConsoleAppender consoleAppender(LoggerContext loggerContext, EvaluatorFilter evaluatorFilter) {
80 | PatternLayout consolePattern = new PatternLayout();
81 | consolePattern.setContext(loggerContext);
82 | consolePattern.setPattern("%blue([%d{ISO8601}]) %highlight(%colourPicker(%-5level)) %blue([%logger{100}]:) %colourPicker(%msg%n%throwable)");
83 | consolePattern.start();
84 |
85 | PatternLayoutEncoder consoleEncoder = new PatternLayoutEncoder();
86 | consoleEncoder.setPattern(consolePattern.getPattern());
87 | consoleEncoder.setContext(loggerContext);
88 | consoleEncoder.start();
89 |
90 | ConsoleAppender consoleAppender = new ConsoleAppender<>();
91 | consoleAppender.setEncoder(consoleEncoder);
92 | consoleAppender.setContext(loggerContext);
93 | consoleAppender.addFilter(evaluatorFilter);
94 |
95 | return consoleAppender;
96 | }
97 |
98 | private static Logger getLogger() {
99 | return logFactory.get();
100 | }
101 |
102 | public void info(String value) {
103 | getLogger().info(value);
104 | }
105 |
106 | public void debug(String value) {
107 | getLogger().debug(value);
108 | }
109 |
110 | public void warn(String value) {
111 | getLogger().warn(value);
112 | }
113 |
114 | public void error(String value) {
115 | getLogger().error(value);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/common/src/main/java/com/cmccarthy/common/utils/StringUtil.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.common.utils;
2 |
3 | import java.util.NoSuchElementException;
4 | import java.util.Random;
5 | import java.util.concurrent.ThreadLocalRandom;
6 |
7 | import static org.apache.commons.lang3.RandomStringUtils.random;
8 |
9 | @SuppressWarnings("unused")
10 | public class StringUtil {
11 |
12 | private static final Random random = new Random();
13 |
14 | public static boolean getRandomBoolean() {
15 | return random.nextBoolean();
16 | }
17 |
18 | public static int getRandomNumber(int min, int max) {
19 | if (min > max) {
20 | throw new IllegalArgumentException(
21 | "Min (" + min + ") should not be larger than Max (" + max + ")");
22 | }
23 | return ThreadLocalRandom.current().nextInt(min, max + 1);
24 | }
25 |
26 | public static String getRandomAlphaString(int length) {
27 | return random(length, true, false);
28 | }
29 |
30 | public static String getRandomAlphaString(int min, int max) {
31 | return random(getRandomNumber(min, max), true, false);
32 | }
33 |
34 | public static String getRandomNumericString(int length, int min, int max) {
35 | return random(length, min, max, false, true);
36 | }
37 |
38 | public static String getRandomNumericString(int min, int max) {
39 | return random(getRandomNumber(min, max), false, true);
40 | }
41 |
42 | public static String getRandomAlphaNumericString(int length) {
43 | return random(length, true, true);
44 | }
45 |
46 | public static String getRandomAlphaNumericString(int min, int max) {
47 | return random(getRandomNumber(min, max), true, true);
48 | }
49 |
50 | public static String getRandomAmount(String min, String max) {
51 | if (Double.parseDouble(min) > Double.parseDouble(max)) {
52 | throw new IllegalArgumentException(
53 | ": Min (" + min + ") should not be larger than Max (" + max + ")");
54 | }
55 | return String.format("%.2f", ThreadLocalRandom.current()
56 | .nextDouble(Double.parseDouble(min), Double.parseDouble(max) + 0.01));
57 | }
58 |
59 | public static double getRandomAmount(double min, double max) {
60 | if (min > max) {
61 | throw new IllegalArgumentException(
62 | ": Min (" + min + ") should not be larger than Max (" + max + ")");
63 | }
64 | return Double.parseDouble(
65 | String.format("%.2f", ThreadLocalRandom.current().nextDouble(min, max + 0.01)));
66 | }
67 |
68 | public static String featureStringManager(String tableValue) {
69 | if (tableValue != null) {
70 | final String[] dateTableData = tableValue.split("\\s+");
71 |
72 | final String switchType = dateTableData[0];
73 |
74 | final int length = Integer.parseInt(dateTableData[1]);
75 |
76 | switch (switchType) {
77 | case "StringInteger":
78 | return getRandomAlphaNumericString(length);
79 | case "String":
80 | return getRandomAlphaString(length);
81 | case "Integer":
82 | return getRandomNumericString(length, 2, 66);
83 | default:
84 | throw new NoSuchElementException("Could not create a String of type : " + switchType);
85 | }
86 | } else {
87 | return null;
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/common/src/main/resources/allure.properties:
--------------------------------------------------------------------------------
1 | allure.results.directory=target/allure-results
2 | allure.link.issue.pattern=https://example.org/issue/{}
3 | allure.link.tms.pattern=https://example.org/tms/{}
--------------------------------------------------------------------------------
/common/src/main/resources/application-cloud-provider.properties:
--------------------------------------------------------------------------------
1 | weather.url.value=http://api.openweathermap.org/data/2.5/weather
2 | wikipedia.url.value=https://www.wikipedia.org/
3 | browser=chrome
4 | gridUrl=http://selenium_hub:4444/wd/hub
5 |
--------------------------------------------------------------------------------
/common/src/main/resources/application-dev.properties:
--------------------------------------------------------------------------------
1 | weather.url.value=http://api.openweathermap.org/data/2.5/weather
2 | wikipedia.url.value=https://www.wikipedia.org/
3 | browser=chrome
--------------------------------------------------------------------------------
/common/src/main/resources/application-headless-github.properties:
--------------------------------------------------------------------------------
1 | weather.url.value=http://api.openweathermap.org/data/2.5/weather
2 | wikipedia.url.value=https://www.wikipedia.org/
3 | browser=chrome
4 | github=true
5 |
--------------------------------------------------------------------------------
/common/src/main/resources/application-prod.properties:
--------------------------------------------------------------------------------
1 | weather.url.value=http://api.openweathermap.org/data/2.5/weather
2 | wikipedia.url.value=https://www.wikipedia.org/
3 | browser=chrome
--------------------------------------------------------------------------------
/common/src/main/resources/application-uat.properties:
--------------------------------------------------------------------------------
1 | weather.url.value=http://api.openweathermap.org/data/2.5/weather
2 | wikipedia.url.value=https://www.wikipedia.org/
3 | browser=opera
--------------------------------------------------------------------------------
/common/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.profiles.active=@activeProfile@
--------------------------------------------------------------------------------
/common/src/main/resources/demo/allure-report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmccarthyIrl/spring-cucumber-testng-parallel-test-harness/9656b0e42f8ea2221f3185d3086d49cfe2fad2ad/common/src/main/resources/demo/allure-report.png
--------------------------------------------------------------------------------
/common/src/main/resources/demo/extent-report.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmccarthyIrl/spring-cucumber-testng-parallel-test-harness/9656b0e42f8ea2221f3185d3086d49cfe2fad2ad/common/src/main/resources/demo/extent-report.jpg
--------------------------------------------------------------------------------
/common/src/main/resources/downloadDriver.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 | export SCRIPT_DIR=../../../../wikipedia/src/test/resources
5 | cd $SCRIPT_DIR &&
6 |
7 | ROOT_DIR=$(pwd)
8 | TEST_RESOURCES=$ROOT_DIR
9 | FILE_EXTENSION=""
10 |
11 | echo $TEST_RESOURCES
12 |
13 | case "$OSTYPE" in
14 | solaris*) echo "$OSTYPE not supported" ;;
15 | darwin*) OS='mac64' ;;
16 | linux*) OS='linux64' ;;
17 | bsd*) echo "$OSTYPE not supported" ;;
18 | msys*) OS='win32' FILE_EXTENSION=".exe" ;;
19 | *) echo "unknown $OSTYPE" ;;
20 | esac
21 |
22 | if [ ! -f "$TEST_RESOURCES/drivers/chromedriver"$FILE_EXTENSION ]; then
23 | cd "$TEST_RESOURCES/drivers" &&
24 | mkdir "temp" &&
25 | cd "temp" &&
26 | curl -L -k --output driver.zip https://www.nuget.org/api/v2/package/Selenium.WebDriver.ChromeDriver/ --ssl-no-revoke &&
27 | unzip driver.zip &&
28 | cd driver/$OS &&
29 | cp "chromedriver$FILE_EXTENSION" "$TEST_RESOURCES/drivers" &&
30 | chmod +700 "$TEST_RESOURCES/drivers/chromedriver"$FILE_EXTENSION &&
31 | cd ../../../ &&
32 | rm -rf temp &&
33 | cd $ROOT_DIR
34 | fi
35 |
36 | if [ ! -f "$TEST_RESOURCES/drivers/geckodriver"$FILE_EXTENSION ]; then
37 | cd "$TEST_RESOURCES/drivers" &&
38 | mkdir "temp" &&
39 | cd "temp" &&
40 | curl -L -k --output driver.zip https://www.nuget.org/api/v2/package/Selenium.WebDriver.GeckoDriver/ --ssl-no-revoke &&
41 | unzip driver.zip &&
42 | cd driver/$OS &&
43 | cp "geckodriver$FILE_EXTENSION" "$TEST_RESOURCES/drivers" &&
44 | chmod +700 "$TEST_RESOURCES/drivers/geckodriver"$FILE_EXTENSION &&
45 | cd ../../../ &&
46 | rm -rf temp &&
47 | cd $ROOT_DIR
48 | fi
49 |
50 | #uncomment to keep bash open
51 | #! /bin/bash
52 |
--------------------------------------------------------------------------------
/common/src/main/resources/extent.properties:
--------------------------------------------------------------------------------
1 | extent.reporter.spark.start=true
2 | extent.reporter.spark.config=../common/src/main/resources/extent.xml
3 | extent.reporter.spark.out=target/cucumber/report.html
4 | screenshot.dir=target/
5 | screenshot.rel.path=./
--------------------------------------------------------------------------------
/common/src/main/resources/extent.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test Framework
7 |
8 |
9 |
10 | UTF-8
11 |
12 |
13 |
14 | http
15 |
16 |
17 | Test Framework
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 | dark
37 |
38 |
39 | MMM dd, yyyy HH:mm:ss
40 |
41 |
42 |
--------------------------------------------------------------------------------
/common/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | spring-cucumber-testng-parallel-test-harness
8 | spring-cucumber-testng-parallel-test-harness
9 | pom
10 | 1.0-SNAPSHOT
11 |
12 |
13 |
14 | cmccarthy
15 | cmccarthy
16 | https://github.com/cmccarthyIrl
17 |
18 |
19 |
20 |
21 |
22 | MIT License
23 | http://www.opensource.org/licenses/mit-license.php
24 | repo
25 |
26 |
27 |
28 |
29 | common
30 | weather
31 | wikipedia
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-parent
37 | 3.5.0
38 |
39 |
40 |
41 |
42 |
43 |
44 | org.springframework.boot
45 | spring-boot-dependencies
46 | 3.5.0
47 | pom
48 | import
49 |
50 |
51 |
52 |
53 |
54 | headless-github
55 | 17
56 | UTF-8
57 | UTF-8
58 | 20250517
59 | 5.5.0
60 | 7.22.0
61 | 7.22.0
62 | 7.22.0
63 | 1.5.13
64 | 32.1.1
65 | 2.15.1
66 | 3.23.1
67 | 1.14.0
68 | 2.29.1
69 | 4.33.0
70 | 1.7.0
71 | 3.1.2
72 | 3.2.3
73 |
74 |
75 |
76 |
77 | org.json
78 | json
79 | ${json.version}
80 |
81 |
82 |
83 |
84 | io.rest-assured
85 | rest-assured
86 | ${rest-assured.version}
87 |
88 |
89 | io.cucumber
90 | cucumber-java
91 | ${cucumber-java.version}
92 |
93 |
94 | io.cucumber
95 | cucumber-spring
96 | ${cucumber-spring.version}
97 |
98 |
99 | io.cucumber
100 | cucumber-testng
101 | ${cucumber-testng.version}
102 |
103 |
104 | io.cucumber
105 | gherkin
106 | ${gherkin.version}
107 |
108 |
109 |
110 |
111 | commons-io
112 | commons-io
113 | ${commons-io.version}
114 |
115 |
116 | org.assertj
117 | assertj-core
118 | ${assertj-core.version}
119 |
120 |
121 | tech.grasshopper
122 | extentreports-cucumber7-adapter
123 | ${extentreports-cucumber7-adapter.version}
124 |
125 |
126 | io.qameta.allure
127 | allure-cucumber7-jvm
128 | ${allure-cucumber7-jvm.version}
129 |
130 |
131 | ch.qos.logback
132 | logback-classic
133 | ${logback.version}
134 |
135 |
136 | ch.qos.logback
137 | logback-core
138 | ${logback.version}
139 |
140 |
141 |
142 |
143 | org.springframework.amqp
144 | spring-rabbit
145 |
146 |
147 | org.springframework.boot
148 | spring-boot-starter-web
149 |
150 |
151 | org.springframework.boot
152 | spring-boot-starter-test
153 | test
154 |
155 |
156 | com.vaadin.external.google
157 | android-json
158 |
159 |
160 | logback-classic
161 | ch.qos.logback
162 |
163 |
164 |
165 |
166 | org.springframework
167 | spring-test
168 |
169 |
170 | org.springframework.boot
171 | spring-boot-starter-aop
172 |
173 |
174 |
175 |
176 | net.sf.jtidy
177 | jtidy
178 | r938
179 |
180 |
181 |
182 |
183 |
184 |
185 | org.apache.maven.plugins
186 | maven-compiler-plugin
187 | ${maven-compiler-plugin.version}
188 |
189 | ${java.version}
190 | ${java.version}
191 |
192 |
193 |
194 | org.apache.maven.plugins
195 | maven-jar-plugin
196 |
197 |
198 |
199 | test-jar
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | dev
212 |
213 | dev
214 |
215 |
216 |
217 | prod
218 |
219 | prod
220 |
221 |
222 |
223 | uat
224 |
225 | uat
226 |
227 |
228 |
229 | cloud-provider
230 |
231 | cloud-provider
232 |
233 |
234 |
235 | headless-github
236 |
237 | headless-github
238 |
239 |
240 |
241 |
242 |
--------------------------------------------------------------------------------
/weather/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spring-cucumber-testng-parallel-test-harness
7 | spring-cucumber-testng-parallel-test-harness
8 | 1.0-SNAPSHOT
9 | ../pom.xml
10 |
11 | 4.0.0
12 |
13 | weather
14 |
15 |
16 |
17 | spring-cucumber-testng-parallel-test-harness
18 | common
19 | 1.0-SNAPSHOT
20 |
21 |
22 |
23 |
24 |
25 |
26 | org.apache.maven.plugins
27 | maven-surefire-plugin
28 | ${maven.surefire.plugin.version}
29 |
30 |
31 | src/test/resources/suite/WeatherSuiteTest.xml
32 |
33 |
34 |
35 |
36 | org.apache.maven.surefire
37 | surefire-testng
38 | ${surefire.testng.version}
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/WeatherRunnerTest.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api;
2 |
3 | import io.cucumber.testng.AbstractTestNGCucumberTests;
4 | import io.cucumber.testng.CucumberOptions;
5 | import org.testng.annotations.DataProvider;
6 |
7 | @CucumberOptions(
8 | features = {
9 | "src/test/resources/feature/WeatherTest.feature"
10 | },
11 | plugin = {
12 | "com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:",
13 | "io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm"
14 | })
15 | public class WeatherRunnerTest extends AbstractTestNGCucumberTests {
16 | @Override
17 | @DataProvider(parallel = true)
18 | public Object[][] scenarios() {
19 | return super.scenarios();
20 | }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/config/WeatherAbstractTestDefinition.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.config;
2 |
3 | import org.springframework.boot.test.context.SpringBootTest;
4 | import org.springframework.test.context.ContextConfiguration;
5 |
6 | @ContextConfiguration(classes = {WeatherContextConfiguration.class})
7 | @SpringBootTest
8 | public class WeatherAbstractTestDefinition {
9 | }
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/config/WeatherContextConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.config;
2 |
3 | import org.springframework.context.annotation.ComponentScan;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.context.annotation.PropertySource;
6 | import org.springframework.retry.annotation.EnableRetry;
7 |
8 | @EnableRetry
9 | @Configuration
10 | @ComponentScan({
11 | "com.cmccarthy.api",
12 | "com.cmccarthy.common"
13 | })
14 | @PropertySource("classpath:/application.properties")
15 | public class WeatherContextConfiguration {
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/model/response/Clouds.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.model.response;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder;
6 |
7 | @JsonInclude(JsonInclude.Include.NON_NULL)
8 | @JsonPropertyOrder({
9 | "all"
10 | })
11 | public class Clouds {
12 |
13 | @JsonProperty("all")
14 | private Integer all;
15 |
16 | @JsonProperty("all")
17 | public Integer getAll() {
18 | return all;
19 | }
20 |
21 | @JsonProperty("all")
22 | public void setAll(Integer all) {
23 | this.all = all;
24 | }
25 | }
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/model/response/Coord.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.model.response;
2 |
3 | import com.fasterxml.jackson.annotation.*;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | @JsonInclude(JsonInclude.Include.NON_NULL)
9 | @JsonPropertyOrder({
10 | "lon",
11 | "lat"
12 | })
13 | public class Coord {
14 |
15 | @JsonProperty("lon")
16 | private Double lon;
17 | @JsonProperty("lat")
18 | private Double lat;
19 | @JsonIgnore
20 | private Map additionalProperties = new HashMap();
21 |
22 | @JsonProperty("lon")
23 | public Double getLon() {
24 | return lon;
25 | }
26 |
27 | @JsonProperty("lon")
28 | public void setLon(Double lon) {
29 | this.lon = lon;
30 | }
31 |
32 | @JsonProperty("lat")
33 | public Double getLat() {
34 | return lat;
35 | }
36 |
37 | @JsonProperty("lat")
38 | public void setLat(Double lat) {
39 | this.lat = lat;
40 | }
41 |
42 | @JsonAnyGetter
43 | public Map getAdditionalProperties() {
44 | return this.additionalProperties;
45 | }
46 |
47 | @JsonAnySetter
48 | public void setAdditionalProperty(String name, Object value) {
49 | this.additionalProperties.put(name, value);
50 | }
51 | }
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/model/response/LocationWeatherRootResponse.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.model.response;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 |
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | @JsonIgnoreProperties(ignoreUnknown = true)
10 | public class LocationWeatherRootResponse {
11 |
12 | private Coord coord;
13 | private List weather = null;
14 | private String base;
15 | private Main main;
16 | private Integer visibility;
17 | private Wind wind;
18 | private Clouds clouds;
19 | private Integer dt;
20 | private Sys sys;
21 | private Integer timezone;
22 | private Integer id;
23 | private String name;
24 | private Integer cod;
25 | private Map additionalProperties = new HashMap();
26 |
27 | public Coord getCoord() {
28 | return coord;
29 | }
30 |
31 | public void setCoord(Coord coord) {
32 | this.coord = coord;
33 | }
34 |
35 | public List getWeather() {
36 | return weather;
37 | }
38 |
39 | public void setWeather(List weather) {
40 | this.weather = weather;
41 | }
42 |
43 | public String getBase() {
44 | return base;
45 | }
46 |
47 | public void setBase(String base) {
48 | this.base = base;
49 | }
50 |
51 | public Main getMain() {
52 | return main;
53 | }
54 |
55 | public void setMain(Main main) {
56 | this.main = main;
57 | }
58 |
59 | public Integer getVisibility() {
60 | return visibility;
61 | }
62 |
63 | public void setVisibility(Integer visibility) {
64 | this.visibility = visibility;
65 | }
66 |
67 | public Wind getWind() {
68 | return wind;
69 | }
70 |
71 | public void setWind(Wind wind) {
72 | this.wind = wind;
73 | }
74 |
75 | public Clouds getClouds() {
76 | return clouds;
77 | }
78 |
79 | public void setClouds(Clouds clouds) {
80 | this.clouds = clouds;
81 | }
82 |
83 | public Integer getDt() {
84 | return dt;
85 | }
86 |
87 | public void setDt(Integer dt) {
88 | this.dt = dt;
89 | }
90 |
91 | public Sys getSys() {
92 | return sys;
93 | }
94 |
95 | public void setSys(Sys sys) {
96 | this.sys = sys;
97 | }
98 |
99 | public Integer getTimezone() {
100 | return timezone;
101 | }
102 |
103 | public void setTimezone(Integer timezone) {
104 | this.timezone = timezone;
105 | }
106 |
107 | public Integer getId() {
108 | return id;
109 | }
110 |
111 | public void setId(Integer id) {
112 | this.id = id;
113 | }
114 |
115 | public String getName() {
116 | return name;
117 | }
118 |
119 | public void setName(String name) {
120 | this.name = name;
121 | }
122 |
123 | public Integer getCod() {
124 | return cod;
125 | }
126 |
127 | public void setCod(Integer cod) {
128 | this.cod = cod;
129 | }
130 |
131 | public Map getAdditionalProperties() {
132 | return this.additionalProperties;
133 | }
134 |
135 | public void setAdditionalProperty(String name, Object value) {
136 | this.additionalProperties.put(name, value);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/model/response/Main.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.model.response;
2 |
3 | import com.fasterxml.jackson.annotation.*;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | @JsonInclude(JsonInclude.Include.NON_NULL)
9 | @JsonPropertyOrder({
10 | "temp",
11 | "feels_like",
12 | "temp_min",
13 | "temp_max",
14 | "pressure",
15 | "humidity"
16 | })
17 | public class Main {
18 |
19 | @JsonProperty("temp")
20 | private Double temp;
21 | @JsonProperty("feels_like")
22 | private Double feelsLike;
23 | @JsonProperty("temp_min")
24 | private Double tempMin;
25 | @JsonProperty("temp_max")
26 | private Double tempMax;
27 | @JsonProperty("pressure")
28 | private Integer pressure;
29 | @JsonProperty("humidity")
30 | private Integer humidity;
31 | @JsonIgnore
32 | private Map additionalProperties = new HashMap();
33 |
34 | @JsonProperty("temp")
35 | public Double getTemp() {
36 | return temp;
37 | }
38 |
39 | @JsonProperty("temp")
40 | public void setTemp(Double temp) {
41 | this.temp = temp;
42 | }
43 |
44 | @JsonProperty("feels_like")
45 | public Double getFeelsLike() {
46 | return feelsLike;
47 | }
48 |
49 | @JsonProperty("feels_like")
50 | public void setFeelsLike(Double feelsLike) {
51 | this.feelsLike = feelsLike;
52 | }
53 |
54 | @JsonProperty("temp_min")
55 | public Double getTempMin() {
56 | return tempMin;
57 | }
58 |
59 | @JsonProperty("temp_min")
60 | public void setTempMin(Double tempMin) {
61 | this.tempMin = tempMin;
62 | }
63 |
64 | @JsonProperty("temp_max")
65 | public Double getTempMax() {
66 | return tempMax;
67 | }
68 |
69 | @JsonProperty("temp_max")
70 | public void setTempMax(Double tempMax) {
71 | this.tempMax = tempMax;
72 | }
73 |
74 | @JsonProperty("pressure")
75 | public Integer getPressure() {
76 | return pressure;
77 | }
78 |
79 | @JsonProperty("pressure")
80 | public void setPressure(Integer pressure) {
81 | this.pressure = pressure;
82 | }
83 |
84 | @JsonProperty("humidity")
85 | public Integer getHumidity() {
86 | return humidity;
87 | }
88 |
89 | @JsonProperty("humidity")
90 | public void setHumidity(Integer humidity) {
91 | this.humidity = humidity;
92 | }
93 |
94 | @JsonAnyGetter
95 | public Map getAdditionalProperties() {
96 | return this.additionalProperties;
97 | }
98 |
99 | @JsonAnySetter
100 | public void setAdditionalProperty(String name, Object value) {
101 | this.additionalProperties.put(name, value);
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/model/response/Sys.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.model.response;
2 |
3 | import com.fasterxml.jackson.annotation.*;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | @JsonInclude(JsonInclude.Include.NON_NULL)
9 | @JsonPropertyOrder({
10 | "type",
11 | "id",
12 | "country",
13 | "sunrise",
14 | "sunset"
15 | })
16 | public class Sys {
17 |
18 | @JsonProperty("type")
19 | private Integer type;
20 | @JsonProperty("id")
21 | private Integer id;
22 | @JsonProperty("country")
23 | private String country;
24 | @JsonProperty("sunrise")
25 | private Integer sunrise;
26 | @JsonProperty("sunset")
27 | private Integer sunset;
28 | @JsonIgnore
29 | private Map additionalProperties = new HashMap();
30 |
31 | @JsonProperty("type")
32 | public Integer getType() {
33 | return type;
34 | }
35 |
36 | @JsonProperty("type")
37 | public void setType(Integer type) {
38 | this.type = type;
39 | }
40 |
41 | @JsonProperty("id")
42 | public Integer getId() {
43 | return id;
44 | }
45 |
46 | @JsonProperty("id")
47 | public void setId(Integer id) {
48 | this.id = id;
49 | }
50 |
51 | @JsonProperty("country")
52 | public String getCountry() {
53 | return country;
54 | }
55 |
56 | @JsonProperty("country")
57 | public void setCountry(String country) {
58 | this.country = country;
59 | }
60 |
61 | @JsonProperty("sunrise")
62 | public Integer getSunrise() {
63 | return sunrise;
64 | }
65 |
66 | @JsonProperty("sunrise")
67 | public void setSunrise(Integer sunrise) {
68 | this.sunrise = sunrise;
69 | }
70 |
71 | @JsonProperty("sunset")
72 | public Integer getSunset() {
73 | return sunset;
74 | }
75 |
76 | @JsonProperty("sunset")
77 | public void setSunset(Integer sunset) {
78 | this.sunset = sunset;
79 | }
80 |
81 | @JsonAnyGetter
82 | public Map getAdditionalProperties() {
83 | return this.additionalProperties;
84 | }
85 |
86 | @JsonAnySetter
87 | public void setAdditionalProperty(String name, Object value) {
88 | this.additionalProperties.put(name, value);
89 | }
90 |
91 | }
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/model/response/Weather.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.model.response;
2 |
3 | import com.fasterxml.jackson.annotation.*;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | @JsonInclude(JsonInclude.Include.NON_NULL)
9 | @JsonPropertyOrder({
10 | "id",
11 | "main",
12 | "description",
13 | "icon"
14 | })
15 | public class Weather {
16 |
17 | @JsonProperty("id")
18 | private Integer id;
19 | @JsonProperty("main")
20 | private String main;
21 | @JsonProperty("description")
22 | private String description;
23 | @JsonProperty("icon")
24 | private String icon;
25 | @JsonIgnore
26 | private Map additionalProperties = new HashMap();
27 |
28 | @JsonProperty("id")
29 | public Integer getId() {
30 | return id;
31 | }
32 |
33 | @JsonProperty("id")
34 | public void setId(Integer id) {
35 | this.id = id;
36 | }
37 |
38 | @JsonProperty("main")
39 | public String getMain() {
40 | return main;
41 | }
42 |
43 | @JsonProperty("main")
44 | public void setMain(String main) {
45 | this.main = main;
46 | }
47 |
48 | @JsonProperty("description")
49 | public String getDescription() {
50 | return description;
51 | }
52 |
53 | @JsonProperty("description")
54 | public void setDescription(String description) {
55 | this.description = description;
56 | }
57 |
58 | @JsonProperty("icon")
59 | public String getIcon() {
60 | return icon;
61 | }
62 |
63 | @JsonProperty("icon")
64 | public void setIcon(String icon) {
65 | this.icon = icon;
66 | }
67 |
68 | @JsonAnyGetter
69 | public Map getAdditionalProperties() {
70 | return this.additionalProperties;
71 | }
72 |
73 | @JsonAnySetter
74 | public void setAdditionalProperty(String name, Object value) {
75 | this.additionalProperties.put(name, value);
76 | }
77 |
78 | }
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/model/response/Wind.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.model.response;
2 |
3 | import com.fasterxml.jackson.annotation.*;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | @JsonInclude(JsonInclude.Include.NON_NULL)
9 | @JsonPropertyOrder({
10 | "speed",
11 | "deg"
12 | })
13 | public class Wind {
14 |
15 | @JsonProperty("speed")
16 | private Double speed;
17 | @JsonProperty("deg")
18 | private Integer deg;
19 | @JsonIgnore
20 | private Map additionalProperties = new HashMap();
21 |
22 | @JsonProperty("speed")
23 | public Double getSpeed() {
24 | return speed;
25 | }
26 |
27 | @JsonProperty("speed")
28 | public void setSpeed(Double speed) {
29 | this.speed = speed;
30 | }
31 |
32 | @JsonProperty("deg")
33 | public Integer getDeg() {
34 | return deg;
35 | }
36 |
37 | @JsonProperty("deg")
38 | public void setDeg(Integer deg) {
39 | this.deg = deg;
40 | }
41 |
42 | @JsonAnyGetter
43 | public Map getAdditionalProperties() {
44 | return this.additionalProperties;
45 | }
46 |
47 | @JsonAnySetter
48 | public void setAdditionalProperty(String name, Object value) {
49 | this.additionalProperties.put(name, value);
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/service/WeatherService.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.service;
2 |
3 | import com.cmccarthy.common.service.RestService;
4 | import com.cmccarthy.common.service.StepDefinitionDataManager;
5 | import com.cmccarthy.common.utils.ApplicationProperties;
6 | import com.cmccarthy.common.utils.LogManager;
7 | import io.restassured.response.Response;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.net.HttpURLConnection;
12 | import java.util.NoSuchElementException;
13 |
14 | @Service
15 | public class WeatherService {
16 | @Autowired
17 | private RestService restService;
18 | @Autowired
19 | private LogManager logManager;
20 | @Autowired
21 | private StepDefinitionDataManager stepDefinitionDataManager;
22 | @Autowired
23 | private ApplicationProperties applicationProperties;
24 |
25 | public void getWeatherForLocation(String location) {
26 | Response response = restService.getRequestSpecification()
27 | .param("q", location)
28 | .param("appid", "0a1b11f110d4b6cd43181d23d724cb94")
29 | .get(applicationProperties.getWeatherAppUrl());
30 |
31 | stepDefinitionDataManager.addToStoredObjectMap("class", response);
32 |
33 | if (response.statusCode() != HttpURLConnection.HTTP_OK) {
34 | logManager.info("Could not retrieve the weather forecast from the Response");
35 | throw new NoSuchElementException("Could not retrieve the weather forecast from the Response");
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/step/Hooks.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.step;
2 |
3 |
4 | import com.cmccarthy.api.config.WeatherAbstractTestDefinition;
5 | import com.cmccarthy.common.utils.HookUtil;
6 | import com.cmccarthy.common.utils.LogManager;
7 | import io.cucumber.java.After;
8 | import io.cucumber.java.Before;
9 | import io.cucumber.java.Scenario;
10 | import io.cucumber.spring.CucumberContextConfiguration;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 |
13 | @CucumberContextConfiguration
14 | public class Hooks extends WeatherAbstractTestDefinition {
15 | @Autowired
16 | private LogManager logManager;
17 | @Autowired
18 | private HookUtil hookUtil;
19 |
20 | @Before
21 | public void beforeScenario(Scenario scenario) {
22 | String filename = scenario.getName().replaceAll("\\s+", "_");
23 | logManager.createNewLogger(filename);
24 | }
25 |
26 | @After
27 | public void afterScenario(Scenario scenario) {
28 | hookUtil.endOfTest(scenario);
29 | }
30 | }
--------------------------------------------------------------------------------
/weather/src/test/java/com/cmccarthy/api/step/WeatherStep.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.api.step;
2 |
3 | import com.cmccarthy.api.config.WeatherAbstractTestDefinition;
4 | import com.cmccarthy.api.model.response.LocationWeatherRootResponse;
5 | import com.cmccarthy.api.service.WeatherService;
6 | import com.cmccarthy.common.service.StepDefinitionDataManager;
7 | import com.cmccarthy.common.utils.LogManager;
8 | import com.google.gson.Gson;
9 | import io.cucumber.java.en.Given;
10 | import io.cucumber.java.en.Then;
11 | import io.restassured.response.Response;
12 | import org.assertj.core.api.SoftAssertions;
13 | import org.springframework.beans.factory.annotation.Autowired;
14 |
15 | public class WeatherStep extends WeatherAbstractTestDefinition {
16 | @Autowired
17 | private LogManager logManager;
18 | @Autowired
19 | private WeatherService weatherService;
20 | @Autowired
21 | private StepDefinitionDataManager stepDefinitionDataManager;
22 |
23 | @Then("^The weather for (.*) should be returned$")
24 | public void theWeatherForDublinShouldBeReturned(String location) {
25 | final SoftAssertions softAssertions = new SoftAssertions();
26 | final LocationWeatherRootResponse locationWeatherRootResponse =
27 | new Gson().fromJson(((Response) stepDefinitionDataManager.getStoredObjectMap().get("class")).getBody().asString(),
28 | LocationWeatherRootResponse.class);
29 | logManager.info(
30 | "Verifying the Response location : " + locationWeatherRootResponse.getName() + ", is equal to the expected location : " + location);
31 | softAssertions.assertThat(locationWeatherRootResponse.getName()).as("Expected the weather forecast to be for : " + location)
32 | .withFailMessage("But it was for : " + locationWeatherRootResponse.getName()).isEqualToIgnoringCase(location);
33 | softAssertions.assertAll();
34 | }
35 |
36 | @Given("^The user has requested the weather for (.*)$")
37 | public void theUserHasRequestedTheWeatherForDublin(String location) {
38 | logManager.info("The user makes an request for the weather in : " + location);
39 | weatherService.getWeatherForLocation(location);
40 | }
41 | }
--------------------------------------------------------------------------------
/weather/src/test/resources/cucumber.properties:
--------------------------------------------------------------------------------
1 | cucumber.publish.quiet=true
--------------------------------------------------------------------------------
/weather/src/test/resources/feature/WeatherTest.feature:
--------------------------------------------------------------------------------
1 | Feature: Open Weather App - Random Tests
2 |
3 | Scenario: Verify the user can retrieve the weather for Sydney
4 | Given The user has requested the weather for Sydney
5 | Then The weather for Sydney should be returned
6 |
7 | Scenario: Verify the user can retrieve the weather for Dublin
8 | Given The user has requested the weather for Dublin
9 | Then The weather for Dublin should be returned
--------------------------------------------------------------------------------
/weather/src/test/resources/feature/WeatherTest2.feature:
--------------------------------------------------------------------------------
1 | Feature: Open Weather App - Random Tests 2
2 |
3 | Scenario: Verify the user can retrieve the weather for Dublin 2
4 | Given The user has requested the weather for Dublin
5 | Then The weather for Dublin should be returned
6 |
--------------------------------------------------------------------------------
/weather/src/test/resources/suite/WeatherSuiteTest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/wikipedia/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | spring-cucumber-testng-parallel-test-harness
7 | spring-cucumber-testng-parallel-test-harness
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 | wikipedia
12 |
13 |
14 |
15 | spring-cucumber-testng-parallel-test-harness
16 | common
17 | 1.0-SNAPSHOT
18 |
19 |
20 | org.seleniumhq.selenium
21 | selenium-java
22 | ${selenium-java.version}
23 |
24 |
25 |
26 |
27 |
28 |
29 | org.apache.maven.plugins
30 | maven-surefire-plugin
31 | ${maven.surefire.plugin.version}
32 |
33 |
34 | src/test/resources/suite/WikipediaSuiteTest.xml
35 |
36 |
37 |
38 |
39 | org.apache.maven.surefire
40 | surefire-testng
41 | ${surefire.testng.version}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/WikipediaParallelRunnerTest.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui;
2 |
3 | import io.cucumber.testng.AbstractTestNGCucumberTests;
4 | import io.cucumber.testng.CucumberOptions;
5 | import org.testng.annotations.DataProvider;
6 |
7 | @CucumberOptions(
8 | features = {
9 | "src/test/resources/feature/"
10 | },
11 | plugin = {
12 | "com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:",
13 | "io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm"
14 | })
15 | public class WikipediaParallelRunnerTest extends AbstractTestNGCucumberTests {
16 |
17 | @Override
18 | @DataProvider(parallel = true)
19 | public Object[][] scenarios() {
20 | return super.scenarios();
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/annotations/PageObject.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.annotations;
2 |
3 | import org.openqa.selenium.support.FindBy;
4 | import org.openqa.selenium.support.PageFactoryFinder;
5 | import org.springframework.context.annotation.Lazy;
6 | import org.springframework.context.annotation.Scope;
7 | import org.springframework.stereotype.Component;
8 |
9 | import java.lang.annotation.ElementType;
10 | import java.lang.annotation.Retention;
11 | import java.lang.annotation.Target;
12 |
13 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
14 |
15 | @Retention(RUNTIME)
16 | @Target({ElementType.FIELD, ElementType.TYPE})
17 | @PageFactoryFinder(FindBy.FindByBuilder.class)
18 | @Lazy
19 | @Component
20 | @Scope("prototype")
21 | public @interface PageObject {
22 | }
23 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/config/WikipediaAbstractTestDefinition.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.config;
2 |
3 | import org.springframework.boot.test.context.SpringBootTest;
4 | import org.springframework.test.context.ContextConfiguration;
5 |
6 | @ContextConfiguration(classes = {WikipediaContextConfiguration.class})
7 | @SpringBootTest
8 | public class WikipediaAbstractTestDefinition {
9 | }
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/config/WikipediaContextConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.config;
2 |
3 | import org.springframework.context.annotation.ComponentScan;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.context.annotation.PropertySource;
6 | import org.springframework.retry.annotation.EnableRetry;
7 |
8 | @EnableRetry
9 | @Configuration
10 | @ComponentScan({"com.cmccarthy.ui", "com.cmccarthy.common"})
11 | @PropertySource("classpath:/application.properties")
12 | public class WikipediaContextConfiguration {
13 | }
14 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/page/AbstractPage.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.page;
2 |
3 | import com.cmccarthy.ui.utils.DriverManager;
4 | import org.openqa.selenium.support.PageFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Component;
7 |
8 | @Component
9 | public abstract class AbstractPage {
10 |
11 | @Autowired
12 | private DriverManager driverManager;
13 |
14 | protected AbstractPage(DriverManager driverManager) {
15 | PageFactory.initElements(driverManager.getDriver(), this);
16 | }
17 |
18 | protected void openAt(String url) {
19 | driverManager.getDriver().get(url);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/page/WikipediaCommonPage.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.page;
2 |
3 | import com.cmccarthy.ui.annotations.PageObject;
4 | import com.cmccarthy.ui.utils.DriverManager;
5 | import org.openqa.selenium.WebElement;
6 | import org.openqa.selenium.support.FindBy;
7 | import org.openqa.selenium.support.How;
8 |
9 | @PageObject
10 | public class WikipediaCommonPage extends AbstractPage {
11 |
12 | @FindBy(how = How.CLASS_NAME, using = "mainpage-welcome-sitename")
13 | private WebElement centralLogo;
14 |
15 | public WikipediaCommonPage(DriverManager driverManager) {
16 | super(driverManager);
17 | }
18 |
19 | public WebElement getCentralLogo() {
20 | return centralLogo;
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/page/WikipediaHomePage.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.page;
2 |
3 | import com.cmccarthy.ui.annotations.PageObject;
4 | import com.cmccarthy.ui.utils.DriverManager;
5 | import org.openqa.selenium.WebElement;
6 | import org.openqa.selenium.support.FindBy;
7 | import org.openqa.selenium.support.How;
8 |
9 | @PageObject
10 | public class WikipediaHomePage extends AbstractPage {
11 |
12 | @FindBy(how = How.XPATH, using = "//a[contains(@href,'commons.wikimedia.org')]")
13 | private WebElement COMMONS_LOGO;
14 |
15 | @FindBy(how = How.CLASS_NAME, using = "central-featured-logo")
16 | private WebElement CENTRAL_LOGO;
17 |
18 | public WikipediaHomePage(DriverManager driverManager) {
19 | super(driverManager);
20 | }
21 |
22 | public void open(String url) {
23 | openAt(url);
24 | }
25 |
26 | public WebElement getCommonPage() {
27 | return COMMONS_LOGO;
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/step/AbstractStep.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.step;
2 |
3 | import com.cmccarthy.ui.utils.DriverHelper;
4 | import org.openqa.selenium.By;
5 | import org.openqa.selenium.WebElement;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Component;
8 |
9 | @Component
10 | public abstract class AbstractStep {
11 |
12 | @Autowired
13 | private DriverHelper driverHelper;
14 |
15 | public void clickAction(WebElement element) throws NoSuchFieldException {
16 | driverHelper.clickAction(element);
17 | }
18 |
19 | public boolean isElementDisplayed(WebElement element) {
20 | return driverHelper.isElementDisplayed(element);
21 | }
22 |
23 | protected void click(WebElement element) throws NoSuchFieldException {
24 | driverHelper.click(element);
25 | }
26 |
27 | protected void click(By locator) throws NoSuchFieldException {
28 | driverHelper.click(locator);
29 | }
30 |
31 | protected void clickAction(By locator) throws NoSuchFieldException {
32 | driverHelper.clickAction(locator);
33 | }
34 |
35 | protected void sendKeys(WebElement element, String value) {
36 | driverHelper.sendKeys(element, value);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/step/CommonPageSteps.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.step;
2 |
3 | import com.cmccarthy.common.utils.LogManager;
4 | import com.cmccarthy.ui.page.WikipediaCommonPage;
5 | import io.cucumber.java.en.Then;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 |
8 | import static org.testng.AssertJUnit.assertTrue;
9 |
10 | public class CommonPageSteps extends AbstractStep {
11 | @Autowired
12 | private LogManager logManager;
13 | @Autowired
14 | private WikipediaCommonPage wikipediaCommonPage;
15 |
16 | @Then("The user should be on the Common page")
17 | public void theUserShouldBeOnTheCommonPage() {
18 | logManager.info("The Wikipedia Common page should be opened");
19 | assertTrue("Wikipedia Common page should be opened",
20 | isElementDisplayed(wikipediaCommonPage.getCentralLogo()));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/step/Hooks.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.step;
2 |
3 | import com.cmccarthy.common.utils.HookUtil;
4 | import com.cmccarthy.common.utils.LogManager;
5 | import com.cmccarthy.ui.config.WikipediaAbstractTestDefinition;
6 | import com.cmccarthy.ui.utils.DriverManager;
7 | import io.cucumber.java.After;
8 | import io.cucumber.java.Before;
9 | import io.cucumber.java.Scenario;
10 | import io.cucumber.spring.CucumberContextConfiguration;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 |
13 | import java.io.IOException;
14 |
15 | @CucumberContextConfiguration
16 | public class Hooks extends WikipediaAbstractTestDefinition {
17 | @Autowired
18 | private LogManager logManager;
19 | @Autowired
20 | private HookUtil hookUtil;
21 | @Autowired
22 | private DriverManager driverManager;
23 |
24 | @Before
25 | public void beforeScenario(Scenario scenario) throws IOException {
26 | String filename = scenario.getName().replaceAll("\\s+", "_");
27 | logManager.createNewLogger(filename);
28 | driverManager.createDriver();
29 | }
30 |
31 | @After
32 | public void afterScenario(Scenario scenario) {
33 | hookUtil.endOfTest(scenario);
34 | if (driverManager.getDriver() != null) {
35 | driverManager.getDriver().quit();
36 | driverManager.setDriver(null);
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/step/WikipediaPageSteps.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.step;
2 |
3 | import com.cmccarthy.common.utils.ApplicationProperties;
4 | import com.cmccarthy.common.utils.LogManager;
5 | import com.cmccarthy.ui.page.WikipediaHomePage;
6 | import io.cucumber.java.en.And;
7 | import io.cucumber.java.en.Given;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 |
10 | import static org.testng.AssertJUnit.assertTrue;
11 |
12 | public class WikipediaPageSteps extends AbstractStep {
13 | @Autowired
14 | private LogManager logManager;
15 | private final ApplicationProperties applicationProperties;
16 | private final WikipediaHomePage wikipediaHomePage;
17 |
18 | public WikipediaPageSteps(ApplicationProperties applicationProperties,
19 | WikipediaHomePage wikipediaHomePage) {
20 | this.applicationProperties = applicationProperties;
21 | this.wikipediaHomePage = wikipediaHomePage;
22 | }
23 |
24 | @Given("The user opened the Wikipedia Homepage")
25 | public void userIsOpenMainPage() throws NoSuchFieldException {
26 | wikipediaHomePage.open(applicationProperties.getWikipediaUrl());
27 | logManager.info("The user navigated to the Wikipedia Homepage : " + applicationProperties
28 | .getWikipediaUrl());
29 | assertTrue("Wikipedia Homepage should be opened",
30 | isElementDisplayed(wikipediaHomePage.getCommonPage()));
31 | }
32 |
33 | @And("The user clicked on the Common link")
34 | public void theUserClickedOnTheCommonLink() throws NoSuchFieldException {
35 | click(wikipediaHomePage.getCommonPage());
36 | logManager.info("The user clicked the Common link on the Homepage");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverHelper.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.ElementNotInteractableException;
5 | import org.openqa.selenium.StaleElementReferenceException;
6 | import org.openqa.selenium.WebElement;
7 | import org.openqa.selenium.interactions.Actions;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.retry.RetryException;
12 | import org.springframework.retry.annotation.Backoff;
13 | import org.springframework.retry.annotation.Retryable;
14 | import org.springframework.stereotype.Component;
15 |
16 | @Component
17 | @SuppressWarnings("unused")
18 | public class DriverHelper {
19 |
20 | private final Logger logger = LoggerFactory.getLogger(DriverHelper.class);
21 | private DriverManager driverManager;
22 | @Autowired
23 | private DriverWait driverWait;
24 |
25 | /**
26 | * Send Keys to the specified element, clears the element first
27 | */
28 | public void sendKeys(WebElement element, String value) {
29 | if (value != null) {
30 | if (!value.isEmpty()) {
31 | clear(element);
32 | element.sendKeys(value);
33 | } else {
34 | clear(element);
35 | }
36 | }
37 | }
38 |
39 | /**
40 | * Clicks on an element by WebElement
41 | */
42 | @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
43 | public void click(WebElement element) throws NoSuchFieldException {
44 | try {
45 | driverWait.waitForElementToLoad(element);
46 | element.click();
47 | } catch (StaleElementReferenceException e) {
48 | logger.warn("Could not click on the element");
49 | throw new RetryException("Could not click on the element : " + element);
50 | }
51 | }
52 |
53 | /**
54 | * Clicks on an element by Locator
55 | */
56 | @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
57 | public void click(By locator) throws NoSuchFieldException {
58 | try {
59 | driverWait.waitForElementToLoad(locator);
60 | driverManager.getDriver().findElement(locator).click();
61 | } catch (StaleElementReferenceException e) {
62 | logger.warn("Could not click on the element");
63 | throw new RetryException("Could not click on the element");
64 | }
65 | }
66 |
67 | /**
68 | * Clicks on an element by Locator
69 | */
70 | @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = { RetryException.class })
71 | public void rightClick(By locator) throws NoSuchFieldException {
72 | driverWait.waitForElementToLoad(locator);
73 | final WebElement element = driverManager.getDriver().findElement(locator);
74 | try {
75 | final Actions builder = new Actions(driverManager.getDriver());
76 | builder.moveToElement(element).contextClick(element);
77 | builder.perform();
78 | } catch (Exception ser) {
79 | logger.warn("Could not right click on the element : " + element);
80 | throw new RetryException("Could not click on the element : " + element);
81 | }
82 | }
83 |
84 | @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
85 | public void scrollElementIntoView(WebElement element) {
86 | try {
87 | driverManager.getJSExecutor().executeScript("arguments[0].scrollIntoView(true);", element);
88 | } catch (Exception ignored) {
89 | logger.warn("Could not scroll the element into view" + element);
90 | throw new RetryException("Could not click on the element : " + element);
91 | }
92 | }
93 |
94 | /**
95 | * Clicks on an element by WebElement
96 | */
97 | @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
98 | public void rightClick(WebElement element) throws NoSuchFieldException {
99 | driverWait.waitForElementToLoad(element);
100 |
101 | try {
102 | final Actions builder = new Actions(driverManager.getDriver());
103 | builder.moveToElement(element).contextClick(element);
104 | builder.perform();
105 | } catch (Exception ser) {
106 | logger.warn("Could not click on the element : " + element);
107 | throw new RetryException("Could not click on the element : " + element);
108 | }
109 | }
110 |
111 | /**
112 | * Clicks on an element using Actions
113 | */
114 |
115 | @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
116 | public void clickAction(WebElement element) throws NoSuchFieldException {
117 | driverWait.waitForElementToLoad(element);
118 | try {
119 | final Actions builder = new Actions(driverManager.getDriver());
120 | builder.moveToElement(element).click(element);
121 | builder.perform();
122 | } catch (Exception ser) {
123 | logger.warn("Could not click action on the element");
124 | throw new RetryException("Could not click on the element : " + element);
125 | }
126 | }
127 |
128 | /**
129 | * Clicks on an element using Actions
130 | */
131 | @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500), retryFor = {RetryException.class})
132 | public void clickAction(By locator) throws NoSuchFieldException {
133 | driverWait.waitForElementToLoad(locator);
134 |
135 | final WebElement element = driverManager.getDriver().findElement(locator);
136 | try {
137 | final Actions builder = new Actions(driverManager.getDriver());
138 | builder.moveToElement(element).click(element);
139 | builder.perform();
140 | } catch (Exception ser) {
141 | logger.warn("Could not click action on the element");
142 | throw new RetryException("Could not click on the element : " + element);
143 | }
144 | }
145 |
146 | /**
147 | * Checks if the specified element is displayed
148 | */
149 | public boolean isElementDisplayed(WebElement element) {
150 | boolean present = false;
151 | try {
152 | present = element.isDisplayed();
153 | } catch (Exception ignored) {
154 | }
155 | return present;
156 | }
157 |
158 | /**
159 | * Clear text from a field
160 | */
161 | private void clear(WebElement element) {
162 | try {
163 | driverManager.getJSExecutor().executeScript("arguments[0].value='';", element);
164 | } catch (ElementNotInteractableException ignored) {
165 | }
166 | }
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverManager.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils;
2 |
3 | import com.cmccarthy.common.utils.ApplicationProperties;
4 | import com.cmccarthy.common.utils.Constants;
5 | import org.openqa.selenium.Capabilities;
6 | import org.openqa.selenium.JavascriptExecutor;
7 | import org.openqa.selenium.WebDriver;
8 | import org.openqa.selenium.chrome.ChromeDriver;
9 | import org.openqa.selenium.chrome.ChromeOptions;
10 | import org.openqa.selenium.edge.EdgeDriver;
11 | import org.openqa.selenium.edge.EdgeOptions;
12 | import org.openqa.selenium.firefox.FirefoxDriver;
13 | import org.openqa.selenium.firefox.FirefoxOptions;
14 | import org.openqa.selenium.ie.InternetExplorerDriver;
15 | import org.openqa.selenium.ie.InternetExplorerOptions;
16 | import org.openqa.selenium.remote.RemoteWebDriver;
17 | import org.openqa.selenium.safari.SafariDriver;
18 | import org.openqa.selenium.safari.SafariOptions;
19 | import org.openqa.selenium.support.ui.WebDriverWait;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 | import org.springframework.beans.factory.annotation.Autowired;
23 | import org.springframework.core.env.Environment;
24 | import org.springframework.stereotype.Component;
25 |
26 | import java.io.IOException;
27 | import java.net.URL;
28 | import java.time.Duration;
29 | import java.util.Arrays;
30 | import java.util.NoSuchElementException;
31 |
32 | @Component
33 | public class DriverManager {
34 |
35 | private static final ThreadLocal driverThreadLocal = new ThreadLocal<>();
36 | private final Logger log = LoggerFactory.getLogger(DriverManager.class);
37 |
38 | @Autowired
39 | private ApplicationProperties applicationProperties;
40 | @Autowired
41 | private DriverWait driverWait;
42 | @Autowired
43 | private Environment environment;
44 |
45 | public void createDriver() throws IOException {
46 | if (getDriver() == null) {
47 | if (Arrays.toString(this.environment.getActiveProfiles()).contains("cloud-provider")) {
48 | setRemoteDriver(new URL(applicationProperties.getGridUrl()));
49 | } else {
50 | setLocalWebDriver();
51 | }
52 | getDriver().manage().deleteAllCookies();//useful for AJAX pages
53 | }
54 | }
55 |
56 | public void setLocalWebDriver() throws IOException {
57 | switch (applicationProperties.getBrowser()) {
58 | case ("chrome") -> {
59 | ChromeOptions options = new ChromeOptions();
60 | options.addArguments("--disable-logging");
61 | options.addArguments("--no-sandbox");
62 | options.addArguments("--disable-dev-shm-usage");
63 | options.addArguments("--start-maximized");
64 | options.addArguments("--headless=new");
65 | driverThreadLocal.set(new ChromeDriver(options));
66 | }
67 | case ("firefox") -> {
68 | FirefoxOptions firefoxOptions = new FirefoxOptions();
69 | firefoxOptions.setCapability("marionette", true);
70 | driverThreadLocal.set(new FirefoxDriver(firefoxOptions));
71 | }
72 | case ("ie") -> {
73 | InternetExplorerOptions capabilitiesIE = new InternetExplorerOptions();
74 | capabilitiesIE.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
75 | driverThreadLocal.set(new InternetExplorerDriver(capabilitiesIE));
76 | }
77 | case ("safari") -> {
78 | SafariOptions operaOptions = new SafariOptions();
79 | driverThreadLocal.set(new SafariDriver(operaOptions));
80 | }
81 | case ("edge") -> {
82 | EdgeOptions edgeOptions = new EdgeOptions();
83 | driverThreadLocal.set(new EdgeDriver(edgeOptions));
84 | }
85 | default ->
86 | throw new NoSuchElementException("Failed to create an instance of WebDriver for: " + applicationProperties.getBrowser());
87 | }
88 | driverWait.getDriverWaitThreadLocal()
89 | .set(new WebDriverWait(driverThreadLocal.get(), Duration.ofSeconds(Constants.timeoutShort), Duration.ofSeconds(Constants.pollingShort)));
90 | }
91 |
92 | private void setRemoteDriver(URL hubUrl) {
93 | Capabilities capability;
94 | switch (applicationProperties.getBrowser()) {
95 | case "firefox" -> {
96 | capability = new FirefoxOptions();
97 | driverThreadLocal.set(new RemoteWebDriver(hubUrl, capability));
98 | }
99 | case "chrome" -> {
100 | ChromeOptions options = new ChromeOptions();
101 | options.addArguments("--no-sandbox");
102 | options.addArguments("--disable-dev-shm-usage");
103 | options.addArguments("--headless");
104 | driverThreadLocal.set(new RemoteWebDriver(hubUrl, options));
105 | }
106 | case "ie" -> {
107 | capability = new InternetExplorerOptions();
108 | driverThreadLocal.set(new RemoteWebDriver(hubUrl, capability));
109 | }
110 | case "safari" -> {
111 | capability = new SafariOptions();
112 | driverThreadLocal.set(new RemoteWebDriver(hubUrl, capability));
113 | }
114 | case ("edge") -> {
115 | capability = new EdgeOptions();
116 | driverThreadLocal.set(new RemoteWebDriver(hubUrl, capability));
117 | }
118 | default ->
119 | throw new NoSuchElementException("Failed to create an instance of RemoteWebDriver for: " + applicationProperties.getBrowser());
120 | }
121 | driverWait.getDriverWaitThreadLocal()
122 | .set(new WebDriverWait(driverThreadLocal.get(), Duration.ofSeconds(Constants.timeoutShort), Duration.ofSeconds(Constants.pollingShort)));
123 | }
124 |
125 | public WebDriver getDriver() {
126 | return driverThreadLocal.get();
127 | }
128 |
129 | public void setDriver(WebDriver driver) {
130 | driverThreadLocal.set(driver);
131 | }
132 |
133 | public JavascriptExecutor getJSExecutor() {
134 | return (JavascriptExecutor) getDriver();
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/DriverWait.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils;
2 |
3 | import com.cmccarthy.common.utils.Constants;
4 | import com.cmccarthy.ui.utils.expectedConditions.*;
5 | import org.openqa.selenium.By;
6 | import org.openqa.selenium.StaleElementReferenceException;
7 | import org.openqa.selenium.WebDriver;
8 | import org.openqa.selenium.WebElement;
9 | import org.openqa.selenium.support.ui.ExpectedCondition;
10 | import org.openqa.selenium.support.ui.FluentWait;
11 | import org.openqa.selenium.support.ui.Wait;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.context.annotation.Lazy;
14 | import org.springframework.stereotype.Component;
15 |
16 | import java.time.Duration;
17 | import java.util.NoSuchElementException;
18 |
19 | @Component
20 | public class DriverWait {
21 |
22 | private static final ThreadLocal> driverWaitThreadLocal = new ThreadLocal<>();
23 | @Autowired
24 | @Lazy
25 | private DriverManager driverManager;
26 |
27 | public ThreadLocal> getDriverWaitThreadLocal() {
28 | return driverWaitThreadLocal;
29 | }
30 |
31 | public void waitForAngular() {
32 | waitUntilAngularReady();
33 | }
34 |
35 | public void waitForElementToLoad(WebElement element) throws NoSuchFieldException {
36 | waitForAngular();
37 | waitForElementVisible(element);
38 | waitForElementClickable(element);
39 | }
40 |
41 | public void waitForElementToLoad(By locator) throws NoSuchFieldException {
42 | waitForAngular();
43 | waitForElementVisible(locator);
44 | waitForElementClickable(locator);
45 | }
46 |
47 | /**
48 | * wait for element visible by element
49 | */
50 | private void waitForElementVisible(WebElement element) {
51 | try {
52 | waitLong().until(new VisibilityOfElement(element));
53 | } catch (Exception ignored) {
54 | }
55 | }
56 |
57 | /**
58 | * wait for element visible by locator
59 | */
60 | private void waitForElementVisible(By locator) {
61 | try {
62 | waitLong().until(new VisibilityOfElementByLocator(locator));
63 | } catch (Exception ignored) {
64 | }
65 | }
66 |
67 | /**
68 | * wait for element Invisible by locator
69 | */
70 | private void waitForElementInVisible(By locator) {
71 | try {
72 | waitLong().until(new InvisibilityOfElementByLocator(locator));
73 | } catch (Exception ignored) {
74 | }
75 | }
76 |
77 | /**
78 | * wait for element Invisible by locator
79 | */
80 | private void waitForElementInVisible(WebElement element) {
81 | try {
82 | waitLong().until(new InvisibilityOfElement(element));
83 | } catch (Exception ignored) {
84 | }
85 | }
86 |
87 | /**
88 | * wait for element clickable by element
89 | */
90 | private void waitForElementClickable(WebElement element) throws NoSuchFieldException {
91 | try {
92 | waitLong().until(new ClickabilityOfElement(element));
93 | } catch (Exception t) {
94 | throw new NoSuchFieldException("could not interact with the element " + element);
95 | }
96 | }
97 |
98 | /**
99 | * wait for element clickable by locator
100 | */
101 | private void waitForElementClickable(By locator) throws NoSuchFieldException {
102 | try {
103 | waitLong().until(new ClickabilityOfElementByLocator(locator));
104 | } catch (Exception t) {
105 | throw new NoSuchFieldException("could not interact with the element by locator " + locator);
106 | }
107 | }
108 |
109 | public Wait waitLong() {
110 | return new FluentWait<>(driverManager.getDriver())
111 | .withTimeout(Duration.ofSeconds(Constants.timeoutLong))
112 | .pollingEvery(Duration.ofMillis(Constants.pollingLong))
113 | .ignoring(NoSuchElementException.class, StaleElementReferenceException.class);
114 | }
115 |
116 | public Wait waitShort() {
117 | return new FluentWait<>(driverManager.getDriver())
118 | .withTimeout(Duration.ofSeconds(Constants.timeoutShort))
119 | .pollingEvery(Duration.ofMillis(Constants.pollingShort))
120 | .ignoring(NoSuchElementException.class, StaleElementReferenceException.class);
121 | }
122 |
123 | private void waitUntilAngularReady() {
124 |
125 |
126 | final Boolean angularUnDefined = (Boolean) driverManager.getJSExecutor()
127 | .executeScript("return window.angular === undefined");
128 |
129 | if (!angularUnDefined) {
130 | Boolean angularInjectorUnDefined = (Boolean) driverManager.getJSExecutor()
131 | .executeScript("return angular.element(document).injector() === undefined");
132 | if (!angularInjectorUnDefined) {
133 | waitForAngularLoad();
134 | waitUntilJSReady();
135 | waitForJQueryLoad();
136 | }
137 | }
138 | }
139 |
140 | private void waitForAngularLoad() {
141 | final String angularReadyScript = "return angular.element(document).injector().get('$http').pendingRequests.length === 0";
142 | final ExpectedCondition angularLoad = driver -> Boolean.valueOf(
143 | (driverManager.getJSExecutor()).executeScript(angularReadyScript).toString());
144 | boolean angularReady = Boolean
145 | .parseBoolean(driverManager.getJSExecutor().executeScript(angularReadyScript).toString());
146 | if (!angularReady) {
147 | waitLong().until(angularLoad);
148 | }
149 | }
150 |
151 | private void waitUntilJSReady() {
152 | final ExpectedCondition jsLoad = driver -> (driverManager.getJSExecutor())
153 | .executeScript("return document.readyState")
154 | .toString()
155 | .equals("complete");
156 |
157 | boolean jsReady = driverManager.getJSExecutor().executeScript("return document.readyState")
158 | .toString().equals("complete");
159 |
160 | if (!jsReady) {
161 | waitLong().until(jsLoad);
162 | }
163 | }
164 |
165 | private void waitForJQueryLoad() {
166 | final ExpectedCondition jQueryLoad = driver -> (
167 | (Long) (driverManager.getJSExecutor()).executeScript("return jQuery.active") == 0);
168 | boolean jqueryReady = (Boolean) driverManager.getJSExecutor()
169 | .executeScript("return jQuery.active==0");
170 | if (!jqueryReady) {
171 | waitLong().until(jQueryLoad);
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/expectedConditions/ClickabilityOfElement.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils.expectedConditions;
2 |
3 | import com.cmccarthy.common.utils.Constants;
4 | import org.openqa.selenium.NoSuchElementException;
5 | import org.openqa.selenium.StaleElementReferenceException;
6 | import org.openqa.selenium.WebDriver;
7 | import org.openqa.selenium.WebElement;
8 | import org.openqa.selenium.support.ui.ExpectedCondition;
9 | import org.openqa.selenium.support.ui.ExpectedConditions;
10 | import org.openqa.selenium.support.ui.FluentWait;
11 | import org.openqa.selenium.support.ui.Wait;
12 |
13 | import java.time.Duration;
14 |
15 | public class ClickabilityOfElement implements ExpectedCondition {
16 |
17 | private final WebElement element;
18 |
19 | public ClickabilityOfElement(WebElement element) {
20 | this.element = element;
21 | }
22 |
23 | @Override
24 | public WebElement apply(WebDriver webDriver) {
25 |
26 | final Wait wait = new FluentWait<>(webDriver)
27 | .withTimeout(Duration.ofSeconds(Constants.timeoutShort))
28 | .pollingEvery(Duration.ofMillis(Constants.pollingShort))
29 | .ignoring(java.util.NoSuchElementException.class,
30 | StaleElementReferenceException.class);
31 | try {
32 | return wait.until(ExpectedConditions.elementToBeClickable(element));
33 | } catch (StaleElementReferenceException | NoSuchElementException exception) {
34 | return element;
35 | } catch (Throwable t) {
36 | throw new Error(t);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/expectedConditions/ClickabilityOfElementByLocator.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils.expectedConditions;
2 |
3 | import com.cmccarthy.common.utils.Constants;
4 | import org.openqa.selenium.*;
5 | import org.openqa.selenium.support.ui.ExpectedCondition;
6 | import org.openqa.selenium.support.ui.ExpectedConditions;
7 | import org.openqa.selenium.support.ui.FluentWait;
8 | import org.openqa.selenium.support.ui.Wait;
9 |
10 | import java.time.Duration;
11 |
12 | public class ClickabilityOfElementByLocator implements ExpectedCondition {
13 |
14 | private final By locator;
15 |
16 | public ClickabilityOfElementByLocator(By locator) {
17 | this.locator = locator;
18 | }
19 |
20 | @Override
21 | public WebElement apply(WebDriver webDriver) {
22 |
23 | final Wait wait = new FluentWait<>(webDriver)
24 | .withTimeout(Duration.ofSeconds(Constants.timeoutShort))
25 | .pollingEvery(Duration.ofMillis(Constants.pollingShort))
26 | .ignoring(java.util.NoSuchElementException.class,
27 | StaleElementReferenceException.class);
28 |
29 | try {
30 | return wait.until(ExpectedConditions.elementToBeClickable(locator));
31 | } catch (StaleElementReferenceException | NoSuchElementException e) {
32 | return webDriver.findElement(locator);
33 | } catch (Throwable t) {
34 | throw new Error(t);
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/expectedConditions/InvisibilityOfElement.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils.expectedConditions;
2 |
3 | import org.openqa.selenium.NoSuchElementException;
4 | import org.openqa.selenium.StaleElementReferenceException;
5 | import org.openqa.selenium.WebDriver;
6 | import org.openqa.selenium.WebElement;
7 | import org.openqa.selenium.support.ui.ExpectedCondition;
8 |
9 | public class InvisibilityOfElement implements ExpectedCondition {
10 |
11 | private final WebElement element;
12 |
13 | public InvisibilityOfElement(WebElement element) {
14 | this.element = element;
15 | }
16 |
17 | @Override
18 | public Boolean apply(WebDriver d) {
19 | try {
20 | return element.isDisplayed();
21 | } catch (StaleElementReferenceException | NoSuchElementException e) {
22 | return true;
23 | } catch (Throwable t) {
24 | throw new Error(t);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/expectedConditions/InvisibilityOfElementByLocator.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils.expectedConditions;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.NoSuchElementException;
5 | import org.openqa.selenium.StaleElementReferenceException;
6 | import org.openqa.selenium.WebDriver;
7 | import org.openqa.selenium.support.ui.ExpectedCondition;
8 |
9 | public class InvisibilityOfElementByLocator implements ExpectedCondition {
10 |
11 | private final By locator;
12 |
13 | public InvisibilityOfElementByLocator(By locator) {
14 | this.locator = locator;
15 | }
16 |
17 | @Override
18 | public Boolean apply(WebDriver d) {
19 | try {
20 | return d.findElement(locator).isDisplayed();
21 | } catch (StaleElementReferenceException | NoSuchElementException e) {
22 | return true;
23 | } catch (Throwable t) {
24 | throw new Error(t);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/expectedConditions/VisibilityOfElement.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils.expectedConditions;
2 |
3 | import org.openqa.selenium.NoSuchElementException;
4 | import org.openqa.selenium.StaleElementReferenceException;
5 | import org.openqa.selenium.WebDriver;
6 | import org.openqa.selenium.WebElement;
7 | import org.openqa.selenium.support.ui.ExpectedCondition;
8 |
9 | public class VisibilityOfElement implements ExpectedCondition {
10 |
11 | private final WebElement element;
12 |
13 | public VisibilityOfElement(WebElement element) {
14 | this.element = element;
15 | }
16 |
17 | @Override
18 | public Boolean apply(WebDriver d) {
19 | try {
20 | return element.isDisplayed();
21 | } catch (StaleElementReferenceException | NoSuchElementException e) {
22 | return false;
23 | } catch (Throwable t) {
24 | throw new Error(t);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/wikipedia/src/test/java/com/cmccarthy/ui/utils/expectedConditions/VisibilityOfElementByLocator.java:
--------------------------------------------------------------------------------
1 | package com.cmccarthy.ui.utils.expectedConditions;
2 |
3 | import org.openqa.selenium.By;
4 | import org.openqa.selenium.NoSuchElementException;
5 | import org.openqa.selenium.StaleElementReferenceException;
6 | import org.openqa.selenium.WebDriver;
7 | import org.openqa.selenium.support.ui.ExpectedCondition;
8 |
9 | public class VisibilityOfElementByLocator implements ExpectedCondition {
10 |
11 | private final By locator;
12 |
13 | public VisibilityOfElementByLocator(By locator) {
14 | this.locator = locator;
15 | }
16 |
17 | @Override
18 | public Boolean apply(WebDriver d) {
19 | try {
20 | return d.findElement(locator).isDisplayed();
21 | } catch (StaleElementReferenceException | NoSuchElementException e) {
22 | return false;
23 | } catch (Throwable t) {
24 | throw new Error(t);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/wikipedia/src/test/resources/cucumber.properties:
--------------------------------------------------------------------------------
1 | cucumber.publish.quiet=true
--------------------------------------------------------------------------------
/wikipedia/src/test/resources/feature/WikipediaTest.feature:
--------------------------------------------------------------------------------
1 | @Wikipedia-UI
2 | Feature: As a user, I want to look for an article on Wikipedia
3 |
4 | Scenario: I want to find an article on Wikipedia 1
5 | Given The user opened the Wikipedia Homepage
6 | And The user clicked on the Common link
7 | Then The user should be on the Common page
8 |
9 | Scenario: I want to find an article on Wikipedia 1.1
10 | Given The user opened the Wikipedia Homepage
11 | And The user clicked on the Common link
12 | Then The user should be on the Common page
13 |
14 |
15 |
--------------------------------------------------------------------------------
/wikipedia/src/test/resources/feature/WikipediaTest2.feature:
--------------------------------------------------------------------------------
1 | @Wikipedia-UI
2 | Feature: As a user, I want to look for an article on Wikipedia 2
3 |
4 | Scenario: I want to find an article on Wikipedia 2
5 | Given The user opened the Wikipedia Homepage
6 | And The user clicked on the Common link
7 | Then The user should be on the Common page
8 |
--------------------------------------------------------------------------------
/wikipedia/src/test/resources/suite/WikipediaSuiteTest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------