title: Java-Cucumber tests using spring dependency injection throws NullPointerException when Feature spans Step classes
body: <p>I am using a fairly typical Maven architecture, Java-Cucumber, Selenium, with Spring Dependency Injection test system set up to test a dynamic Angular front end website. (Versions in the pom.xml)<a href="https://i.stack.imgur.com/rRDLQ.png" rel="nofollow noreferrer">ArchitectureWSpringDI</a> It works really well, I can run hundreds of tests easily, but I can't &quot;DRY&quot; out the test steps the way I could with Ruby Watir. One article states that Ruby has a &quot;world&quot; object that Java is lacking, but the Spring used for Dependency Injection is supposed to solve that</p>
  <p>I've read a lot of &quot;retaining state&quot; posts, but nothing seems to apply to how this works, and a lot are several versions behind in Cucumber and Spring, though I am still using Java 8. Most of posts for retaining state seem to be between steps in a single file, in a single test.</p>
  <p>The main example, which is one of many, is I want to be able to have a steps file with my @Given I login step, and not have to put that step in a hundred other step files.</p>
  <p>If I have a feature file like this:</p>
  <pre><code>Feature: As an account holder I examine account details
  
    Scenario: View personal summary widget info on details page
      Given Log into &quot;web&quot; on &quot;dev&quot; as &quot;username&quot; with &quot;password&quot; using &quot;chrome&quot;
      When I tap the first account section
      Then I see a list of transactions
  </code></pre>
  <p>and match it with a steps file that has all the steps in it like this</p>
  <pre><code>@SpringBootTest
  public class AccountsSteps {
  
      private final MyAccountsPage page;
      @Autowired
      public AccountsSteps(MyAccountsPage page){
          this.page = page;
      }
  
      @Given(&quot;Log into {string} on {string} as {string} with {string} using {string}&quot;)
      public void logIntoOnAsWithUsing(String app, String env, String user, String pass, String browser) {
          page.loadAny(env, app, browser);
          page.sendUsername(user);
          page.sendPassword(pass);
          page.loginButtonClick();
      }
  
      @When(&quot;I tap the first account section&quot;)
      public void iTapTheFirstAccountSection() {
          page.waitForListOfElementType(WebElement);
          page.clickFirstAccountLink();
      }
  
      @Then(&quot;I see a list of transactions&quot;)
      public void iSeeAListOfTransactions() {
          By selector = By.cssSelector(&quot;div.container&quot;);
          page.waitForLocateBySelector(selector);
          Assert.assertTrue(page.hasTextOnPage(&quot;Account details&quot;));
      }
  }
  </code></pre>
  <p>Everything works great, but if I have another Feature that uses the same @Given, so the one above and below are exact, so it doesn't create a new step in the new Steps file.</p>
  <pre><code>Feature: As an account owner I wish to edit my details
  
    Scenario: My profile loads and verifies the correct member's name
      Given Log into &quot;web&quot; on &quot;dev&quot; as &quot;username&quot; with &quot;password&quot; using &quot;chrome&quot;
      When I use the link in the Self service drop down for My profile
      Then the Contact Details tab loads the proper customer name &quot;Firstname Lastname&quot;
  </code></pre>
  <p>Matched to this Step file, notice the lack of a Given step since it's using the one from the other file.</p>
  <pre><code>@SpringBootTest
  public class MyProfileSteps {
  
      private final MyProfilePage page;
      @Autowired
      public MyProfileSteps(MyProfilePage page){
          this.page = page;
      }
  
      @When(&quot;I use the link in the Self service drop down for My profile&quot;)
      public void iUseTheLinkInTheSelfServiceDropDownForMyProfile() {
          page.clickSelfServiceLink();
          page.clickMyProfileLink();
      }
  
      @Then(&quot;the Contact Details tab loads the proper customer name {string}&quot;)
      public void theContactDetailsTabLoadsTheCustomerName(String fullName) {
          System.out.println(page.getCustomerNameFromProfile().getText());
          Assert.assertTrue(page.getCustomerNameFromProfile().getText().contains(fullName));
          page.teardown();
      }
  }
  </code></pre>
  <p>I finally get to the crux of my problem. Upon switching to a step in a different step file, it throws the exception.</p>
  <pre><code>When I use the link in the Self service drop down for My profile
        java.lang.NullPointerException
      at java.util.Objects.requireNonNull(Objects.java:203)
      at org.openqa.selenium.support.ui.FluentWait.&lt;init&gt;(FluentWait.java:106)
      at org.openqa.selenium.support.ui.FluentWait.&lt;init&gt;(FluentWait.java:97)
      at projectname.pages.BasePage.waitForClickableThenClickByLocator(BasePage.java:417)
      at projectname.pages.BasePageWeb.clickSelfServiceLink(BasePageWeb.java:858)
      at projectname.steps.MyProfileSteps.iUseTheLinkInTheSelfServiceDropDownForMyProfile(MyProfileSteps.java:39)
      at ✽.I use the link in the drop down for My profile(file:///Users/name/git/project/tests/projectname/src/test/resources/projectname/features/autocomplete/my_profile.feature:10)
  
  </code></pre>
  <p>I've specifically tied them all together so a test only calls one new Selenium Instance per test, and it is definitely not opening a new browser window, it just crashes and closes.</p>
  <pre><code>public interface WebDriverInterface {
  
      WebDriver getDriver();
      WebDriver getDriverFire();
      void shutdownDriver();
      WebDriver stopOrphanSession();
  }
  </code></pre>
  <p>And there are several profiles that will run different configurations, but my main local testing WebDriverInterface looks like this.</p>
  <pre><code>@Profile(&quot;local&quot;)
  @Primary
  @Component
  public class DesktopLocalBrowsers implements WebDriverInterface {
  
      @Value(&quot;${browser.desktop.width}&quot;)
      private int desktopWidth;
  
      @Value(&quot;${browser.desktop.height}&quot;)
      private int desktopHeight;
  
      @Value(&quot;${webdriver.chrome.mac.driver}&quot;)
      private String chromedriverLocation;
  
      @Value(&quot;${webdriver.gecko.mac.driver}&quot;)
      private String firefoxdriverLocation;
  
      public WebDriver local;
      public WebDriver local2;
  
      public DesktopLocalBrowsers() {
      }
  
      @Override
      public WebDriver getDriver() {
          System.setProperty(&quot;webdriver.chrome.driver&quot;, chromedriverLocation);
          System.setProperty(&quot;webdriver.chrome.silentOutput&quot;, &quot;true&quot;);
          ChromeOptions chromeOptions = new ChromeOptions();
          chromeOptions.addArguments(&quot;--disable-extensions&quot;);
          chromeOptions.addArguments(&quot;window-size=&quot; + desktopWidth + &quot;,&quot; + desktopHeight);
          local = new ChromeDriver(chromeOptions);
          return local;
      }
  
      @Override
      public WebDriver getDriverFire() {
          System.setProperty(&quot;webdriver.gecko.driver&quot;, firefoxdriverLocation);
          FirefoxBinary firefoxBinary = new FirefoxBinary();
          FirefoxOptions firefoxOptions = new FirefoxOptions();
          firefoxOptions.setLogLevel(FirefoxDriverLogLevel.FATAL);
          firefoxOptions.setBinary(firefoxBinary);
          local2 = new FirefoxDriver(firefoxOptions);
          return local2;
      }
  
      @Override
      public void shutdownDriver() {
          try{
              local.quit();
          }catch (NullPointerException npe){
              local2.quit();
          }
      }
  
  
      public WebDriver stopOrphanSession(){
          try{
              if(local != null){
                  return local;
              }
          }catch (NullPointerException npe){
              System.out.println(&quot;All Drivers Closed&quot;);
          }
          return local2;
      }
  }
  </code></pre>
  <p>I have fairly standard Runners. I've tried several variations of the Cucumber Runner, moved around into different directories, using glue and extraglue configurations, but either nothing changes, or I break it completely. Here is the one that is working now.</p>
  <pre><code>@RunWith(Cucumber.class)
  @CucumberOptions(features = &quot;src/test/resources/projectname/features/&quot;,
          glue = &quot;backbase&quot;,
  //        extraGlue = &quot;common&quot;,   // glue and extraGlue cannot be used together
          plugin = {
                  &quot;pretty&quot;,
                  &quot;summary&quot;,
                  &quot;de.monochromata.cucumber.report.PrettyReports:target/cucumber&quot;,
          })
  public class RunCucumberTest {
  
  }
  </code></pre>
  <p>and my Spring Runner</p>
  <pre><code>@RunWith(SpringRunner.class)
  @CucumberContextConfiguration
  @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
  public class SpringContextRunner {
  }
  </code></pre>
  <p>And the out of the box Application page for reference.</p>
  <pre><code>@SpringBootApplication
  public class Application {
  
      public static void main(String[] args) {
          SpringApplication.run(Application.class, args);
      }
  }
  </code></pre>
  <p>And just in case someone finds it useful to brainstorming or diagnosing, my page objects start with the BasePage, which has become far too enormous since it contains all the common methods, but looks something like this.</p>
  <pre><code>public abstract class BasePageWeb {
      
      @Value(&quot;${projectname.retail.dev}&quot;)
      private String devUrl;
      @Value(&quot;${projectname.retail.sit}&quot;)
      private String sitUrl;
      @Value(&quot;${projectname.retail.uat}&quot;)
      private String uatUrl;
  
      protected WebDriver driver;
      public WebDriverWait wait;
  
      protected final WebDriverInterface webDriverInterface;
  
      public BasePageWeb(WebDriverInterface webDriverInterface) {
          this.webDriverInterface = webDriverInterface;
      }
  
      // env choices: lcl, dev, sit, uat -&gt; app choices: web, id, emp, cxm -&gt; browser choices: chrome, fire
      public void loadAny(String env, String app, String browser) {
  
          if (browser.equals(&quot;chrome&quot;)) {
              driver = this.webDriverInterface.getDriver();
          } else if (browser.equals(&quot;fire&quot;)) {
              driver = this.webDriverInterface.getDriverFire();
          }
  
          wait = new WebDriverWait(driver, 30);
  
          String url = &quot;&quot;;
          String title = &quot;&quot;;
          switch (app) {
              case &quot;web&quot;:
                  switch (env) {
                      case &quot;dev&quot;:
                          url = devUrl;
                          title = &quot;Log in to Project Name&quot;;
                          break;
                      case &quot;sit&quot;:
                          url = sitUrl;
                          title = &quot;Log in to Project Name&quot;;
                          break;
                      case &quot;uat&quot;:
                          url = uatUrl;
                          title = &quot;Log in to Project Name&quot;;
                          break;
                  }
                  break;
              default:
                  System.out.println(&quot;There were no matches to your login choices.&quot;);
          }
          driver.get(url);
          wait.until(ExpectedConditions.titleContains(title));
      }
  }
  </code></pre>
  <p>And then when I have specific topics where I can create methods that will apply only to that sub area, I extend out the base page, and I inject the sub page into the Steps pages.</p>
  <pre><code>@Component
  public class MyAccountsPage extends BasePageWeb {
  
  
      public MyAccountsPage(WebDriverInterface webDriverInterface) {
          super(webDriverInterface);
      }
  
      // Find the Product Title Elements, Convert to Strings, and put them all in a simple List.
  
      public List&lt;String&gt; getAccountInfoTitles(){
          List&lt;WebElement&gt; accountInfoTitlesElements =
                  driver.findElements(By.cssSelector(&quot;div &gt; .bb-account-info__title&quot;));
          return accountInfoTitlesElements.stream()
                                          .map(WebElement::getText)
                                          .collect(Collectors.toList());
      }
  }
  </code></pre>
  <p>If anyone can see what I'm doing wrong, or make suggestions for investigation, I would appreciate it. I know there have been some major cucumber changes after 6.6.0 for how the framework scans for annotations and such, but I haven't been able to determine if that is related or not.</p>
  <p>For reference. The pom.xml with all the versions and included dependencies.</p>
  <pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
  &lt;project xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
           xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot;
           xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
      &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
  
      &lt;parent&gt;
          &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
          &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
          &lt;version&gt;2.3.3.RELEASE&lt;/version&gt;
          &lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;
      &lt;/parent&gt;
  
      &lt;groupId&gt;java-cucumber-generic&lt;/groupId&gt;
      &lt;artifactId&gt;java-cucumber-generic-web&lt;/artifactId&gt;
      &lt;version&gt;1.0.1-SNAPSHOT&lt;/version&gt;
      &lt;packaging&gt;jar&lt;/packaging&gt;
  
      &lt;properties&gt;
          &lt;java.version&gt;1.8&lt;/java.version&gt;
          &lt;cucumber.version&gt;6.6.0&lt;/cucumber.version&gt;
          &lt;junit.version&gt;4.13&lt;/junit.version&gt;
          &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
          &lt;port&gt;8358&lt;/port&gt;
          &lt;cucumber.reporting.version&gt;5.3.1&lt;/cucumber.reporting.version&gt;
          &lt;cucumber.reporting.config.file&gt;automation-web/src/test/resources/projectname/cucumber-reporting.properties&lt;/cucumber.reporting.config.file&gt;
          &lt;org.mapstruct.version&gt;1.3.1.Final&lt;/org.mapstruct.version&gt;
          &lt;h2database.version&gt;1.4.200&lt;/h2database.version&gt;
          &lt;appium.java.client.version&gt;7.3.0&lt;/appium.java.client.version&gt;
          &lt;guava.version&gt;29.0-jre&lt;/guava.version&gt;
          &lt;reporting-plugin.version&gt;4.0.83&lt;/reporting-plugin.version&gt;
          &lt;commons-text.version&gt;1.9&lt;/commons-text.version&gt;
          &lt;commons-io.version&gt;2.8.0&lt;/commons-io.version&gt;
      &lt;/properties&gt;
  
      &lt;dependencies&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;io.cucumber&lt;/groupId&gt;
              &lt;artifactId&gt;cucumber-java&lt;/artifactId&gt;
              &lt;version&gt;${cucumber.version}&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;io.cucumber&lt;/groupId&gt;
              &lt;artifactId&gt;cucumber-junit&lt;/artifactId&gt;
              &lt;version&gt;${cucumber.version}&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;io.cucumber&lt;/groupId&gt;
              &lt;artifactId&gt;cucumber-java8&lt;/artifactId&gt;
              &lt;version&gt;${cucumber.version}&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;junit&lt;/groupId&gt;
              &lt;artifactId&gt;junit&lt;/artifactId&gt;
              &lt;version&gt;${junit.version}&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;!-- Added beyond original archetype --&gt;
          &lt;!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-surefire-plugin --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
              &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
              &lt;version&gt;2.12.4&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;!-- To make Wait Until work --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;com.google.guava&lt;/groupId&gt;
              &lt;artifactId&gt;guava&lt;/artifactId&gt;
              &lt;version&gt;${guava.version}&lt;/version&gt;
          &lt;/dependency&gt;
  
          &lt;!-- Cucumber Reporting --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;net.masterthought&lt;/groupId&gt;
              &lt;artifactId&gt;cucumber-reporting&lt;/artifactId&gt;
              &lt;version&gt;${cucumber.reporting.version}&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;de.monochromata.cucumber&lt;/groupId&gt;
              &lt;artifactId&gt;reporting-plugin&lt;/artifactId&gt;
              &lt;version&gt;${reporting-plugin.version}&lt;/version&gt;
          &lt;/dependency&gt;
  
  
          &lt;!-- For dependency injection https://cucumber.io/docs/cucumber/state/#dependency-injection --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;io.cucumber&lt;/groupId&gt;
              &lt;artifactId&gt;cucumber-spring&lt;/artifactId&gt;
              &lt;version&gt;${cucumber.version}&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-data-rest&lt;/artifactId&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
          &lt;/dependency&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
              &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
              &lt;scope&gt;test&lt;/scope&gt;
              &lt;exclusions&gt;
                  &lt;exclusion&gt;
                      &lt;groupId&gt;org.junit.vintage&lt;/groupId&gt;
                      &lt;artifactId&gt;junit-vintage-engine&lt;/artifactId&gt;
                  &lt;/exclusion&gt;
              &lt;/exclusions&gt;
          &lt;/dependency&gt;
  
          &lt;!-- https://mvnrepository.com/artifact/com.h2database/h2 --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;com.h2database&lt;/groupId&gt;
              &lt;artifactId&gt;h2&lt;/artifactId&gt;
              &lt;version&gt;${h2database.version}&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
  
          &lt;!-- To generate getters, setters, equals, hascode, toString methods --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
              &lt;artifactId&gt;lombok&lt;/artifactId&gt;
              &lt;optional&gt;true&lt;/optional&gt;
          &lt;/dependency&gt;
  
          &lt;!-- Java client, wrapped by Appium --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;io.appium&lt;/groupId&gt;
              &lt;artifactId&gt;java-client&lt;/artifactId&gt;
              &lt;version&gt;${appium.java.client.version}&lt;/version&gt;
              &lt;scope&gt;test&lt;/scope&gt;
          &lt;/dependency&gt;
  
          &lt;!-- https://mvnrepository.com/artifact/org.apache.commons/commons-text --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
              &lt;artifactId&gt;commons-text&lt;/artifactId&gt;
              &lt;version&gt;${commons-text.version}&lt;/version&gt;
          &lt;/dependency&gt;
          &lt;!-- https://mvnrepository.com/artifact/commons-io/commons-io --&gt;
          &lt;dependency&gt;
              &lt;groupId&gt;commons-io&lt;/groupId&gt;
              &lt;artifactId&gt;commons-io&lt;/artifactId&gt;
              &lt;version&gt;${commons-io.version}&lt;/version&gt;
          &lt;/dependency&gt;
  
  
      &lt;/dependencies&gt;
  
      &lt;build&gt;
          &lt;plugins&gt;
              &lt;plugin&gt;
                  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                  &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
                  &lt;version&gt;3.8.1&lt;/version&gt;
                  &lt;configuration&gt;
                      &lt;encoding&gt;UTF-8&lt;/encoding&gt;
                      &lt;source&gt;${java.version}&lt;/source&gt;
                      &lt;target&gt;${java.version}&lt;/target&gt;
                  &lt;/configuration&gt;
              &lt;/plugin&gt;
  
              &lt;!-- Added beyond original archetype --&gt;
  
              &lt;plugin&gt;
                  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                  &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
                  &lt;version&gt;2.12.4&lt;/version&gt;
                  &lt;configuration&gt;
                      &lt;testFailureIgnore&gt;true&lt;/testFailureIgnore&gt;
                  &lt;/configuration&gt;
              &lt;/plugin&gt;
          &lt;/plugins&gt;
      &lt;/build&gt;
  &lt;/project&gt;
  
  </code></pre>