Spring causing WebDriver to 'quit()' after each test

Viewed 71

I'm trying to get a Spring Boot Selenium app to work. I can get one test to run successfully, but when I run more than one test, the second test complains with:

Session ID is null. Using WebDriver after calling quit()?

After looking through the source code and putting in breakpoints, it looks like Spring is calling the quit() method for WebDriver after the first test.

How do I get it to not quit after each test? I've thought about just not using dependency injection for the page classes.

Here is my test class:

@SpringBootTest
public class LoginTest {

  @Autowired LoginPage loginPage;

  @Test
  public void shouldLogin() {
    loginPage.login();
  }

  @Test
  public void shouldLoginToAdminPage() {
    loginPage.adminLogin();
  }
}

My LoginPage:

@Component
public class LoginPage extends BasePage {

  @FindBy(how = How.XPATH, using = "//gat-input[@formcontrolname = 'userName']//input")
  private WebElement txtUserName;

  @FindBy(how = How.XPATH, using = "//gat-input[@formcontrolname='password']//input")
  private WebElement txtPassword;

  @FindBy(how = How.XPATH, using = "//gat-button[@label='Login']/button")
  private WebElement btnLogin;

  protected void login(String username, String password) {
    loadPage();
    sendKeysWhenReady(txtUserName, username);
    sendKeysWhenReady(txtPassword, password);
    clickWhenReady(btnLogin);
  }

  public void adminLogin() {
    login(properties.getAdminUsername(), properties.getAdminPassword());
  }

  public void login() {
    login(properties.getRegularUsername(), properties.getRegularPassword());
  }

  @Override
  public void verifyPageLoaded() {
    waitTillElementIsReady(btnLogin, 5);
  }

  @Override
  protected String getPath() {
    return "/login";
  }
}

The BasePage:

public abstract class BasePage {

  @Autowired private WebDriver driver;
  @Autowired protected Properties properties;

  @PostConstruct
  public void initDriver() {
    driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS);
    PageFactory.initElements(driver, this);
  }

  public void loadPage() {
    getDriver().get(getUrl());
    verifyPageLoaded();
  }

  public String getUrl() {
    return properties.getBaseUrl().concat(getPath());
  }

  // example "/login"
  protected abstract String getPath();

  public abstract void verifyPageLoaded();

  public WebDriver getDriver() {
    return driver;
  }

  protected void waitTillElementIsReady(WebElement webElement, int seconds) {
    WebDriverWait wait = new WebDriverWait(driver, seconds);
    wait.until(ExpectedConditions.visibilityOf(webElement));
  }

  protected void sendKeysWhenReady(WebElement webElement, String keys) {
    waitTillElementIsReady(webElement, 5);
    webElement.sendKeys(keys);
  }

  protected void clickWhenReady(WebElement webElement) {
    waitTillElementIsReady(webElement, 3);
    webElement.click();
  }
}

The Config class:

public class WebDriverLibrary {
  @Bean
  public WebDriver getChromeDriver() {
    WebDriverManager.chromedriver().setup();
    return new ChromeDriver();
  }
}

And my dependencies:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.seleniumhq.selenium:selenium-java:4.2.1'
    implementation 'io.github.bonigarcia:webdrivermanager:5.2.0'
    implementation 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.testng:testng:7.6.0'
    testImplementation 'org.testcontainers:junit-jupiter:1.17.2'
    testImplementation 'org.testcontainers:selenium:1.17.2'
}
1 Answers

I found that there are two ways to address this.

However, it all comes down to the Selenium webdriver java implementation not really being "Spring" friendly. You need a new WebDriver for each test and it needs to be shared between page objects. There is no great way to do this with @Autowired page objects.

So you can use dependency injection, but you would have to use the @DirtiesContext annotation before each test, which would create and destroy the WebDriver each time (along with all the other injected objects). However, it would also be potentially very expensive depending on how intense each test is and how big the context that is being reloaded every time would be.

The other way is to not have the page objects injected, but to create them anew before each test (jUnit: @BeforeEach, TestNG: @BeforeMethod), with the WebDriver object as a constructor argument for each so that each page object can share the WebDriver. You would also need to perform the WebDriver.quit() after each test to shut down the stale driver (jUnit: @AfterEach, TestNG: @AfterMethod).

Related