diff --git a/testing-modules/cucumber/pom.xml b/testing-modules/cucumber/pom.xml
new file mode 100644
index 0000000000..dda5fc8ce3
--- /dev/null
+++ b/testing-modules/cucumber/pom.xml
@@ -0,0 +1,182 @@
+
+
+
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+
+ 4.0.0
+ cucumber
+ 1.0-SNAPSHOT
+ cucumber
+
+
+ 11
+ 11
+ 6.9.1
+ 5.4.0
+ 3.141.59
+ 4.3.1
+ 0.40
+ 3.0.0
+ 4.5.3
+ 2.2.5.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.webjars
+ webjars-locator
+ ${webjars-locator.version}
+
+
+ org.webjars.npm
+ jquery-slim
+ ${jquery.version}
+
+
+ org.webjars
+ bootstrap
+ ${bootstrap.version}
+
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ io.cucumber
+ cucumber-java
+ ${cucumber.version}
+
+
+ io.cucumber
+ cucumber-junit-platform-engine
+ ${cucumber.version}
+
+
+ io.cucumber
+ cucumber-spring
+ ${cucumber.version}
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ ${selenium.version}
+
+
+ org.seleniumhq.selenium
+ selenium-support
+ ${selenium.version}
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ ${webdrivermanager.version}
+ test
+
+
+ io.rest-assured
+ spring-mock-mvc
+ ${rest-assured.version}
+ test
+
+
+ io.rest-assured
+ json-schema-validator
+ ${rest-assured.version}
+ test
+
+
+ org.springframework.cloud
+ spring-cloud-contract-wiremock
+ ${spring-cloud-contract-wiremock.version}
+ test
+
+
+
+
+
+
+
+ net.masterthought
+ maven-cucumber-reporting
+ ${cucumber-reporting.version}
+
+ randomnumbergenerator
+
+ ${project.build.directory}
+
+
+ ${project.build.directory}/cucumber
+
+ **/*.properties
+
+ **/*.json
+
+
+
+
+
+
+
+
+
+ acceptance
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${maven-failsafe-plugin.version}
+
+
+ **/*IT.java
+
+
+
+ integration-test
+ verify
+
+
+
+ net.masterthought
+ maven-cucumber-reporting
+
+
+ execution
+ post-integration-test
+
+ generate
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/Application.java b/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/Application.java
new file mode 100644
index 0000000000..fad34a07ba
--- /dev/null
+++ b/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/Application.java
@@ -0,0 +1,13 @@
+package com.baeldung.cucumber_tags;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/controller/HealthController.java b/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/controller/HealthController.java
new file mode 100644
index 0000000000..24c8116307
--- /dev/null
+++ b/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/controller/HealthController.java
@@ -0,0 +1,17 @@
+package com.baeldung.cucumber_tags.controller;
+
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HealthController {
+
+ @RequestMapping(value="/status", produces= MediaType.APPLICATION_JSON_VALUE)
+ public HttpStatus statusCheck() {
+ return ResponseEntity.ok().build().getStatusCode();
+ }
+}
diff --git a/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/controller/UiController.java b/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/controller/UiController.java
new file mode 100644
index 0000000000..73b705f10a
--- /dev/null
+++ b/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/controller/UiController.java
@@ -0,0 +1,36 @@
+package com.baeldung.cucumber_tags.controller;
+
+import com.baeldung.cucumber_tags.service.RandomNumberGeneratorService;
+import lombok.Data;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+
+
+@Controller
+public class UiController {
+
+ @GetMapping("/random-number-generator")
+ public String showForm(Model model) {
+ RandomNumberQuery randomNumberQuery = new RandomNumberQuery();
+ model.addAttribute("randomNumberQuery", randomNumberQuery);
+
+ return "random-number-generator";
+ }
+
+ @PostMapping(value = "/random-number-generator")
+ public String generateRandomNumber(@ModelAttribute("randomNumberQuery") final RandomNumberQuery randomNumberQuery) {
+ RandomNumberGeneratorService service = new RandomNumberGeneratorService();
+ randomNumberQuery.randomNumber = service.generateRandomNumber(randomNumberQuery.min, randomNumberQuery.max);
+ return "random-number-generator";
+}
+
+ @Data
+ private static class RandomNumberQuery {
+ Integer min = null;
+ Integer max = null;
+ Integer randomNumber = null;
+ }
+}
diff --git a/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/service/RandomNumberGeneratorService.java b/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/service/RandomNumberGeneratorService.java
new file mode 100644
index 0000000000..b40c703593
--- /dev/null
+++ b/testing-modules/cucumber/src/main/java/com/baeldung/cucumber_tags/service/RandomNumberGeneratorService.java
@@ -0,0 +1,10 @@
+package com.baeldung.cucumber_tags.service;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class RandomNumberGeneratorService {
+
+ public int generateRandomNumber(int min, int max) {
+ return ThreadLocalRandom.current().nextInt(min, max + 1);
+ }
+}
\ No newline at end of file
diff --git a/testing-modules/cucumber/src/main/resources/templates/random-number-generator.html b/testing-modules/cucumber/src/main/resources/templates/random-number-generator.html
new file mode 100644
index 0000000000..1d612e3856
--- /dev/null
+++ b/testing-modules/cucumber/src/main/resources/templates/random-number-generator.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+ Random Number Generator
+
+
+
+
+
+
+
+
+
+
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/AcceptanceTestRunnerIT.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/AcceptanceTestRunnerIT.java
new file mode 100644
index 0000000000..68d29b4b1c
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/AcceptanceTestRunnerIT.java
@@ -0,0 +1,9 @@
+package com.baeldung.cucumber_tags.acceptance;
+
+
+import io.cucumber.junit.platform.engine.Cucumber;
+
+@Cucumber()
+public class AcceptanceTestRunnerIT {
+
+}
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/api/steps/HealthSteps.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/api/steps/HealthSteps.java
new file mode 100644
index 0000000000..d491326ded
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/api/steps/HealthSteps.java
@@ -0,0 +1,37 @@
+package com.baeldung.cucumber_tags.acceptance.api.steps;
+
+
+import com.baeldung.cucumber_tags.acceptance.commonutil.ScenarioContextApi;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+import org.hamcrest.Matchers;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class HealthSteps {
+
+ @Autowired
+ private ScenarioContextApi context;
+
+
+ @When("^I make a GET call on ([^\"]*)$")
+ public void iMakeAGETCallOn(String path) {
+ context.invokeHttpGet(path);
+ }
+
+ @When("^I make a POST call on ([^\"]*)$")
+ public void iMakeAPOSTCallOn(String path) {
+ context.invokeHttpPost(path, context.postBody);
+ }
+
+ @Then("^I should receive (\\d+) response status code$")
+ public void iShouldReceiveStatusCodeResponse(int code) {
+ context.response.then().statusCode(code);
+ }
+
+ @Then("^should receive a non-empty body$")
+ public void shouldReceiveANonEmptyBody() {
+ context.response.then().body(Matchers.notNullValue());
+ }
+
+
+}
\ No newline at end of file
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/CucumberEnvironment.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/CucumberEnvironment.java
new file mode 100644
index 0000000000..f78f9b52f8
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/CucumberEnvironment.java
@@ -0,0 +1,44 @@
+package com.baeldung.cucumber_tags.acceptance.commonutil;
+
+import org.openqa.selenium.InvalidArgumentException;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Optional;
+
+public final class CucumberEnvironment {
+
+ private static final String SELENIUM_GRID_URL_ENV_VAR = "SELENIUM_GRID_URL";
+ private static final String SERVICE_HOSTNAME = "SERVICE_HOSTNAME";
+ private static final String LOCALHOST = "localhost";
+
+ private CucumberEnvironment() {
+ }
+
+ /**
+ * Gets the network host where the service is running. When running while developing, this is going to be
+ * localhost because webdriver and the test runner are going to be on the same machine. On gitlab-ci /
+ * docker-compose though, they are going to be on separate hosts so webdriver needs to hit the
+ * machine where the test runner is starting the service, not localhost.
+ */
+ public static String getServiceHost() {
+ final Optional serviceHostname = Optional.ofNullable(System.getenv(SERVICE_HOSTNAME));
+ return serviceHostname.orElse(LOCALHOST);
+ }
+
+ /**
+ * Gets the url where the selenium grid instance is. This is used for gitlab-ci / docker-compose setups.
+ */
+ public static Optional getSeleniumGridUrl() {
+ final Optional seleniumGridUrlString = Optional.ofNullable(System.getenv(SELENIUM_GRID_URL_ENV_VAR));
+ return seleniumGridUrlString.map(CucumberEnvironment::parseSeleniumGridUrl);
+ }
+
+ private static URL parseSeleniumGridUrl(String seleniumGridUrlString) {
+ try {
+ return new URL(seleniumGridUrlString);
+ } catch (MalformedURLException e) {
+ throw new InvalidArgumentException(SELENIUM_GRID_URL_ENV_VAR + "env var is not a valid URL");
+ }
+ }
+}
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioContextApi.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioContextApi.java
new file mode 100644
index 0000000000..534608bd34
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioContextApi.java
@@ -0,0 +1,57 @@
+package com.baeldung.cucumber_tags.acceptance.commonutil;
+
+import io.restassured.http.ContentType;
+import io.restassured.module.mockmvc.response.MockMvcResponse;
+import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
+
+@Component
+@Scope(scopeName = "cucumber-glue")
+public class ScenarioContextApi {
+
+ @LocalServerPort
+ int port;
+
+ private ScenarioReport report;
+ public MockMvcRequestSpecification request;
+ public MockMvcResponse response;
+ public Map postBody = new HashMap<>();
+ public Map queryParams = new HashMap<>();
+
+ public ScenarioContextApi() {
+ reset();
+ }
+
+ private void reset() {
+ report = new ScenarioReport();
+ request = null;
+ response = null;
+ postBody.clear();
+ queryParams.clear();
+ }
+
+ public void invokeHttpGet(String path, Object... pathParams) {
+ request = given().log().all();
+ response = request.when().get(path, pathParams);
+ response.then().log().all();
+ }
+
+ public void invokeHttpPost(String path, Map data) {
+ request = given().log().all().body(data).queryParams(queryParams).contentType(ContentType.JSON);
+ response = request.post(path);
+ response.then().log().all();
+ }
+
+
+ public ScenarioReport getReport() {
+ return report;
+ }
+
+}
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioContextUI.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioContextUI.java
new file mode 100644
index 0000000000..b332ceb3b4
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioContextUI.java
@@ -0,0 +1,77 @@
+package com.baeldung.cucumber_tags.acceptance.commonutil;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import java.net.URL;
+
+import static io.github.bonigarcia.wdm.WebDriverManager.getInstance;
+import static io.github.bonigarcia.wdm.config.DriverManagerType.CHROME;
+
+@Component
+@Scope(scopeName = "cucumber-glue")
+public class ScenarioContextUI {
+
+ protected static final String RANDOM_NUMBER_URL = "/random-number-generator";
+
+ @LocalServerPort
+ int port;
+ private WebDriver driver;
+ private ScenarioReport report;
+
+ public ScenarioContextUI() {
+ reset();
+ }
+
+ private void reset() {
+ report = new ScenarioReport();
+ driver = null;
+ }
+
+ private static WebDriver getRemoteWebDriver(URL url) {
+ return new RemoteWebDriver(url, DesiredCapabilities.chrome());
+ }
+
+ private static WebDriver getLocalChromeDriver() {
+ getInstance(CHROME).setup();
+ return new ChromeDriver();
+ }
+
+ public ScenarioReport getReport() {
+ return report;
+ }
+
+ public String getRandomNumberUrl() {
+ return "http://" + getServiceBaseUrl() + RANDOM_NUMBER_URL;
+ }
+
+ private String getServiceBaseUrl() {
+ return CucumberEnvironment.getServiceHost() + ":" + Integer.toString(port);
+ }
+
+ /**
+ * If we are running inside docker (mostly for gitlab ci purposes), we expect a selenium grid setup.
+ * If that environment variable isn't set, we assume we're in "dev mode" and ChromeDriverManager will
+ * provide the local instance of chromedriver (no need to have chromedriver installed).
+ */
+ public WebDriver getWebDriver() {
+ if (driver == null) {
+ return getFreshWebdriver();
+ } else {
+ return driver;
+ }
+ }
+
+ private WebDriver getFreshWebdriver() {
+ driver = CucumberEnvironment.getSeleniumGridUrl()
+ .map(ScenarioContextUI::getRemoteWebDriver)
+ .orElseGet(ScenarioContextUI::getLocalChromeDriver);
+ return driver;
+ }
+
+}
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioHooks.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioHooks.java
new file mode 100644
index 0000000000..cf44310fa8
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioHooks.java
@@ -0,0 +1,58 @@
+package com.baeldung.cucumber_tags.acceptance.commonutil;
+
+
+import io.cucumber.java.After;
+import io.cucumber.java.Before;
+import io.cucumber.java.Scenario;
+import io.cucumber.spring.CucumberContextConfiguration;
+import io.restassured.config.LogConfig;
+import io.restassured.module.mockmvc.RestAssuredMockMvc;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.io.IOException;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@ActiveProfiles(profiles = {"acceptance-test"})
+@AutoConfigureMockMvc
+@CucumberContextConfiguration
+public class ScenarioHooks {
+
+ @Autowired
+ private ScenarioContextUI uiContext;
+
+ @Autowired
+ private ScenarioContextApi apiContext;
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Before("@ui")
+ public void setupForUI() {
+ uiContext.getWebDriver();
+ }
+
+ @After("@ui")
+ public void tearDownForUi(Scenario scenario) throws IOException {
+ uiContext.getReport().write(scenario);
+ uiContext.getReport().captureScreenShot(scenario, uiContext.getWebDriver());
+ uiContext.getWebDriver().quit();
+ }
+
+ @Before("@api")
+ public void setupForApi() {
+ RestAssuredMockMvc.mockMvc(mvc);
+ RestAssuredMockMvc.config = RestAssuredMockMvc.config()
+ .logConfig(new LogConfig(
+ apiContext.getReport().getRestLogPrintStream(),
+ true));
+ }
+
+ @After("@api")
+ public void tearDownForApi(Scenario scenario) throws IOException {
+ apiContext.getReport().write(scenario);
+ }
+}
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioReport.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioReport.java
new file mode 100644
index 0000000000..58ad44de89
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/commonutil/ScenarioReport.java
@@ -0,0 +1,44 @@
+package com.baeldung.cucumber_tags.acceptance.commonutil;
+
+
+import io.cucumber.java.Scenario;
+import org.openqa.selenium.OutputType;
+import org.openqa.selenium.TakesScreenshot;
+import org.openqa.selenium.WebDriver;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ScenarioReport {
+
+ private List messages = new ArrayList<>();
+ private ByteArrayOutputStream restLogOutputStream = new ByteArrayOutputStream();
+
+ public void addMessage(String message) {
+ messages.add(message);
+ }
+
+ public PrintStream getRestLogPrintStream() {
+ return new PrintStream(restLogOutputStream);
+ }
+
+ public void write(Scenario scenario) throws IOException {
+ for (String msg : messages) {
+ scenario.log(msg);
+ }
+ scenario.log(restLogOutputStream.toString());
+ restLogOutputStream.close();
+ }
+
+ public void captureScreenShot(Scenario scenario, WebDriver driver) {
+ try {
+ final byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
+ scenario.attach(screenshot, "image/png", "test");
+ } catch (Exception exception) {
+ scenario.log(exception.toString());
+ }
+ }
+}
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/pages/Page.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/pages/Page.java
new file mode 100644
index 0000000000..5ee9556c28
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/pages/Page.java
@@ -0,0 +1,28 @@
+package com.baeldung.cucumber_tags.acceptance.ui.pages;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.openqa.selenium.support.ui.ExpectedConditions.titleIs;
+
+public class Page {
+ private static final long DEFAULT_WAIT_SECONDS = 5;
+ private static Page currentPage;
+ final WebDriver driver;
+
+ Page(WebDriver driver, String title) {
+ currentPage = this;
+ this.driver = driver;
+ getWait().until(titleIs(title));
+ }
+
+ public static T getPage(Class pageClass) {
+ return pageClass.cast(checkNotNull(currentPage));
+ }
+
+ WebDriverWait getWait() {
+ return new WebDriverWait(driver, DEFAULT_WAIT_SECONDS);
+ }
+
+}
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/pages/RandomNumberGeneratorPage.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/pages/RandomNumberGeneratorPage.java
new file mode 100644
index 0000000000..24e4748313
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/pages/RandomNumberGeneratorPage.java
@@ -0,0 +1,41 @@
+package com.baeldung.cucumber_tags.acceptance.ui.pages;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+public class RandomNumberGeneratorPage extends Page {
+
+ @FindBy(id = "min")
+ private WebElement minField;
+
+ @FindBy(id = "max")
+ private WebElement maxField;
+
+ @FindBy(id = "generate")
+ private WebElement generateButton;
+
+ @FindBy(id = "result")
+ private WebElement result;
+
+ public RandomNumberGeneratorPage(WebDriver driver) {
+ super(driver, "Random Number Generator");
+ }
+
+ public void enterMinField(String min) {
+ minField.sendKeys(min);
+ }
+
+ public void enterMaxField(String max) {
+ maxField.sendKeys(max);
+ }
+
+ public void pressGenerateButton() {
+ generateButton.click();
+ }
+
+ public String getResult() {
+ return result.getText();
+ }
+}
+
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/steps/RandomNumberGeneratorSteps.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/steps/RandomNumberGeneratorSteps.java
new file mode 100644
index 0000000000..d7856c4c5e
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/acceptance/ui/steps/RandomNumberGeneratorSteps.java
@@ -0,0 +1,49 @@
+package com.baeldung.cucumber_tags.acceptance.ui.steps;
+
+import com.baeldung.cucumber_tags.acceptance.commonutil.ScenarioContextUI;
+import com.baeldung.cucumber_tags.acceptance.ui.pages.RandomNumberGeneratorPage;
+import io.cucumber.java.en.And;
+import io.cucumber.java.en.Given;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+import org.openqa.selenium.support.PageFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static com.baeldung.cucumber_tags.acceptance.ui.pages.Page.getPage;
+import static org.junit.Assert.assertTrue;
+
+public class RandomNumberGeneratorSteps {
+
+ @Autowired
+ private ScenarioContextUI context;
+
+ @Given("we are expecting a random number between min and max")
+ public void expectingRandomNumberBetweenMinAndMax() {
+ }
+
+ @And("I am on random-number-generator page")
+ public void iAmOnRandomNumberGeneratorPage() {
+ context.getWebDriver().get(context.getRandomNumberUrl());
+ PageFactory.initElements(context.getWebDriver(), RandomNumberGeneratorPage.class);
+ }
+
+ @When("^I enter min ([^\"]*)$")
+ public void whenIenterMin(String min) {
+ getPage(RandomNumberGeneratorPage.class).enterMinField(min);
+ }
+
+ @When("^I enter max ([^\"]*)$")
+ public void whenIenterMax(String max) {
+ getPage(RandomNumberGeneratorPage.class).enterMaxField(max);
+ }
+
+ @And("^I press Generate button")
+ public void pressScanButton() {
+ getPage(RandomNumberGeneratorPage.class).pressGenerateButton();
+ }
+
+ @Then("I should receive a random number between {int} and {int}")
+ public void iShouldReceiveARandomNumberBetweenAnd(int min, int max) {
+ assertTrue(Integer.parseInt(getPage(RandomNumberGeneratorPage.class).getResult()) >= min && Integer.parseInt(getPage(RandomNumberGeneratorPage.class).getResult()) <= max);
+ }
+}
diff --git a/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/service/RandomNumberGeneratorServiceUnitTest.java b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/service/RandomNumberGeneratorServiceUnitTest.java
new file mode 100644
index 0000000000..7adbffba0c
--- /dev/null
+++ b/testing-modules/cucumber/src/test/java/com/baeldung/cucumber_tags/service/RandomNumberGeneratorServiceUnitTest.java
@@ -0,0 +1,18 @@
+package com.baeldung.cucumber_tags.service;
+
+import org.junit.jupiter.api.Test;
+
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class RandomNumberGeneratorServiceUnitTest {
+
+ private final RandomNumberGeneratorService tested = new RandomNumberGeneratorService();
+
+ @Test
+ public void generateRandomNumberReturnsOK() {
+
+ int actual = tested.generateRandomNumber(1,5);
+ assertTrue(actual>=1 && actual<=5);
+ }
+}
\ No newline at end of file
diff --git a/testing-modules/cucumber/src/test/resources/com/baeldung/cucumber_tags/acceptance/api/health.feature b/testing-modules/cucumber/src/test/resources/com/baeldung/cucumber_tags/acceptance/api/health.feature
new file mode 100644
index 0000000000..b1d54fcd14
--- /dev/null
+++ b/testing-modules/cucumber/src/test/resources/com/baeldung/cucumber_tags/acceptance/api/health.feature
@@ -0,0 +1,7 @@
+@api
+Feature: Health check
+
+ Scenario: Should have a working health check
+ When I make a GET call on /status
+ Then I should receive 200 response status code
+ And should receive a non-empty body
\ No newline at end of file
diff --git a/testing-modules/cucumber/src/test/resources/com/baeldung/cucumber_tags/acceptance/ui/random-number.feature b/testing-modules/cucumber/src/test/resources/com/baeldung/cucumber_tags/acceptance/ui/random-number.feature
new file mode 100644
index 0000000000..07cced0580
--- /dev/null
+++ b/testing-modules/cucumber/src/test/resources/com/baeldung/cucumber_tags/acceptance/ui/random-number.feature
@@ -0,0 +1,10 @@
+@ui
+Feature: UI - Random Number Generator
+
+ Scenario: Successfully generate a random number
+ Given we are expecting a random number between min and max
+ And I am on random-number-generator page
+ When I enter min 1
+ And I enter max 10
+ And I press Generate button
+ Then I should receive a random number between 1 and 10
\ No newline at end of file
diff --git a/testing-modules/cucumber/src/test/resources/junit-platform.properties b/testing-modules/cucumber/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000000..7b1808c0bf
--- /dev/null
+++ b/testing-modules/cucumber/src/test/resources/junit-platform.properties
@@ -0,0 +1 @@
+cucumber.plugin=pretty, json:target/cucumber/cucumber.json
\ No newline at end of file
diff --git a/testing-modules/pom.xml b/testing-modules/pom.xml
index b467b3c503..b4b8c94a26 100644
--- a/testing-modules/pom.xml
+++ b/testing-modules/pom.xml
@@ -42,6 +42,7 @@
junit-4
testing-libraries
powermock
+ spring-junit5-cucumber