Search code examples
javaseleniumdependency-injectiontestngcucumber-jvm

Initialise WebDriver as dependency injection in Cucumber-JVM before test


I am new to Cucumber-JVM and previously I used to create a BaseTest class, initialise WebDriver there in some @BeforeMethod and then extend all other Test Classes from BaseTest class, so WebDriver initialisation is managed a single time in the code base and WebDriver initialisation happens before each test as it should be. With Cucumber-JVM, I understand that idea is to initialise WebDriver in some class, mark this class with @ScenarioScoped annotation and then use instance of this class in StepDefinitions with @Inject annotation — so I can access created WebDriver. I tried different options for it, but was not successful. I followed steps in these guides, but each time I get similar errors: http://www.thinkcode.se/blog/2017/08/16/sharing-state-between-steps-in-cucumberjvm-using-guice https://codoid.com/automation-testing/an-all-inclusive-guide-to-achieve-cucumber-dependency-injection-using-guice/

I attach my files with current state below and hope that someone can direct me to a right way how I can do it.

[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.813 s <<< FAILURE! - in TestSuite
[ERROR] wiki.runners.MainPageRunnerTest.runScenario["Open main page from home page in English locale", "Optional[Open main page]"](1)  Time elapsed: 0.643 s  <<< FAILURE!
com.google.inject.ProvisionException: 
Unable to provision, see the following errors:

1) [Guice/ErrorInjectingConstructor]: NoSuchMethodError: 'ImmutableMap ImmutableMap.of(Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object)'
  at wiki.World.<init>(World.java:10)
  at wiki.World.class(World.java:10)
  at OpenMainPageDefinitions.world(OpenMainPageDefinitions.java:15)
      \_ for field world
  while locating OpenMainPageDefinitions

Learn more:
  https://github.com/google/guice/wiki/ERROR_INJECTING_CONSTRUCTOR

1 error

======================
Full classname legend:
======================
ImmutableMap:            "com.google.common.collect.ImmutableMap"
OpenMainPageDefinitions: "wiki.definitions.OpenMainPageDefinitions"
========================
End of classname legend:
========================

        at com.google.inject.internal.InternalProvisionException.toProvisionException(InternalProvisionException.java:251)
        at com.google.inject.internal.InjectorImpl$1.get(InjectorImpl.java:1104)
        at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1139)
        at io.cucumber.guice.GuiceFactory.getInstance(GuiceFactory.java:83)
        at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47)
        at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:29)
        at io.cucumber.core.runner.CoreStepDefinition.execute(CoreStepDefinition.java:66)
        at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:63)
        at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10)
        at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:86)
        at io.cucumber.core.runner.TestStep.run(TestStep.java:57)
        at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:51)
        at io.cucumber.core.runner.TestCase.run(TestCase.java:95)
        at io.cucumber.core.runner.Runner.runPickle(Runner.java:75)
        at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132)
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$3(CucumberExecutionContext.java:151)
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23)
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:151)
        at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129)
        at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:135)
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:673)
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:220)
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50)
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:945)
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:193)
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
        at org.testng.TestRunner.privateRun(TestRunner.java:808)
        at org.testng.TestRunner.run(TestRunner.java:603)
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:429)
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:423)
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:383)
        at org.testng.SuiteRunner.run(SuiteRunner.java:326)
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95)
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1249)
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
        at org.testng.TestNG.runSuites(TestNG.java:1092)
        at org.testng.TestNG.run(TestNG.java:1060)
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:324)
        at org.apache.maven.surefire.testng.TestNGXmlTestSuite.execute(TestNGXmlTestSuite.java:74)
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:123)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:456)
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:169)
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:595)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:581)
Caused by: java.lang.NoSuchMethodError: 'com.google.common.collect.ImmutableMap com.google.common.collect.ImmutableMap.of(java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object, java.lang.Object)'
        at org.openqa.selenium.chrome.AddHasCasting.getAdditionalCommands(AddHasCasting.java:38)
        at org.openqa.selenium.chrome.ChromeDriver$ChromeDriverCommandExecutor.getExtraCommands(ChromeDriver.java:123)
        at org.openqa.selenium.chrome.ChromeDriver$ChromeDriverCommandExecutor.<init>(ChromeDriver.java:118)
        at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:106)
        at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:93)
        at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:82)
        at wiki.WebDriverManager.<init>(WebDriverManager.java:16)
        at wiki.World.<init>(World.java:11)
        at wiki.World$$FastClassByGuice$$11467.GUICE$TRAMPOLINE(<generated>)
        at wiki.World$$FastClassByGuice$$11467.apply(<generated>)
        at com.google.inject.internal.DefaultConstructionProxyFactory$FastClassProxy.newInstance(DefaultConstructionProxyFactory.java:82)
        at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:114)
        at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:91)
        at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:300)
        at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)
        at io.cucumber.guice.SequentialScenarioScope.lambda$scope$0(SequentialScenarioScope.java:40)
        at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:45)
        at com.google.inject.internal.SingleFieldInjector.inject(SingleFieldInjector.java:50)
        at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:146)
        at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:124)
        at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:91)
        at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:300)
        at com.google.inject.internal.InjectorImpl$1.get(InjectorImpl.java:1101)
        ... 50 more

[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Failures: 
[ERROR]   MainPageRunnerTest>AbstractTestNGCucumberTests.runScenario:35 » Provision Unable to provision, see the following errors:

1) [Guice/ErrorInjectingConstructor]: NoSuchMethodError: 'ImmutableMap ImmutableMap.of(Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object)'
  at wiki.World.<init>(World.java:10)
  at wiki.World.class(World.java:10)
  at OpenMainPageDefinitions.world(OpenMainPageDefinitions.java:15)
      \_ for field world
  while locating OpenMainPageDefinitions

Learn more:
  https://github.com/google/guice/wiki/ERROR_INJECTING_CONSTRUCTOR

1 error

======================
Full classname legend:
======================
ImmutableMap:            "com.google.common.collect.ImmutableMap"
OpenMainPageDefinitions: "wiki.definitions.OpenMainPageDefinitions"
========================
End of classname legend:
========================

[INFO] 
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

OpenMainPageDefinitions.java:

package wiki.definitions;

import com.google.inject.Inject;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import wiki.World;
import wiki.pages.EnMainPage;
import wiki.pages.HomePage;

public class OpenMainPageDefinitions {

    @Inject World world;
    public HomePage homePage;
    public EnMainPage enMainPage;

    @Given("Home page is opened")
    public void homePageIsOpened() {
        homePage = new HomePage(world.manager.get());
        homePage.open().assertCentralFeaturedLogoIsDisplayed();
    }

    @When("User clicks on Main Page Link")
    public void userClicksOnMainPageLink() {
        homePage.clickEnMainPageLink();
        enMainPage = new EnMainPage(world.manager.get());
    }

    @Then("Welcome block is displayed")
    public void welcomeBlockIsDisplayed() {
        enMainPage.assertWelcomeBlockIsDisplayed();
    }

}

World.java

package wiki;

import io.cucumber.guice.ScenarioScoped;

@ScenarioScoped
public class World {

    public WebDriverManager manager;

    public World() {
        manager = new WebDriverManager();
    }

}

WebDriverManager.java

package wiki;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

public class WebDriverManager {

    public String env;
    public ChromeOptions options;
    public WebDriver driver;

    public WebDriverManager() {
        env = getEnv();
        options = getChromeOptions();
        driver = new ChromeDriver(options);
    }

    public ChromeOptions getChromeOptions() {
        if(env.equals("CIRCLE_CI")) {
            return new ChromeOptions()
                    .addArguments("--disable-dev-shm-usage")
                    .addArguments("--headless")
                    .addArguments("--no-sandbox");
        }
        return new ChromeOptions();
    }

    public String getEnv() {
        String env = System.getenv("WIKI_ENV");
        return env == null ? "LOCAL" : env;
    }

    public WebDriver get() {
        return driver;
    }

}

pom.xml

<?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>

    <groupId>org.example</groupId>
    <artifactId>cucumber-selenium-tests</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <maven-surefire-plugin.version>3.0.0-M6</maven-surefire-plugin.version>
        <maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
        <testng.version>7.5</testng.version>
        <selenium-java.version>4.1.3</selenium-java.version>
        <cucumber.version>7.2.3</cucumber.version>
        <google-inject.version>5.1.0</google-inject.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler-plugin.version}</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>${testng.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium-java.version}</version>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-testng</artifactId>
            <version>${cucumber.version}</version>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-guice</artifactId>
            <version>${cucumber.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>${google-inject.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <configuration>
                    <systemPropertyVariables>
                        <webdriver.chrome.driver>/tmp/chromedriver/chromedriver</webdriver.chrome.driver>
                    </systemPropertyVariables>
                    <suiteXmlFiles>
                        <suiteXmlFile>testng.xml</suiteXmlFile>
                    </suiteXmlFiles>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Solution

  • If you run mvn dependency:tree -Dverbose in your project root you will be able to analyze the dependency hierarchy of your project.

    Package com.google.common.collect.ImmutableMap is distributed within guava library. The dependency analysis shows that Selenium uses version 31.1 while Guice uses version 30.1.

    Guava that is under Guice is closer to the tree root so that it is taken by Maven considering that the closer lib to the root the more priority it has in case of conflicts.

    There are three solutions:

    1. Decrease version of Selenium to the one using lower version of Guava (ideally 30.1 but the major version match is also acceptable)
    2. Increase version of Guice to the one using higher version of Guava (ideally 31.1 but the major version match is also acceptable)
    3. Specify the guava dependency implicitly. Put 31.1 version dependency to your pom.xml. It will have the absolute priority over others. And hope it is backward compatible with 30.1 so that Guice logic will not be affected.