Selenium Spring Boot Cucumber Junit 5 Test Automation Project

Hi all, I will explain a sample test automation project with Selenium and Spring Boot in this article. It is a combination of the latest technologies in test automation. First, I will start with some Spring boot terminology, and then we will use them in our Selenium project. Our test site is an e-commerce website n11.com, and we will have scenarios for invalid login. Let’s Start!

Tech Stack

  • Java 17
  • Selenium 4.x
  • JUnit 5.x (Jupiter)
  • Cucumber 7.x
  • Spring Boot 3.0.1

Summary of Spring Boot Features for the Selenium Automation Project

The project I will explain in this article will have Spring Boot’s features like Dependency Inversion, Spring profiles, Aspect-Oriented Programming (AOP), Parallel test execution, BDD with Cucumber, Selenium Grid, and more.

Spring Boot Quick Start Guide for Test Automation

First, we need to add the dependencies below in our pom.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.swtestacademy</groupId>
    <artifactId>selenium-springboot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <maven.surefire.version>3.0.0-M8</maven.surefire.version>
        <java.version>17</java.version>
        <selenium.version>4.8.0</selenium.version>
        <junit.jupiter.version>5.9.2</junit.jupiter.version>
        <junit.platform.version>1.9.2</junit.platform.version>
        <testng.version>7.7.1</testng.version>
        <lombok.version>1.18.24</lombok.version>
        <cucumber.version>7.10.1</cucumber.version>
    </properties>

    <dependencies>
        <!--Spring Boot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--Selenium-->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>

        <!--JUnit5-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.jupiter.version}</version>
        </dependency>

        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <version>${junit.platform.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <version>${junit.platform.version}</version>
            <scope>test</scope>
        </dependency>

        <!--Cucumber-->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>

        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-testng</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit-platform-engine</artifactId>
            <version>${cucumber.version}</version>
        </dependency>

        <!--TestNG-->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven.surefire.version}</version>
                <configuration>
                    <reportFormat>plain</reportFormat>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

You can also start a spring boot project via https://start.spring.io/, and then you will add the other testing dependencies, which you can find in the pom.xml above.

Note: I have used 3.0.1 version and updated the pom.xml on 10 Jan 2023.

We need a spring boot application class in the main package below.

@SpringBootApplication()
public class SpringSeleniumApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringSeleniumApplication.class, args);
    }
}

Below are the Spring Boot annotations that I used in the project.

@Autowired: This annotation automatically creates the instances declared with @Component annotation with @Bean annotation. It does the dependency inversion-related operations behind the scenes.

@PostConstruct: It is a part of the spring bean life cycle, and it does the operations after the post-construction of the beans. @Postconstruct annotated method will be invoked after the bean has been constructed using the default constructor and just before its instance is returned to requesting object.

@Component: @Component is an annotation allowing Spring to detect our custom beans automatically.

@Lazy: @Lazy annotation indicates whether a bean is to be lazily initialized. It can be used on @Component and @Bean definitions. A @Lazy bean is not initialized until referenced by another bean or explicitly retrieved from BeanFactory. Beans that are not annotated with @Lazy are initialized eagerly.

@Bean: Spring @Bean annotation tells that a method produces a bean to be managed by the Spring container. It is a method-level annotation. During Java configuration (@Configuration), the method is executed, and its return value is registered as a bean within a BeanFactory.

@Configuration: @Configuration tags the class as a source of bean definitions for the application context.

@Value: Spring @Value annotation assigns default values to variables and method arguments. We can read spring environment variables and system variables using @Value annotation.

@Profile: This annotation loads the specific profiles. Profiles are a core feature of the framework, allowing us to map our beans to different profiles, for example, dev, test, stage, etc.

Custom Annotations

I used some custom annotations to gather multiple annotations in one annotation. Below are some examples of custom annotations.

@Lazy and @Autowired annotation combination, and I named it as @LazyAutowired:

@Lazy
@Autowired
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LazyAutowired {
}

@Lazy and @Component annotation combination was named as @LazyComponent:

@Lazy
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LazyComponent {
}

@ElapsedTime – Spring AOP:

@Documented
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ElapsedTime {
}

@TakeScreenshot – Spring AOP:

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TakeScreenshot {
}

For configurations:

@Lazy
@Configuration
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LazyConfiguration {
}

For Tests:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = SpringSeleniumApplication.class)
public @interface SeleniumTest {
}

For parallel test execution:

@Bean
@Scope("webdriverscope")
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WebdriverScopeBean {
}

Spring Boot Selenium Test Automation Classes

Let’s start with Configurations. We need to define the objects from external libraries as beans like WebDriver instances in the configurations.

WebdriverConfig Class

@Profile(“!grid”) tells us that this configuration works when the profile is not the grid.

@WebdriverScopeBean is for parallel test execution.

@ConditionalOnProperty annotation helps us use the specific beans based on the properties in the configuration properties file which is application.properties in the resource folder.

@Primary annotation gives a higher preference to a bean when there are multiple beans of the same type.

@ConditionalMissingBean annotation lets a bean be included based on the absence of specific beans. 

@Profile("!grid")
@LazyConfiguration
public class WebDriverConfig {
    @WebdriverScopeBean
    @ConditionalOnProperty(name = "browser", havingValue = "firefox")
    @Primary
    public WebDriver firefoxDriver() {
        FirefoxOptions firefoxOptions = new FirefoxOptions();
        Proxy proxy = new Proxy();
        proxy.setAutodetect(false);
        proxy.setNoProxy("no_proxy-var");
        firefoxOptions.setCapability("proxy", proxy);
        return new FirefoxDriver(firefoxOptions);
    }

    @WebdriverScopeBean
    @ConditionalOnProperty(name = "browser", havingValue = "edge")
    @Primary
    public WebDriver edgeDriver() {
        return new EdgeDriver();
    }

    @WebdriverScopeBean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "browser", havingValue = "chrome")
    @Primary
    public WebDriver chromeDriver() {
        return new ChromeDriver();
    }
}

WebDriverWaitConfig Class

This class is for auto-wiring the WebdriverWait instance in the tests. The default timeout duration is set as 30 seconds, and for parallel test execution, the bean is annotated with @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE). A bean with the prototype scope will return a different instance every time it is requested from the container. 

@LazyConfiguration
public class WebDriverWaitConfig {

    @Value("${default.timeout:30}")
    private int timeout;

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public WebDriverWait webdriverWait(WebDriver driver) {
        return new WebDriverWait(driver, Duration.ofMillis(this.timeout));
    }
}

RemoteWebDriverConfig Class

@Profile(“grid”) annotation is for Selenium Grid and remotewebdriver. When we run the tests with “spring.profiles.active=grid” environment variable, the tests will use application-grid.properties file under the resources folder as the main configuration file.

spring boot selenium remotewebdriver profile selection

spring profiles

@Profile("grid")
@LazyConfiguration
public class RemoteWebDriverConfig {
    @Value("${selenium.grid.url}")
    private URL url;

    @WebdriverScopeBean
    @ConditionalOnProperty(name = "browser", havingValue = "firefox")
    @Primary
    public WebDriver remoteFirefoxDriver(){
        FirefoxOptions firefoxOptions = new FirefoxOptions();
        return new RemoteWebDriver(this.url, firefoxOptions);
    }

    @WebdriverScopeBean
    @ConditionalOnProperty(name = "browser", havingValue = "edge")
    @Primary
    public WebDriver remoteEdgeDriver(){
        EdgeOptions edgeOptions = new EdgeOptions();
        return new RemoteWebDriver(this.url, edgeOptions);
    }

    @WebdriverScopeBean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "browser", havingValue = "chrome")
    @Primary
    public WebDriver remoteChromeDriver(){
        ChromeOptions chromeOptions = new ChromeOptions();
        return new RemoteWebDriver(this.url, chromeOptions);
    }
}

OtherBeansConfigs Class

For the other beans, I used this class. The class below has the JavaScriptExecutor bean.

@LazyConfiguration
public class OtherBeansConfigs {
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public JavascriptExecutor javascriptExecutor(WebDriver driver) {
        return (JavascriptExecutor) driver;
    }
}

For parallel test execution, I have three scope classes as follows.

WebdriverScope Class

It extends the SimpleThreadScope based on the webdriver session and cleans the threadScope map. It is a ThreadLocal map inside SimpleThreadScope class.

public class WebdriverScope extends SimpleThreadScope {
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object o = super.get(name, objectFactory);
        SessionId sessionId = ((RemoteWebDriver)o).getSessionId();
        if(Objects.isNull(sessionId)){
            super.remove(name);
            o = super.get(name, objectFactory);
        }
        return o;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }
}

WebdriverScopeConfig Class

It is a configuration class, and it creates the WebdriverScopePostProcessor bean. 

@Configuration
public class WebdriverScopeConfig {
    @Bean
    public static BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return new WebdriverScopePostProcessor();
    }
}

WebdriverScopePostProcessor Class

This class implements the BeanFactoryPostProcessor functional interface and registers the scope as “webdriverscope” by using WebDriverScope class as an argument.

public class WebdriverScopePostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope("webdriverscope", new WebdriverScope());
    }
}

Let’s continue with Page Classes.

BasePage Class

This is the base class for all pages, and all pages extend the BasePage class. Here I autowired the WebDriver, WebDriverWait, JavascripExecutor, LogUtil classes and in this way, I can use the instances of these classes. Also, with @PostConstruct annotation after the creation of this page, I initiated the elements with “PageFactory.initElements(this.driver, this);” this line for PageFactory usage.

package com.swtestacademy.springbootselenium.pages;

import com.swtestacademy.springbootselenium.utils.LogUtil;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

public abstract class BasePage {
    @Autowired
    protected WebDriver driver;

    @Autowired
    protected WebDriverWait wait;

    @Autowired
    protected JavascriptExecutor javascriptExecutor;

    @Autowired
    protected LogUtil logUtil;

    @PostConstruct
    private void init() {
        PageFactory.initElements(this.driver, this);
    }

    public abstract boolean isAt();

    public <T> void waitElement(T elementAttr) {
        if (elementAttr
            .getClass()
            .getName()
            .contains("By")) {
            wait.until(ExpectedConditions.presenceOfElementLocated((By) elementAttr));
        } else {
            wait.until(ExpectedConditions.visibilityOf((WebElement) elementAttr));
        }
    }

    public <T> void waitElements(T elementAttr) {
        if (elementAttr
            .getClass()
            .getName()
            .contains("By")) {
            wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy((By) elementAttr));
        } else {
            wait.until(ExpectedConditions.visibilityOfAllElements((WebElement) elementAttr));
        }
    }

    //Click Method by using JAVA Generics (You can use both By or Web element)
    public <T> void click(T elementAttr) {
        waitElement(elementAttr);
        if (elementAttr
            .getClass()
            .getName()
            .contains("By")) {
            driver
                .findElement((By) elementAttr)
                .click();
        } else {
            ((WebElement) elementAttr).click();
        }
    }

    public void jsClick(By by) {
        javascriptExecutor.executeScript("arguments[0].click();", wait.until(ExpectedConditions.visibilityOfElementLocated(by)));
    }

    //Write Text by using JAVA Generics (You can use both By or WebElement)
    public <T> void writeText(T elementAttr, String text) {
        waitElement(elementAttr);
        if (elementAttr
            .getClass()
            .getName()
            .contains("By")) {
            wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy((By) elementAttr));
            driver
                .findElement((By) elementAttr)
                .sendKeys(text);
        } else {
            wait.until(ExpectedConditions.visibilityOf((WebElement) elementAttr));
            ((WebElement) elementAttr).sendKeys(text);
        }
    }

    //Read Text by using JAVA Generics (You can use both By or WebElement)
    public <T> String readText(T elementAttr) {
        if (elementAttr
            .getClass()
            .getName()
            .contains("By")) {
            return driver
                .findElement((By) elementAttr)
                .getText();
        } else {
            return ((WebElement) elementAttr).getText();
        }
    }

    @SneakyThrows
    public <T> String readTextErrorMessage(T elementAttr) {
        Thread.sleep(2000); //This needs to be improved.
        return driver
            .findElement((By) elementAttr)
            .getText();
    }

    //Close popup if exists
    public void handlePopup(By by) throws InterruptedException {
        waitElements(by);
        List<WebElement> popup = driver.findElements(by);
        if (!popup.isEmpty()) {
            popup
                .get(0)
                .click();
            Thread.sleep(200);
        }
    }
}

HomePage Class

Homepage class extends the BasePage class and does the related operations for the Homepage of n11.com like “goToHomePage()” and “goToLoginPage()”. I also overrode the isAt method of the BasePage class to wait for the HomePage class to load completely.

@LazyComponent
public class HomePage extends BasePage {
    @Autowired
    LoginPage loginPage;

    @Value("${application.url}")
    private String baseURL;

    //*********Web Elements By Using Page Factory*********
    @FindBy(how = How.CLASS_NAME, using = "btnSignIn")
    public WebElement signInButton;

    //*********Page Methods*********
    //Go to Homepage
    public HomePage goToHomePage() {
        driver.get(baseURL);
        return this;
    }

    //Go to LoginPage
    public HomePage goToLoginPage() {
        click(signInButton);
        return this;
    }

    @Override
    public boolean isAt() {
        return this.wait.until((d) -> this.signInButton.isDisplayed());
    }
}

LoginPage Class

LoginPage class extends the BasePage class and does the login page-related operations.

@LazyComponent
public class LoginPage extends BasePage {
    //********* Web Elements by using Page Factory *********
    @FindBy(how = How.ID, using = "email")
    public WebElement userName;

    @FindBy(how = How.ID, using = "password")
    public WebElement password;

    //********* Web Elements by using By Class *********
    By loginButtonBy          = By.id("loginButton");
    By errorMessageUsernameBy = By.xpath("//*[@id=\"loginForm\"]/div[1]/div/div");
    By errorMessagePasswordBy = By.xpath("//*[@id=\"loginForm\"]/div[2]/div/div");
    By errorMessagePasswordCssBy = By.cssSelector("div[data-errormessagefor='password'] > .errorText");

    //*********Page Methods*********
    public LoginPage login(String userName, String password) {
        writeText(this.userName, userName);
        writeText(this.password, password);
        jsClick(loginButtonBy);
        return this;
    }

    public LoginPage verifyLoginUserNameErrorMessage(String expectedText) {
        assertEquals(expectedText, readText(errorMessageUsernameBy));
        return this;
    }

    public LoginPage verifyPasswordErrorMessage(String expectedText) {
        assertEquals(expectedText, readText(errorMessagePasswordBy));
        return this;
    }

    public LoginPage verifyPasswordErrorMessageWithCss(String expectedText) {
        assertEquals(expectedText, readTextErrorMessage(errorMessagePasswordCssBy));
        return this;
    }

    public LoginPage verifyLogEntryFailMessage() {
        logUtil.isLoginErrorLog(driver);
        return this;
    }

    @Override public boolean isAt() {
        return this.wait.until((d) -> this.userName.isDisplayed());
    }
}

We will continue with the Steps class, and we have only LoginSteps class for our scenario.

LoginSteps Class

As seen below, in LoginSteps class, I auto-wired the page classes and defined the scenario methods. The @ElapsedTime and @TakeScreenshot annotations are Spring AOP (aspect-oriented programming) annotations which I will share their details later. I also read the browser value from the configuration properties by using @Value annotation of the spring framework.

@LazyComponent
public class LoginSteps {
    @Value("${browser}")
    private String browser;

    @LazyAutowired
    HomePage homePage;

    @LazyAutowired
    LoginPage loginPage;

    public LoginSteps givenIAmAtLoginPage() {
        homePage
            .goToHomePage()
            .goToLoginPage();
        return this;
    }

    @ElapsedTime
    public LoginSteps whenILogin(String userName, String password) {
        loginPage
            .login(userName, password);
        return this;
    }

    public LoginSteps thenIVerifyUserNameErrorMessages(String expected) {
        loginPage
            .verifyLoginUserNameErrorMessage(expected);
        return this;
    }

    @TakeScreenshot
    public LoginSteps thenIVerifyInvalidLoginMessage() {
        if(!browser.equalsIgnoreCase("firefox")) {
            loginPage
                .verifyLogEntryFailMessage();
        } else {
            loginPage.verifyPasswordErrorMessageWithCss("E-posta adresiniz veya şifreniz hatalı");
        }
        return this;
    }

    @TakeScreenshot
    public LoginSteps thenIVerifyPasswordErrorMessage(String expected) {
        loginPage
            .verifyPasswordErrorMessage(expected);
        return this;
    }

    @TakeScreenshot
    public LoginSteps thenIVerifyPasswordErrorMessageWithCss(String expected) {
        loginPage
            .verifyPasswordErrorMessageWithCss(expected);
        return this;
    }
}

I created some util classes like LogUtil, ScreenshotUtil, WindowSwitchUtil, etc. You can find all of them on the GitHub page of the project.

BrowserOps Class

This class is for preparing and getting the browser-specific options.

package com.swtestacademy.springbootselenium.utils;

import com.swtestacademy.springbootselenium.annotations.LazyComponent;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.logging.LoggingPreferences;

import java.util.logging.Level;

@LazyComponent
public class BrowserOps {
    public ChromeOptions getChromeOptions() {
        ChromeOptions chromeOptions = new ChromeOptions();
        LoggingPreferences logPrefs = new LoggingPreferences();
        logPrefs.enable(LogType.BROWSER, Level.ALL);
        logPrefs.enable(LogType.DRIVER, Level.ALL);

        chromeOptions.setCapability("goog:loggingPrefs", logPrefs);
        return chromeOptions;
    }

    public FirefoxOptions getFireFoxOptions() {
        FirefoxProfile firefoxProfile = new FirefoxProfile();
        firefoxProfile.setPreference("devtools.console.stdout.content", true);

        FirefoxOptions firefoxOptions = new FirefoxOptions();
        LoggingPreferences logPrefs = new LoggingPreferences();
        logPrefs.enable(LogType.BROWSER, Level.ALL);
        logPrefs.enable(LogType.DRIVER, Level.ALL);

        firefoxOptions
            .setProfile(firefoxProfile)
            .setCapability("moz:loggingPrefs", logPrefs);
        return firefoxOptions;
    }
}

ElementContainsText Class

This custom-expected condition class waits until the element contains a specific text.

//Custom Expected Condition Class
public class ElementContainsText implements ExpectedCondition<Boolean> {
    private final String textToFind;
    private final By     by;

    //Constructor (Set the given values)
    public ElementContainsText(final By by, final String textToFind) {
        this.by = by;
        this.textToFind = textToFind;
    }

    //Override the apply method with your own functionality
    @Override
    public Boolean apply(WebDriver webDriver) {
        //Find the element with given By method (By CSS, XPath, Name, etc.)
        WebElement element = Objects
            .requireNonNull(webDriver)
            .findElement(this.by);

        //Check that the element contains given text?
        return element
            .getText()
            .contains(this.textToFind);
    }

    //This is for log message. I override it because when test fails, it will give us a meaningful message.
    @Override
    public String toString() {
        return ": \"Does " + this.by + " contain " + this.textToFind + "?\"";
    }
}

LogUtil Class

This class is for log utility for the tests.

@LazyComponent
public class LogUtil {
    public static LogEntries getLogs(WebDriver driver) {
        return driver
            .manage()
            .logs()
            .get(LogType.BROWSER);
    }

    public void isLoginErrorLog(WebDriver driver) {
        //Check logs (works only Chrome and Edge)
        LogEntries logEntries = driver
            .manage()
            .logs()
            .get(LogType.BROWSER);
        Assert.assertTrue(logEntries
            .getAll()
            .stream()
            .anyMatch(logEntry -> logEntry
                .getMessage()
                .contains("An invalid email address was specified")));
    }
}

ScreenshotUtil Class

This utility class is responsible for taking screenshots.

@LazyComponent
public class ScreenshotUtil {
    @Autowired
    private ApplicationContext ctx;

    @Value("${screenshot.path}")
    private Path path;

    public void takeScreenShot(String testName) throws IOException {
        File sourceFile = this.ctx.getBean(TakesScreenshot.class).getScreenshotAs(OutputType.FILE);
        FileCopyUtils.copy(sourceFile, this.path.resolve( testName + ".png").toFile());
    }

    public byte[] getScreenshot(){
        return this.ctx.getBean(TakesScreenshot.class).getScreenshotAs(OutputType.BYTES);
    }

}

WindowSwitchUtil Class

This class is for switching windows.

@LazyComponent
public class WindowSwitchUtil {
    @Autowired
    private ApplicationContext ctx;

    public void switchByWindowTitle(final String title) {
        WebDriver driver = this.ctx.getBean(WebDriver.class);

        driver
            .getWindowHandles()
            .stream()
            .map(handle -> driver
                .switchTo()
                .window(handle)
                .getTitle())
            .filter(t -> t.startsWith(title))
            .findFirst()
            .orElseThrow(() -> {
                throw new RuntimeException("There is no such window available.");
            });
    }

    public void switchByIndex(final int index) {
        WebDriver driver = this.ctx.getBean(WebDriver.class);

        String[] handles = driver
            .getWindowHandles()
            .toArray(new String[0]);

        driver
            .switchTo()
            .window(handles[index]);
    }

}

Now it is time to continue with test classes.

BaseTest Class

The base test class is annotated by @SeleniumTest annotation and the Lombok library’s @Getter annotation to get the instances from the base test class. We have here the common logger, application context instances, and by using the applicationContext’s WebDriver bean, we can quit the browser in the teardown() method.

@SeleniumTest
@Getter
public class BaseTest {
    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    @BeforeEach
    public void setup() {
    }

    @LazyAutowired
    public ApplicationContext applicationContext;

    @AfterEach
    public void teardown() {
        this.applicationContext
            .getBean(WebDriver.class)
            .quit();
    }
}

LoginTest Class

The LoginTest class extends the BaseTest class and is annotated with Junit Jupiter’s @Execution(ExecutionMode.CONCURRENT) annotation for parallel test execution. In this class, I auto-wired the LoginSteps then I used its methods to implement test scenarios for each test.

@Execution(ExecutionMode.CONCURRENT)
public class LoginTest extends BaseTest {
    @LazyAutowired
    LoginSteps loginSteps;

    @Test
    public void invalidUserNameInvalidPassword() {
        loginSteps
            .givenIAmAtLoginPage()
            .whenILogin("[email protected]", "11223344")
            .thenIVerifyInvalidLoginMessage();
    }

    @Test
    public void emptyUserEmptyPassword() {
        loginSteps
            .givenIAmAtLoginPage()
            .whenILogin("", "")
            .thenIVerifyUserNameErrorMessages("Lütfen e-posta adresinizi girin.")
            .thenIVerifyPasswordErrorMessage("Bu alanın doldurulması zorunludur.");
    }
}

Cucumber BDD with Spring Boot and Selenium

I also added Cucumber BDD support to this project. You can find all details under the cucumber package. Let’s check the classes and files under this package.

Cucumber Spring Boot Selenium

Login.Feature Feature File

Login.feature is the cucumber feature file for login tests. Here we have two negative login scenarios that are annotated by @negative tag.

Feature: Login Feature

  @negative
  Scenario Outline: I login the website with invalid username and invalid password
    Given I am on the login page
    When I try to login with "<username>" and "<password>"
    Then I verify invalid login message
    Examples:
      | username               | password |
      | [email protected] | 11223344 |


  @negative
  Scenario Outline: I login the website with empty username and empty password
    Given I am on the login page
    When I try to login with "<username>" and "<password>"
    Then I verify invalid login message
    Examples:
      | username | password |
      |          |          |

LoginSteps Class

Our step definitions are in this class. Here I auto-wired the page classes and defined the steps of our scenarios.

public class LoginSteps {
    @Value("${browser}")
    private String browser;

    @LazyAutowired
    private HomePage homePage;

    @LazyAutowired
    private LoginPage loginPage;

    @Given("I am on the login page")
    public void iAmOnTheLoginPage() {
        homePage
            .goToHomePage()
            .goToLoginPage();
    }

    @When("I try to login with {string} and {string}")
    public void iTryToLoginWithAnd(String userName, String password) {
        loginPage
            .login(userName, password);
    }

    @Then("I verify invalid login message")
    public void iVerifyInvalidLoginMessage() {
        if (!browser.equalsIgnoreCase("firefox")) {
            loginPage
                .verifyLogEntryFailMessage();
        } else {
            loginPage.verifyPasswordErrorMessageWithCss("E-posta adresiniz veya şifreniz hatalı");
        }
    }
}

Cucumber Hooks Class

Cucumber hooks are essential to do operations before or after the steps. Here I defined these operations by using Cucumber Hooks.

public class CucumberHooks {

    @LazyAutowired
    private ScreenshotUtil screenshotUtil;

    @LazyAutowired
    private ApplicationContext applicationContext;

    @AfterStep
    public void afterStep(Scenario scenario){
        if(scenario.isFailed()){
            scenario.attach(this.screenshotUtil.getScreenshot(), "image/png", scenario.getName());
        }
    }

    @After
    public void afterScenario(){
        this.applicationContext.getBean(WebDriver.class).quit();
    }

}

CucumberSpringContextConfig Class

This class is necessary for Cucumber and Spring boot integration. I annotated this class with @CucumberContextConfiguration and @SpringBootTest annotations.

@CucumberContextConfiguration
@SpringBootTest
public class CucumberSpringContextConfig {

}

RunCucumberTest Class

This class is for running the Cucumber tests. I used Cucumber JVM 7.x version and Junit Jupiter’s @Suite annotations. @SelectDirectories annotation is for the feature files, and @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = “com.swtestacademy.springbootselenium.cucumber”) is for gluing the steps files.

@Suite
@IncludeEngines("cucumber")
@SelectDirectories("src/test/java/com/swtestacademy/springbootselenium/cucumber/features")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.swtestacademy.springbootselenium.cucumber")
@ConfigurationParameter(key = Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME, value = "true")
//@ConfigurationParameter(key = Constants.FILTER_TAGS_PROPERTY_NAME, value = "@negative")
public class RunCucumberTest {
}

After implementing this class, we can run the cucumber test with the command below.

mvn -Dtest="com.swtestacademy.springbootselenium.cucumber.RunCucumberTest" test

or we can use the command below as well.

mvn clean install -Dcucumber.glue="com.swtestacademy.springbootselenium.cucumber.steps" -Dcucumber.plugin="com/swtestacademy/springbootselenium/cucumber/features"

We can also add Cucumber properties to junit-platform.properties file as shown below.

cucumber.publish.enabled=true
cucumber.glue=com.swtestacademy.springbootselenium.cucumber
cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=dynamic
cucumber.plugin=pretty, html:target/cucumber-reports/Cucumber.html, json:target/cucumber-reports/Cucumber.json, junit:target/cucumber-reports/Cucumber.xml

I will share the other properties files that I used in the project.

application.properties

The default configuration properties file is application.properties.

application.url=http://www.n11.com/
screenshot.path=/Users/onur/Desktop/temp
browser=chrome
logging.level.root=INFO
logging.file.name=${screenshot.path}/test-execution.log
default.timeout=30

#logging.pattern.file=%d %p %c{1.} [%t] %m%n
#logging.pattern.console=%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n

application-grid.properties

When we run the tests on Selenium Grid, I have to use application-grid.properties file as a configuration file.

selenium.grid.url=http://localhost:4444/wd/hub
browser=chrome

junit-platform.properties

These are the properties of JUnit Jupiter for parallel test execution and cucumber test executions.

junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=dynamic
cucumber.publish.enabled=true
cucumber.glue=com.swtestacademy.springbootselenium.cucumber
cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=dynamic
cucumber.plugin=pretty, html:target/cucumber-reports/Cucumber.html, json:target/cucumber-reports/Cucumber.json, junit:target/cucumber-reports/Cucumber.xml

Spring Aspect Oriented Programming classes in the project are ElapsedTimeAspect and ScreenshotAspect classes.

ElapsedTimeAspect Class

This class is annotated with @Aspect annotation, and it uses the @Around annotation of aspectjweaver to measure the elapsed time of the methods annotated with @ElapsedTime annotation. 

@Aspect
@Configuration
@Slf4j
public class ElapsedTimeAspect {
    @Around("@annotation(com.swtestacademy.springbootselenium.annotations.ElapsedTime)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object obj = proceedingJoinPoint.proceed();
        long duration = System.currentTimeMillis() - startTime;
        log.info("Elapsed time of {} class's {} method is {}", proceedingJoinPoint
                .getSignature()
                .getDeclaringTypeName(),
            proceedingJoinPoint
                .getSignature()
                .getName(), duration + " ms.");
        return obj;
    }
}

ScreenshotAspect Class

This class is annotated with @Aspect annotation, and it uses the @After annotation of aspectjweaver to take screenshots after the methods annotated with @TakeScreenshot annotation. 

@Aspect
@Component
public class ScreenshotAspect {
    @Autowired
    private ScreenshotUtil screenshotUtil;

    @After("@annotation(takeScreenshot)")
    public void after(JoinPoint joinPoint, TakeScreenshot takeScreenshot) throws IOException {
        this.screenshotUtil.takeScreenShot(joinPoint.getSignature().getName());
    }
}

How to Run Tests

We can run the test in the command line with the maven command below. The below command is for the zhs terminal.

mvn -Dtest="com.swtestacademy.springbootselenium.tests.**" test

The command below is for the bash terminal.

mvn -Dtest=com.swtestacademy.springbootselenium.tests.** test

If we want to select a specific profile, we have to specify this as shown below.

mvn -Dtest="com.swtestacademy.springbootselenium.tests.**" -Dspring.profiles.active=grid test

For selenium grid execution, we should activate the selenium grid by running the Selenium Docker compose file.

docker-compose -f docker-compose-v3.yml up

# To execute this docker-compose yml file use `docker-compose -f docker-compose-v3.yml up`
# Add the `-d` flag at the end for detached execution
# To stop the execution, hit Ctrl+C, and then `docker-compose -f docker-compose-v3.yml down`
version: "3"
services:
  chrome:
    image: selenium/node-chrome:4.1.2-20220131
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_MAX_SESSIONS=5

  edge:
    image: selenium/node-edge:4.1.2-20220131
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_MAX_SESSIONS=5
  firefox:
    image: selenium/node-firefox:4.1.2-20220131
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_MAX_SESSIONS=5

  selenium-hub:
    image: selenium/hub:4.1.2-20220131
    container_name: selenium-hub
    ports:
      - "4442:4442"
      - "4443:4443"
      - "4444:4444"

docker compose up selenium

After this step, we can check the grid via this link:  http://localhost:4444/ui/index.html#/

docker selenium grid

And then, we can run the tests via IntelliJ or maven.

On IntelliJ, you need to specify the spring profile as grid: spring.profiles.active=grid

selenium grid execution

When tests are running, you can see the status on the dashboard.

selenium grid dashboard

selenium grid dashboard running tests

Via maven you should use the command below:

mvn -Dtest="com.swtestacademy.springbootselenium.tests.**" -Dspring.profiles.active=grid test

If you want to run the cucumber tests, you can use the command below:

mvn -Dtest="com.swtestacademy.springbootselenium.cucumber.RunCucumberTest" test

or

mvn clean install -Dcucumber.glue="com.swtestacademy.springbootselenium.cucumber.steps" -Dcucumber.plugin="com/swtestacademy/springbootselenium/cucumber/features"

On IntelliJ, you can right-click the “RunCucumberTest” class and run the tests.

run cucumber tests in junit 5 and cucumber jvm7 and spring boot

That’s all for this article. It is a long yet informative and comprehensive article.

GitHub Project for Selenium Spring Boot Cucumber Test Automation Project

https://github.com/swtestacademy/selenium-springboot/tree/junit-springboot-selenium

Thanks for reading.
Onur Baskirt

63 thoughts on “Selenium Spring Boot Cucumber Junit 5 Test Automation Project”

  1. This is pretty neat!! Nice blog 👍
    How is your set-up different from the usual Selenium+JUnit+Cucumber+Grid set-up?
    Being relatively new to Spring boot and Selenium, I am not able to comprehend the advantage of using Spring boot for web application test automation

    Reply
    • Running the grid part is the same. I used docker compose and brought the grid up and running. Implementation side, I used grid properties in the resource folder, created a configuration for the grid, and created beans in the run time with the help of spring boot.

      Spring boot has many advantages like dependency inversion (the beans/instances are created on the runtime). You do not need to instantiate the instances classical way. You need to autowire them. Also, spring boot comes with many annotations, which I shared in the article, providing many features. If you use spring boot in test automation, you will have more artillery in your arsenal, and you can do many more with the help of these.

      Reply
      • Does this project support TestNG? I see TestNG related dependencies added to maven. I was not able to configure for Selenium+SpringBoot+TestNG. May be you can assist

        Reply
  2. com.swtestacademy.springbootselenium.configuration.WebDriverConfig.java
    should have below line for Chrome to work properly
    WebDriverManager.chromedriver().setup();
    similarly for other browsers as well in their respective methods before returning the corresponding driver;

    example:
    public WebDriver chromeDriver() {
    WebDriverManager.chromedriver().setup();
    return new ChromeDriver();
    }

    Reply
  3. Hello, very details project, thank you for your work. Tell me please, when I run your example I can see colourful log evert scenario name, given, when, then and also name of class test which has all it. But in my project it is not work. Is this switchable function?

    Reply
  4. I’m learning to take advantage of Spring Boot in my UI tests with the Selenium WebDriver API.
    On that note, why does IntelliJ complain about there not being any beans of “WebDriver” type found in your BasePage class?

    Reply
  5. hi, great blog!

    what is the advantage of SimpleThreadScope if you are anyway closing the WebDriver after every test?
    Doesn’t closing the WebDriver after every test make it slow?

    Reply
    • also, maybe I am wrong, could you please give it a try?

      In my opinion, if you had for e.g. 10 test cases, all of them would spin up a new chrome instance for local. this is because I guess parallel tests use fork join pool behind the scenes, so SimpleThreadScope treats them as separate threads.

      You could maybe emulate this behavior by just copy-pasting what is there in Login.feature file 4-5 times over there itself.

      Reply
  6. Hi, my chrome version is “101.0.4951.67” but ı get error;

    “org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘homePage’: Unsatisfied dependency expressed through field ‘driver’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘chromeDriver’ defined in class path resource [com/swtestacademy/springbootselenium/configuration/WebDriverConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.openqa.selenium.WebDriver]: Factory method ‘chromeDriver’ threw exception; nested exception is java.lang.IllegalStateException: The path to the driver executable The path to the driver executable must be set by the webdriver.chrome.driver system property; for more information, see https://github.com/SeleniumHQ/selenium/wiki/ChromeDriver. The latest version can be downloaded from https://chromedriver.storage.googleapis.com/index.html

    how can resolve? Thanks.

    Reply
  7. Great article! I loved the ability to clone the project and get a complete, up-to-date setup for Selenium and Cucumber in a Spring setup.

    I second the motion to include “WebDriverManager.chromedriver().setup();” in the code, at least as commented code. The line will help people who are not too technical or have issues downloading and using a native browser.

    Also having two “steps”-directories, with two LoginSteps-classes got me confused for a bit. I prefer all steps under cucumber, but I am guessing this was just some forgotten cleanup?

    Reply
    • Thank you so much for your comment Dag. In 2023, I will also try to update the articles and the codes. :-) When I wrote the article, I used the latest technology stack. I am happy to hear that you did not face any errors.

      Reply
  8. For some reason parallelism is not working. Even though I set to 2, it is still launching as many browser windows as the number of tests to execute. Do I have to make any changes to get it working?

    Reply
    • Hi, maybe the reason is dynamic parallelism. In the below config properties, I suggest changing the strategy to fixed rather than dynamic.

      junit.jupiter.execution.parallel.enabled=true
      junit.jupiter.execution.parallel.config.strategy=dynamic
      cucumber.publish.enabled=true
      cucumber.glue=com.swtestacademy.springbootselenium.cucumber
      cucumber.execution.parallel.enabled=true
      cucumber.execution.parallel.config.strategy=dynamic

      Details:

      dynamic
      Computes the desired parallelism based on the number of available processors/cores multiplied by the junit.jupiter.execution.parallel.config.dynamic.factor configuration parameter (defaults to 1).

      fixed
      Uses the mandatory junit.jupiter.execution.parallel.config.fixed.parallelism configuration parameter as the desired parallelism.

      custom
      Allows you to specify a custom ParallelExecutionConfigurationStrategy implementation via the mandatory junit.jupiter.execution.parallel.config.custom.class configuration parameter to determine the desired configuration.

      Please try below configs:

      junit.jupiter.execution.parallel.enabled=true
      junit.jupiter.execution.parallel.config.strategy=fixed
      junit.jupiter.execution.parallel.config.fixed.parallelism = 2
      cucumber.publish.enabled=true
      cucumber.glue=com.swtestacademy.springbootselenium.cucumber
      cucumber.execution.parallel.enabled=true
      cucumber.execution.parallel.config.strategy=fixed
      cucumber.execution.parallel.config.fixed.parallelism = 2

      Reply
  9. I want to run it locally on my machine but im facing this error instead. command i used is mvn -Dtest=”com.ztools.tests.**” test

    [ERROR] invalidUserNameInvalidPassword Time elapsed: 0.598 s <<< ERROR!
    java.lang.IllegalStateException: No Scope registered for scope name 'webdriverscope'
    at com.ztools.tests.LoginTest.invalidUserNameInvalidPassword(LoginTest.java:18)

    Reply
    • I have no idea how you changed the project structure. As I see, you renamed the packages. I suggest first just clone the project and run with the command which I have shared in the article. If all is ok, then step by step rename the packages like ztools etc. It seems like a configuration error.

      Reply
      • But i have this error. I’m using chromedriver in my usr/local/bin/chromedriver. How do i set it up? It seems like it wont fire up any chromedriver nor google-chrome

        unreported exception java.lang.Throwable; must be caught or declared to be thrown

        Reply
  10. Onur Baskirt : Its great article and learned lot of things

    I am trying the dynamic way and want to cap the number of browser

    cucumber.execution.parallel.enabled=true
    cucumber.execution.parallel.config.strategy=dynamic
    cucumber.execution.parallel.config.dynamic.factor=1

    since I am running on 10 core machine , as per the documentation it should open 10 browsers and its opening many more 35+
    can you please help me is there any issues with my config

    Reply
    • If you don’t use Cucumber below one is for JUnit 5 only solution:

      I have created a custom strategy (https://github.com/swtestacademy/selenium-springboot/blob/junit-springboot-selenium/src/test/java/com/swtestacademy/springbootselenium/configuration/CustomStrategy.java) and published the main branch. Please, pull the latest code and test it. It worked on my local machine.

      Now, you can set the parallelism in junit-platform.properties file below way:

      junit.jupiter.execution.parallel.config.custom.parallelism=10

      I hope this will solve your problems. Have a good day and happy testing.

      Reply
    • and for Cucumber please use below configuration in junit-platform.properties file.

      cucumber.publish.enabled=true
      cucumber.glue=com.swtestacademy.springbootselenium.cucumber
      cucumber.execution.parallel.enabled=true
      cucumber.execution.parallel.config.strategy=custom
      cucumber.execution.parallel.config.custom.parallelism=10
      cucumber.execution.parallel.config.custom.class=com.swtestacademy.springbootselenium.configuration.CustomStrategy
      cucumber.plugin=pretty, html:target/cucumber-reports/Cucumber.html, json:target/cucumber-reports/Cucumber.json, junit:target/cucumber-reports/Cucumber.xml

      and then please run the test with the below command.

      mvn -Dtest=”com.swtestacademy.springbootselenium.cucumber.RunCucumberTest” test

      I have tested locally by changing the “cucumber.execution.parallel.config.custom.parallelism” with 1, 2, and 3 and both worked fine as expected. I hope this solves your problem.

      Reply
  11. I have multiple scenarios in one feature file like you have and I close/quit the browser after each scenario (using hooks like you have put). But at the beginning of second scenario, it throws Invalid Session error as it cannot find the valid driver object. Do you know how to resolve this?

    Exception is as below:
    org.openqa.selenium.NoSuchSessionException: invalid session id

    Reply
    • I’m having the same issue.

      Did you manage do solve it?

      Somehow it only works if the concurrent number is 2.
      If it’s only 1, the second test fails.

      I’ve tried to initialise the driver on the @BeforeEach of the BaseTest, but with no success.

      Reply
      • I have just cloned the project. Then, updated the chrome driver on my machine and updated the chrome. Reference: https://www.swtestacademy.com/install-chrome-driver-on-mac/. After, I ran the tests by right-clicking and “RunCucumberTest” file and then clicked the run. For both settings in junit-platform.properties file as shown below, it worked as expected. I could not find any problems.

        cucumber.execution.parallel.config.custom.parallelism=2
        cucumber.execution.parallel.config.custom.parallelism=1

        I could not reproduce your problems.

        Reply
  12. Hello Onur,

    I just pulled from ur branch. And run RunCucumberTest class. Found out this error. Can you please advice on it?

    \\\
    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘homePage’: Unsatisfied dependency expressed through field ‘driver’: Error creating bean with name ‘chromeDriver’ defined in class path resource [com/swtestacademy/springbootselenium/configuration/WebDriverConfig.class]: Failed to instantiate [org.openqa.selenium.WebDriver]: Factory method ‘chromeDriver’ threw exception with message: org/apache/hc/client5/http/config/ConnectionConfig

    Reply
  13. Hello, Onur!

    Thank you for such a detailed explanation.

    For the remote execution, I would like to add the test name as a capability, but I am struggling a little bit to achieve it. I have the test name on the setup method using testInfo.getDisplayName(), but I do not known how to propagate it until the getChromeOptions method, for example.

    public ChromeOptions getChromeOptions() {
    ChromeOptions chromeOptions = new ChromeOptions();
    LoggingPreferences logPrefs = new LoggingPreferences();
    logPrefs.enable(LogType.BROWSER, Level.ALL);
    logPrefs.enable(LogType.DRIVER, Level.ALL);
    chromeOptions.setCapability(“goog:loggingPrefs”, logPrefs);

    // this is what I want to achieve
    // chromeOptions.setCapability(“name”, testInfo.getDisplayName());

    return chromeOptions;
    }

    Do you have any idea if this is possible? Do you have any suggestions on how can I achieve it?

    Thank you so much!

    Reply
  14. Hi Onur Baskirt,
    I am not using Cucumber only use TestNG, how to integrate Our framework with Extend report and send a report on mail

    Reply
  15. I’m currently using Spring Boot 2.7.13 and it would seem even that early there’s no need to exclude junit-vintage-engine for Spring-boot-starter-test as you still have done with SB 3 as since some 2.x version everything in the SB dependency tree uses junit 5. Does this project setup require this for some project-specific reason?

    Reply
  16. public void takeScreenShot(String testName) throws IOException {
    File sourceFile = this.ctx.getBean(TakesScreenshot.class).getScreenshotAs(OutputType.FILE);
    FileCopyUtils.copy(sourceFile, this.path.resolve( testName + “.png”).toFile());
    }

    You take TakesScreenshot from the context. I don’t see where did you put it in context?

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.