0

I have a Selenium test based on Cucumber and Spring Boot that throws an exception (error shown below) when I tried to simplify the test by moving the methods setup() and closeBrowser() in the parent class.

If I cut-paste both these methods in GoogleCalcStepDefinition.java file everything works fine and the tests also pass without any issues.

Not sure how to move these common methods to another class to simplify the tests and support maintainability and extensibility.

I googled around and found this SO link (Cucumber class extending step definitions and hooks) but it does not have any code snippet and does not provide much help.

Please guide.

DemoApplicationTests.java

@RunWith(SpringRunner.class)
@SpringBootTest
public abstract class DemoApplicationTests {

    private static final String CHROME_DRIVER_EXE = "drivers/chromedriver.exe";
    private static final String WEBDRIVER_CHROME_DRIVER = "webdriver.chrome.driver";
    private WebDriver driver;
    private GoogleSearchPage googleSearchPage;

    @Before //this uses cucumber.api.Before (equivalent of JUnit @BeforeClass)
    public void setup() {
        String filePath = ClassLoader.getSystemClassLoader().getResource(CHROME_DRIVER_EXE).getFile();
        System.setProperty(WEBDRIVER_CHROME_DRIVER, filePath);
        ChromeOptions options = new ChromeOptions();
        options.setExperimentalOption("useAutomationExtension", false);
        driver = new ChromeDriver(options);
        googleSearchPage = PageFactory.initElements(driver, GoogleSearchPage.class);
    }

    @After //this uses cucumber.api.After (equivalent of JUnit @AfterClass)
    public void closeBrowser() {
        if (null != driver) {
            driver.close();
            driver.quit();
        }
    }

}

GoogleCalcStepDefinition.java

@Ignore
public class GoogleCalcStepDefinition extends DemoApplicationTests {

    @Given("^I open Google$")
    public void I_open_google() {
        //Set implicit wait of 5 seconds and launch google
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        driver.get("https://www.google.co.in");
    }

    @When("^I enter \"([^\"]*)\" in search textbox$")
    public void I_enter_in_search_textbox(String additionTerms) {
        googleSearchPage.searchBox.sendKeys(additionTerms); //passing 2+2 here
        googleSearchPage.searchBtn.click();
    }

    @Then("^I should get result as \"([^\"]*)\"$")
    public void I_should_get_correct_result(String expectedResult) {
        String result = googleSearchPage.calculatorTextBox.getText();
        assertEquals(result, expectedResult); //Verify that result of 2+2 is 4
    }

}

TestRunner.java

@RunWith(Cucumber.class)
@CucumberOptions(
        plugin = {"pretty", "json:target/cucumber-reports/cucumber.json"},
        features = {"src/test/resources/features"})
public class TestRunner {

}

GoogleSearchPage.java

public class GoogleSearchPage {

    @FindBy(name = "q")
    WebElement searchBox;
    @FindBy(name = "btnK")
    WebElement searchBtn;
    @FindBy(id = "cwos")
    WebElement calculatorTextBox;

}

calc.feature (src/test/resources/features)

Feature: Check addition in Google calculatorcontent
  In order to verify that Google calculator work correctly
  As a user of Google
  I should be able to get correct addition result

  Scenario: Addition
    Given I open Google
    When I enter "2+2" in search textbox
    Then I should get result as "4"

pom.xml

<dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>4.2.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-junit</artifactId>
        <version>4.2.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-spring</artifactId>
        <version>4.2.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.seleniumhq.selenium</groupId>
                <artifactId>selenium-chrome-driver</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-chrome-driver</artifactId>
        <version>2.45.0</version>
        <scope>test</scope>
    </dependency>

Error (when I execute TestRunner):

cucumber.runtime.CucumberException: You're not allowed to extend classes that define Step Definitions or hooks. class com.example.GoogleCalcStepDefinition extends class com.example.DemoApplicationTests

    at cucumber.runtime.java.MethodScanner.validateMethod(MethodScanner.java:76)
    at cucumber.runtime.java.MethodScanner.scan(MethodScanner.java:62)
    at cucumber.runtime.java.MethodScanner.scan(MethodScanner.java:43)
    at cucumber.runtime.java.JavaBackend.loadGlue(JavaBackend.java:83)
    at cucumber.runner.Runner.<init>(Runner.java:28)
    at cucumber.runner.ThreadLocalRunnerSupplier.createRunner(ThreadLocalRunnerSupplier.java:42)
    at cucumber.runner.ThreadLocalRunnerSupplier.access$000(ThreadLocalRunnerSupplier.java:13)
    at cucumber.runner.ThreadLocalRunnerSupplier$1.initialValue(ThreadLocalRunnerSupplier.java:22)
    at cucumber.runner.ThreadLocalRunnerSupplier$1.initialValue(ThreadLocalRunnerSupplier.java:19)
    at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:180)
    at java.lang.ThreadLocal.get(ThreadLocal.java:170)
    at cucumber.runner.ThreadLocalRunnerSupplier.get(ThreadLocalRunnerSupplier.java:38)
    at cucumber.api.junit.Cucumber.<init>(Cucumber.java:106)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)
    at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)
    at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
    at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
    at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
    at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:33)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:49)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)


Process finished with exit code -1
Nital
  • 5,784
  • 26
  • 103
  • 195
  • The hook methods can be placed in any class whoose package structre is mentioned in the glue option of the runner. This will be automatically scanned by cucumber. No need for extending the step definition class. – Grasshopper Apr 12 '19 at 03:20
  • @Grasshopper - I added a class `HooksDemo.java` a method `setup()` with just `System.out.println` within it and also added the package with `glue = {"com.example.hooks"}` in the runner. Still this method does not get called when I run the test. Can you please point me to an example that I could refer? – Nital Apr 12 '19 at 14:25

2 Answers2

0

If you want to share WebDriver among different step definition classes, consider using Singleton Holder pattern for your web driver.

Your step definition should not initialise web driver and your test suite, it should ideally include only code related to actual testing/assertion, you would be better off to create some sort of Test Manager class (singleton) that initialises web driver and everything needed to run your tests, load properties, setup test data etc, and in your step definitions simply refer to your Test Manager.

Cucumber will look for step definition in all classes specified within 'glue' directory from your runner class. So potentially, lets say you have some test scenarios that are testing Login page and Shop page, you could have:

  • LoginSteps.java -> hold step definitions for Login page scenarios
  • ShopSteps.java -> hold step definitions for Shop page scenarios
  • CommonSteps.java -> hold steps that you use both in Login and Shop (and other)
Matthewek
  • 1,519
  • 2
  • 22
  • 42
0

You can utilize open source framework QAF which takes care of driver management, resource management and other functional test automation needs. Once you use qaf you will found all setup and teardown related code is redundant as it is taken care by qaf. You can concentrate more on automating functionality of AUT. Refer answer to similar question.

user861594
  • 5,733
  • 3
  • 29
  • 45