rs79
rs79

Reputation: 2321

In Cucumber, is it possible to programmatically get the current step being executed?

Scenario: As a user, I want to login to the system
Given I am on my website
When I enter valid credentials
Then I am taken to the home page

The scenario name can be retrieved using the getName() function. Is there a way to also get the step being executed (in Java)? We foresee the use of this in logging and reporting.

So, for the scenario above, I am on my website would be returned while the corresponding step definition is being executed.

Upvotes: 4

Views: 30370

Answers (12)

Metamix
Metamix

Reputation: 1

As a newbie I could not comment the answer from AndyGee but if you want to have the actual name you have to use the .getName() method or .getUri + .getLine() to get something like an id (.getId() does not return a unique ID).

@BeforeStep
public void beforeStep(Scenario scenario){
  System.out.println(scenario.getName().toString());
}

Currently we are using the .getUri() method and check the Uri against a substring to be a little more flexible in the future.

Upvotes: 0

Sreenidhi GSD
Sreenidhi GSD

Reputation: 31

Here's an update to handle the framework changes. The "testCase" field is hidden under the "delegate". I got this working with io.cucumber.java version 5.7.0

public String getStepText(io.cucumber.java.Scenario scenario){      
    String  currentStepDescr = null;

    //value currentStepDefIndex is tracked in the another class
    int currentStepDefIndex = OtherClass.getStepIndex();

    Field f = scenario.getClass().getDeclaredField("delegate");
    f.setAccessible(true);
    TestCaseState tcs = (TestCaseState) f.get(scenario);

    Field f2 = tcs.getClass().getDeclaredField("testCase");
    f2.setAccessible(true);
    TestCase r = (TestCase) f2.get(tcs);

        List<PickleStepTestStep> stepDefs = r.getTestSteps()
                .stream()
                .filter(x -> x instanceof PickleStepTestStep)
                .map(x -> (PickleStepTestStep) x)
                .collect(Collectors.toList());


        PickleStepTestStep currentStepDef = stepDefs
                .get(currentStepDefIndex);
        currentStepDescr = currentStepDef.getStep().getText();
        currentStepDefIndex += 1;
        OtherClass.setStepIndex(currentStepDefIndex);
         return currentStepDescr ;
       }

Below are the dependencies in my pom.xml

<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-core -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-core</artifactId>
            <version>5.7.0</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-testng -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-testng</artifactId>
            <version>5.7.0</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>5.7.0</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-jvm-deps -->
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-jvm-deps</artifactId>
            <version>1.0.6</version>
            <scope>provided</scope>
        </dependency>

Upvotes: 3

Tihamer
Tihamer

Reputation: 971

Grabbing the annotation using self-reflection seems more straightforward to me:

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

@When("^User enters username and password$")
public void userEntersUsernameAndPassword() throws Throwable{
    Method callingMethod = new Object() {} .getClass() .getEnclosingMethod();
    Annotation  myAnnotation = callingMethod.getAnnotations()[0];   
    System.out.println("myAnnotation=" + myAnnotation);

Results in:

[email protected](timeout=0, value=^User is in portal page$)

Upvotes: 1

Jernej Gorički
Jernej Gorički

Reputation: 894

I solved it using @BeforeStep & @AfterStep. It is a bit hacky, so use it only if you are know what you are doing.

public class StepDefBeginEndLogger {

private int currentStepDefIndex = 0;

@BeforeStep
public void doSomethingBeforeStep(Scenario scenario) throws Exception {

    Field f = scenario.getClass().getDeclaredField("testCase");
    f.setAccessible(true);
    TestCase r = (TestCase) f.get(scenario);

    //You need to filter out before/after hooks
    List<PickleStepTestStep> stepDefs = r.getTestSteps()
            .stream()
            .filter(x -> x instanceof PickleStepTestStep)
            .map(x -> (PickleStepTestStep) x)
            .collect(Collectors.toList());


    //This object now holds the information about the current step definition
    //If you are using pico container 
    //just store it somewhere in your world state object 
    //and to make it available in your step definitions.
    PickleStepTestStep currentStepDef = stepDefs
            .get(currentStepDefIndex);
}

@AfterStep
public void doSomethingAfterStep(Scenario scenario) {
    currentStepDefIndex += 1;
}

}

Upvotes: 3

JvdB
JvdB

Reputation: 49

Just leaving this here for future reference...

The current version of Cucumber (4.2.5) has the BeforeStep hook, but only provides access to the current running scenario.

What I did to extract the current step, was using reflection to access the steps within that scenario;

@BeforeStep
public void beforeStep(Scenario scn) throws Exception {
    currentStepIndex++;

    Field testCaseField = scn.getClass().getDeclaredField("testCase");
    testCaseField.setAccessible(true);

    TestCase tc = (TestCase) testCaseField.get(scn);
    Field testSteps = tc.getClass().getDeclaredField("testSteps");
    testSteps.setAccessible(true);

    List<TestStep> teststeps = tc.getTestSteps();
    try {
        PickleStepTestStep pts = (PickleStepTestStep) teststeps.get(currentStepIndex);
        getLog().info("########################");
        getLog().info("##########STEP##########");
        getLog().info(pts.getStepText());
        getLog().info("########################");
        currentStepIndex++;
    } catch (Exception ignore) {
    }
}

The only downside is, that you require a int currentStepIndex at class level, and need to add 1 with every @Before or @BeforeStep.

BE WARNED that the use of this type of reflection may fail to work in future releases of Cucumber, as the Cucumber team can decide to change their internals.

Upvotes: 2

Sintrias
Sintrias

Reputation: 586

I had this same question. I attempted to use rs79's answer but either I don't know what I'm actually doing with it or it doesn't work. Java gives me an "AmbiguousStepDefinitionException" or something like that. So I did it a different way. It takes a little work if you have a slew of step definitions but it works and is pretty simple:

@Then(value = "^The page should navigate to url \"([^\"])\"$", timeout = MAX_TIME)
public void the_page_should_navigate_to_url(String url) {
    //below I use a custom class with a static method setStepName() which just sets a string field in the class
    CustomClass.setStepName("Then The page should navigate to url " + url);
    //Assert
}

Now you have access to the step name without needing any kind of complicated tool. Just use a get method to access the step variable in your custom class. Hope that helps.

Upvotes: 0

AndyGee
AndyGee

Reputation: 371

These hooks will help:

@BeforeStep
public void beforeStep(Scenario scenario){
  System.out.println(scenario.toString());
}

@AfterStep
public void afterStep(Scenario scenario){
  System.out.println(scenario.toString());
}

Upvotes: 0

Harald Brabenetz
Harald Brabenetz

Reputation: 504

I think the CucumberWithSerenity register a Listener which stores the current Step Name.

Try this in your Test-Runner:

//import net.serenitybdd.cucumber.CucumberWithSerenity;
@RunWith(CucumberWithSerenity.class)
@CucumberOptions(...

And then in in your Step:

//import net.thucydides.core.model.TestStep;
//import net.thucydides.core.steps.StepEventBus;
if (!StepEventBus.getEventBus().isBaseStepListenerRegistered()) {
    return "Unknown"; // CucumberWithSerenity is required.
} 
String currentStepDescr = StepEventBus.getEventBus().getCurrentStep()
    .transform(TestStep::getDescription)
    .get();

Dependency:

<dependency>
    <groupId>net.serenity-bdd</groupId>
    <artifactId>serenity-core</artifactId>
    <version>${serenity.version}</version>
</dependency>

Upvotes: 3

rs79
rs79

Reputation: 2321

We solved this problem by wrapping the entire step as a parameter into the Step Definition. In other words, the step

Given I am on my website

translates into

'Given I am on my website'

And the step definition will actually accept a string parameter that will correspond to the step

    @And("(.*)") //plus something specific to map step
    public void Initialization(String step) throws Exception {
            //do something with step
    }

Upvotes: 2

Jefe infiltrado
Jefe infiltrado

Reputation: 382

you could add a step like

When I log in with the user 'user' and the password 'password'

and repeat this step whenever you need a login

You have to put the class containing the step definition in a package used by every Runner that will need the login.

Upvotes: -2

Sid
Sid

Reputation: 408

Being a newbie m not allowed to comment so here is some info, assuming you are using cucumber-jvm.

Short answer, No, Cucumber by itself doesnt have the option to read step names. You could use the method names to identify what was called.

Also, @BEFORE STEP / @AFTER STEP tags are not yet available so you will have to define the call for each step.

https://github.com/cucumber/cucumber-jvm/pull/838#issuecomment-234110573

or the testing framework like junit or testng could let you access the execution details - something like this: http://junit.org/junit4/javadoc/4.12/org/junit/rules/TestWatcher.html.

And if you really need the step names only for reporting purposes, you can simply parse the xml report that the testing framework generates.

Upvotes: 1

Thomas Sundberg
Thomas Sundberg

Reputation: 4323

Are you asking if it is possible to get some logging that indicates that the step When I enter valid credentials is executed?

If so, the answer is yes.

Cucumber as such doesn't have a notion of logging so you would have to add your own favorite logging framework. Since Cucumber doesn't know about logging through your favorite log framework, you will have to add a log statement in each step you implement in Java.

I have never seen the need for logging myself. The execution log from Maven, or whatever build tool you are using, have been sufficient for me for a long time.

The reports include the steps executed so that case is covered.

Upvotes: 0

Related Questions