Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sharing same selenium WebDriver between step definition files

Right now we're working on adopting Cucumber to run functional tests on our Java8/Spring app. We want our step definition files to remain as DRY as possible and as such plan on using the same step definitions in different feature files. Since we are using a selenium WebDriver to drive our tests, we need to share the same driver between step definitions.

To demonstrate why having multiple drivers is an issue for us, imagine a feature file that defines two steps: one to navigate to a page, and another to assert that a line appears on that page. If both steps happen to be defined in separate files, the first step definition will use its driver to navigate to the page. By the time the second step definition runs the assertion against its driver it hasn't navigated to the page (since those actions went to the other driver) and the test fails.

We tried implementing a base class (that contains the driver) that each step definition file would extend. As it turns out Cucumber instantiates an instance of each step definition class, and therefore we end up with each step definition having different WebDriver instances.

We thought about using Spring to inject an instance of the WebDriver in each step definition file, but I believe this would cause the same problem described above.

I know that the singleton pattern can be used to achieve this, but ours seems like such a common problem and the singleton pattern feels like overkill. Is this actually the right way to approach it? Or am I missing something really obvious?

Thank you in advance for your help!

like image 922
davidicus Avatar asked Jul 22 '15 20:07

davidicus


2 Answers

I reccomend you to use pico-container as a dependency injection framework to use with cucumber-jvm.

With PicoContainer, you can have a 'base' class with the instance of WebDriver, and then pass this base class automactically to any other class. Or even you could pass directly the web driver if you prefer.

<dependency>
    <groupId>info.cukes</groupId>
    <artifactId>cucumber-picocontainer</artifactId>
    <version>1.2.3</version>
    <scope>test</scope>
</dependency>

Example:

Base class with the instance of WebDriver:

public class ContextSteps {

   private static boolean initialized = false;

   private WebDriver driver;

   @Before
   public void setUp() throws Exception {
      if (!initialized) {
         // initialize the driver
         driver = = new FirefoxDriver();

         initialized = true;
      }
   }

   public WebDriver getDriver() {
      return driver;
   }
}

Other class who access webDriver through pico-container DI.

public class OtherClassSteps {

   private ContextSteps contextSteps;

   // PicoContainer injects class ContextSteps
   public OtherClassSteps (ContextSteps contextSteps) {
      this.contextSteps = contextSteps;
   }


   @Given("^Foo step$")
   public void fooStep() throws Throwable {
      // Access WebDriver instance
      WebDriver driver = contextSteps.getDriver();
   }
}

Hope it helps.

like image 133
troig Avatar answered Oct 24 '22 00:10

troig


This question is old, and I left the project shortly after asking this question, but I went back and looked at the code we put in place (using the singleton pattern) and this is what we ended up with. I forget exactly why we couldn't use pico-container (it was possibly an organizational constraint) but if you can use extra libraries I remember that solution worked well.

I will leave that as the accepted answer but hopefully this solution is useful for those who find themselves in a similar position that I was in a few years ago.

public class TestingBase {

    private static TestingBase instance;
    private static WebDriver driver;
    private static Thread CLOSE_DRIVER = new Thread() {
        @Override
        public void run() {
            driver.close();
        }

    };

    static {
        Runtime.getRuntime().addShutdownHook(CLOSE_DRIVER);
    }

    private TestingBase() {
        DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
        desiredCapabilities.setJavascriptEnabled(true);
        desiredCapabilities.setCapability("takesScreenshot", false);
        desiredCapabilities.setCapability("handlesAlerts", true);

        desiredCapabilities.setCapability(PhantomJSDriverService.PHANTOMJS_CLI_ARGS, new String[]{
                "--web-security=false",
                "--ssl-protocol=TLSv1",
                "--ignore-ssl-errors=true",
                "--webdriver-loglevel=ERROR",
                "--webdriver-logfile=/var/log/phantomjs/ghostrdriver.log"
        });
        desiredCapabilities.setCapability("elementScrollBehavior",true);
        driver = new PhantomJSDriver(desiredCapabilities);
    }

    public static TestingBase getTestingBase() {
        if (instance == null) {
            instance = new TestingBase();
        }
        return instance;

    }

    public static WebDriver getDriver() {
        return getTestingBase().driver;
    }
}
like image 27
davidicus Avatar answered Oct 23 '22 23:10

davidicus