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!
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.
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With