diff --git a/pom.xml b/pom.xml
index cdd6c4522..4cca338b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,6 +119,49 @@
spring-boot-devtools
true
+
+
+
+ io.cucumber
+ cucumber-java
+ 6.10.3
+ test
+
+
+ io.cucumber
+ cucumber-junit
+ 6.10.3
+ test
+
+
+ io.cucumber
+ cucumber-spring
+ 6.10.3
+ test
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ 3.141.59
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ ${junit-jupiter.version}
+ test
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ 4.4.0
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.19.0
+ test
+
diff --git a/src/test/java/org/springframework/samples/petclinic/integration/Browser.java b/src/test/java/org/springframework/samples/petclinic/integration/Browser.java
new file mode 100644
index 000000000..54882b0f1
--- /dev/null
+++ b/src/test/java/org/springframework/samples/petclinic/integration/Browser.java
@@ -0,0 +1,43 @@
+package org.springframework.samples.petclinic.integration;
+
+import java.util.concurrent.TimeUnit;
+
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeDriverService;
+import org.openqa.selenium.chrome.ChromeOptions;
+
+import io.github.bonigarcia.wdm.WebDriverManager;
+
+public class Browser {
+ public final static WebDriver webDriver = createDriver();
+
+ private static WebDriver createDriver() {
+ setupDriver();
+ ChromeDriver chromeDriver = new ChromeDriver(
+ new ChromeDriverService.Builder().withSilent(true).build(),
+ chromeOptions()
+ );
+ chromeDriver.manage().window().setSize(new Dimension(1024, 768));
+ chromeDriver.manage().timeouts().implicitlyWait(0L, TimeUnit.SECONDS);
+ return chromeDriver;
+ }
+
+ private static void setupDriver() {
+ WebDriverManager
+ .chromedriver()
+ .timeout(60)
+ .setup();
+ }
+
+ private static ChromeOptions chromeOptions() {
+ final ChromeOptions chromeOptions = new ChromeOptions();
+ chromeOptions.addArguments("enable-automation");
+ chromeOptions.addArguments("--no-sandbox");
+ chromeOptions.addArguments("--disable-infobars");
+ chromeOptions.addArguments("--disable-dev-shm-usage");
+ chromeOptions.addArguments("--disable-browser-side-navigation");
+ return chromeOptions;
+ }
+}
diff --git a/src/test/java/org/springframework/samples/petclinic/integration/CucumberRunner.java b/src/test/java/org/springframework/samples/petclinic/integration/CucumberRunner.java
new file mode 100644
index 000000000..6b28fc7db
--- /dev/null
+++ b/src/test/java/org/springframework/samples/petclinic/integration/CucumberRunner.java
@@ -0,0 +1,16 @@
+package org.springframework.samples.petclinic.integration;
+
+import org.junit.runner.RunWith;
+
+import io.cucumber.junit.Cucumber;
+import io.cucumber.junit.CucumberOptions;
+
+
+@RunWith(Cucumber.class)
+@CucumberOptions(
+ plugin = { "pretty", "html:target/cucumber-html-report" },
+ glue = { "org.springframework.samples.petclinic.integration" },
+ features = "classpath:scenarios"
+)
+public class CucumberRunner {
+}
diff --git a/src/test/java/org/springframework/samples/petclinic/integration/SpringIntegrationTest.java b/src/test/java/org/springframework/samples/petclinic/integration/SpringIntegrationTest.java
new file mode 100644
index 000000000..a59c16b26
--- /dev/null
+++ b/src/test/java/org/springframework/samples/petclinic/integration/SpringIntegrationTest.java
@@ -0,0 +1,10 @@
+package org.springframework.samples.petclinic.integration;
+
+import org.springframework.boot.test.context.SpringBootTest;
+
+import io.cucumber.spring.CucumberContextConfiguration;
+
+@CucumberContextConfiguration
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+public class SpringIntegrationTest {
+}
diff --git a/src/test/java/org/springframework/samples/petclinic/integration/StepDefinition.java b/src/test/java/org/springframework/samples/petclinic/integration/StepDefinition.java
new file mode 100644
index 000000000..db06aaefd
--- /dev/null
+++ b/src/test/java/org/springframework/samples/petclinic/integration/StepDefinition.java
@@ -0,0 +1,51 @@
+package org.springframework.samples.petclinic.integration;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import io.cucumber.java.en.Given;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+
+public class StepDefinition extends SpringIntegrationTest {
+ private final WebDriver webDriver = Browser.webDriver;
+
+ @Given("^I go to the main page$")
+ public void mainPage() {
+ webDriver.navigate().to("http://localhost:8080");
+ }
+
+ @When("^I click on the link with title \"([^\"]*)\"$")
+ public void clickOnLinkWithTitle(String linkTitle) {
+ By elementSelector = By.xpath(String.format("//*[@title='%s']", linkTitle));
+ new WebDriverWait(webDriver, 60L).until(driver -> driver.findElement(elementSelector).isDisplayed());
+ WebElement webElement = webDriver.findElement(elementSelector);
+ webElement.click();
+ }
+
+ @Then("^I should see the \"([^\"]*)\" page$")
+ public void shouldSeeThePage(String pageTitle) {
+ By elementSelector = By.tagName("h2");
+ new WebDriverWait(webDriver, 60L).until(driver -> driver.findElement(elementSelector).isDisplayed());
+ WebElement webElement = webDriver.findElement(elementSelector);
+ assertThat(webElement.getText()).isEqualTo(pageTitle);
+ }
+
+ @When("^I fill the field named \"([^\"]*)\" with value \"([^\"]*)\"$")
+ public void fillInputBoxWithValue(String name, String value) {
+ By elementSelector = By.name(name);
+ WebElement webElement = webDriver.findElement(elementSelector);
+ webElement.sendKeys(value);
+ }
+
+ @When("^I submit the form \"([^\"]*)\"$")
+ public void submitForm(String id) {
+ By elementSelector = By.id(id);
+ WebElement webElement = webDriver.findElement(elementSelector);
+ webElement.submit();
+ }
+}
diff --git a/src/test/resources/scenarios/AddOwners.feature b/src/test/resources/scenarios/AddOwners.feature
new file mode 100644
index 000000000..e69f35b0a
--- /dev/null
+++ b/src/test/resources/scenarios/AddOwners.feature
@@ -0,0 +1,3 @@
+Feature: Add owners
+
+ # Add scenarios covering all cases.
diff --git a/src/test/resources/scenarios/FindOwners.feature b/src/test/resources/scenarios/FindOwners.feature
new file mode 100644
index 000000000..1ca6624b6
--- /dev/null
+++ b/src/test/resources/scenarios/FindOwners.feature
@@ -0,0 +1,14 @@
+Feature: Find owners
+
+ Scenario: Find owners page
+ When I click on the link with title "find owners"
+ Then I should see the "Find Owners" page
+
+ Scenario: Should find an owner
+ When I fill the field named "lastName" with value "Franklin"
+ Then I should see the "Owner Information" page
+
+ Scenario: Should find multiple owners
+ When I fill the field named "lastName" with value "Davis"
+ Then I should see the "Owners" page
+ # Add additional checks here.
diff --git a/src/test/resources/scenarios/MainPage.feature b/src/test/resources/scenarios/MainPage.feature
new file mode 100644
index 000000000..eebeae133
--- /dev/null
+++ b/src/test/resources/scenarios/MainPage.feature
@@ -0,0 +1,5 @@
+Feature: Main page
+
+ Scenario: Main page
+ When I go to the main page
+ Then I should see the "Welcome" page