11

I am trying to find out if there is an option to figure out the cucumber step currently getting executed, I am trying to perform certain action depending on the step name.

I can see StepDefinitionMatch class gets the steps, but I am not sure how can I access the steps at runtime. Any help? Adding a snapshot of the call stack if that helps.

public StepDefinitionMatch(List<Argument> arguments, StepDefinition stepDefinition, String featurePath, Step step, LocalizedXStreams localizedXStreams) {
    super(arguments, stepDefinition.getLocation(false));
    this.stepDefinition = stepDefinition;
    this.featurePath = featurePath;
    this.step = step;
    this.localizedXStreams = localizedXStreams;
}

enter image description here

0

8 Answers 8

2

If you are using cucumber-jvm version that is 5.6.0 or latest please follow below solution. since Reporter class is deprecated you have to implement ConcurrentEventListener and add this class to your TestRunner plugin section.

public class StepDetails implements ConcurrentEventListener {
    public static String stepName;

    public EventHandler<TestStepStarted> stepHandler = new EventHandler<TestStepStarted>() {
        @Override
        public void receive(TestStepStarted event) {
            handleTestStepStarted(event);
        }

    };

    @Override
    public void setEventPublisher(EventPublisher publisher) {
        publisher.registerHandlerFor(TestStepStarted.class, stepHandler);
    }

    private void handleTestStepStarted(TestStepStarted event) {
        if (event.getTestStep() instanceof PickleStepTestStep) {
            PickleStepTestStep testStep = (PickleStepTestStep)event.getTestStep();
            stepName = testStep.getStep().getText();
        }


    }
}

later add this class to your plugin section of cucumberOptions of your runner file

@CucumberOptions(dryRun=false,plugin = {"<yourPackage>.StepDetails"})

you can get the stepname wherever you want just by calling the stepName variable

System.out.println(StepDetails.stepName);
Sign up to request clarification or add additional context in comments.

1 Comment

Perfect solution! Not sure why this was not accepted.
1

You can implement a ConcurrentEventListener, setting it in the plugin section:

@RunWith(Cucumber.class)
@CucumberOptions(
...
plugin = {
   ...,
   "com.mycompany.myproduct.AcceptanceStepNameLogger"
}...

AcceptanceStepNameLogger example (in this case I only needed to log PickleStepTestStep steps, not hooks):

import cucumber.api.*;
import cucumber.api.event.*;

public class AcceptanceStepNameLogger implements ConcurrentEventListener {

    @Override
    public void setEventPublisher(EventPublisher publisher) {

        publisher.registerHandlerFor(TestStepStarted.class, new EventHandler<TestStepStarted>() {
            @Override
            public void receive(TestStepStarted event) {
                if (event.testStep instanceof PickleStepTestStep) {
                    final PickleStepTestStep ev = (PickleStepTestStep) event.testStep;
                    final String args = StringUtils.join(ev.getDefinitionArgument().stream().map(Argument::getValue).toArray(), ",");
                    String testDescription = ev.getStepText() + " : " + ev.getStepLocation();
                    if (StringUtils.isNotBlank(args)) {
                        testDescription += (" : args = (" + args + ")");
                    }
                    System.out.println("STARTING STEP: " + testDescription);
                }
            }
        });

        publisher.registerHandlerFor(TestStepFinished.class, new EventHandler<TestStepFinished>() {
            @Override
            public void receive(TestStepFinished event) {
                if (event.testStep instanceof PickleStepTestStep) {
                    PickleStepTestStep ev = (PickleStepTestStep) event.testStep;
                    final String testDescription = ev.getStepText() + " : " + ev.getStepLocation();
                    System.out.println("FINISHED STEP: " + testDescription);
                }
            }
        });

    }

}

Comments

1
package runner.steps;

import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.*;
import java.text.SimpleDateFormat;
import java.util.Date;

public class StepStartedTestListener implements ConcurrentEventListener {
@Override
public void setEventPublisher(EventPublisher publisher) {
    publisher.registerHandlerFor(TestStepStarted.class,this::handleTestStepStarted);
}


private void handleTestStepStarted(TestStepStarted event){
    if (event.getTestStep() instanceof PickleStepTestStep) {
        PickleStepTestStep testStep = (PickleStepTestStep)event.getTestStep();
        String stepName = testStep.getStep().getText();
        stepDetailsPrint(stepName);
    }
}

private void stepDetailsPrint(String stepName){
    String ANSI_RESET = "\u001B[0m";
    String ANSI_GREEN = "\u001B[42m";
    Date date = new Date();
    SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
    System.out.println("Step started running at: "+formatter.format(date));
    System.out.println(ANSI_GREEN
            + stepName
            + ANSI_RESET);
}

}

And then you need to add this class as a plugin listner in the cucmber options:

@CucumberOptions(
    plugin =   { "runner.steps.StepStartedTestListener"}, 

)

Comments

0

One way to get hold of the runtime variables is to use the plugin option. Though it seems like an abuse of the Reporter interface to access the StepDefinitionMatch variable for Cucumber version 1.2.5.

For this create a custom class which implements the Reporter interface.

public class CustomFormatter implements Reporter{

    public  CustomFormatter() { }

    @Override
    public void before(Match match, Result result) {}

    @Override
    public void result(Result result) {}

    @Override
    public void after(Match match, Result result) {}

    @Override
    public void match(Match match) {
        ThreadLocalStepDefinitionMatch.set((StepDefinitionMatch)match);
    }

    @Override
    public void embedding(String mimeType, byte[] data) {}

    @Override
    public void write(String text) {}
}

The ThreadLocal class to store the StepDefinitionMatch variable.

public class ThreadLocalStepDefinitionMatch {

    private static final ThreadLocal<StepDefinitionMatch> threadStepDefMatch = new InheritableThreadLocal<StepDefinitionMatch>();

    private ThreadLocalStepDefinitionMatch() {
    }

    public static StepDefinitionMatch get() {
        return threadStepDefMatch.get();
    }

    public static void set(StepDefinitionMatch match) {
        threadStepDefMatch.set(match);
    }

    public static void remove() {
        threadStepDefMatch.remove();
    }
}

The declaration for custom plugin in the runner class

@CucumberOptions(plugin = { "pretty", "html:report", "json:reports.json",
        "rerun:target/rerun.txt", "cusform.CustomFormatter" }

Finally accessing the StepDefinitionMatch variable in the step definition class

@When("^user gets count from \"([^\"]*)\"$")
    public void userGetsCountFromAndStores(String arg) {
        StepDefinitionMatch match = ThreadLocalStepDefinitionMatch.get();
        System.out.println(match.getStepName());
        System.out.println(match.getPattern());
        System.out.println(match.getArguments());
    }

The console output gives the following

user gets count from "Car1"
^user gets count from "([^"]*)"$
[Car1]

You are using a pretty old Cucumber version, if you upgrade to Cucumber 2 there will be some changes. The StepDefinitionMatch replaced by PickleTestStep. The declaration in the runner remains the same. The ThreadLocal class change the stored class to PickleTestStep.

The custom formatter becomes

public class CustomFormatter implements Formatter {

    public CustomFormatter() {}

    private EventHandler<TestStepStarted> stepStartedHandler = new EventHandler<TestStepStarted>() {
        @Override
        public void receive(TestStepStarted event) {
            handleTestStepStarted(event);
        }
    };

    @Override
    public void setEventPublisher(EventPublisher publisher) {
        publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler);
    }

    private void handleTestStepStarted(TestStepStarted event) { 
        if(event.testStep instanceof PickleTestStep) {
            ThreadLocalPickleStep.set((PickleTestStep)event.testStep);
        }

    }
}

Accessing the StepDefinitionMatch variable in the step definition class

@When("^user gets count from \"([^\"]*)\"$")
    public void userGetsCountFromAndStores(String arg) {

        System.out.println(ThreadLocalPickleStep.get().getStepText());
        System.out.println(ThreadLocalPickleStep.get().getPattern());
        System.out.println(ThreadLocalPickleStep.get().getDefinitionArgument());
    }

The output is the same as before.

Comments

0

Below code can be used to get current step method name

new Throwable().getStackTrace()[0].getMethodName()

Example: Given user is on Login page

@Given("^user is on Login page$") public void user_is_on_Login_page() throws Throwable {

}

new Throwable().getStackTrace()[0].getMethodName() => it will return current step method name as "user_is_on_Login_page"

Comments

0

My Cucumber version is 4.7.2 and i wanted to get the step name in my After annotation so that i can use is as fail message while capturing screenshot.i got the Steptext in BeforeStep Annotations and stored it in run time data. I used below code which helped me.

     private int currentStepDefIndex = 0;
    String  currentStepDescr = null;

    @BeforeStep
    public void getCurrentStep(Scenario scenario) throws Exception {
        currentStepDefIndex++;

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

        TestCase Tcs = (TestCase) testCase.get(scenario);
        Field TestSteps = Tcs.getClass().getDeclaredField("testSteps");
        TestSteps.setAccessible(true);

        List<TestStep> teststeps = Tcs.getTestSteps();
        try {
            PickleStepTestStep stepDefs = (PickleStepTestStep) teststeps.get(currentStepDefIndex);
            
               currentStepDescr= stepDefs.getStepText();
                TestContext.getInstance().setValue("CurrentStep", currentStepDescr);
          


            currentStepDefIndex++;
        } catch (Exception e) {
            ignore.printStackTrace();
        }
    }


    @After
    public void OnFailureScreenshot(Scenario scenario) {


        currentStepDescr=TestContext.getInstance().getValue("CurrentStep");

        if(scenario.isFailed()){
           //code to capture screenshot and pass currentStepDescription as fail message.
        }

    }
}

Comments

0

Here is what I did in cucumber 4,

You can Create a event listener class and add it as a plugin at cucumber options:

public class TestStepWatch implements ConcurrentEventListener {
    private EventHandler<TestStepStarted> stepHandler = new EventHandler<TestStepStarted>()
    {
        @Override
        public void receive(TestStepStarted event)
        {
            handleTestStep(event);
        }

        private synchronized void handleTestStep(TestStepStarted event)
        {
            System.out.println("Running step:"+event.testStep.getStepText());
        }
    }
}

1 Comment

TestStepStarted only has getCodeLocation, and does not have getStepText.
-1

Just wait for Cucumber 3.0.0 release, you can access the step names using @AfterStep and @BeforeStep Annotations.

https://github.com/cucumber/cucumber-jvm/blob/master/CHANGELOG.md https://github.com/cucumber/cucumber-jvm/pull/1323

Thanks to Aniket (Coding-Yogi) https://github.com/coding-yogi

2 Comments

What is the object that needs to be passed to the AfterStep and BeforeStep? The Scenario object getName() returns the description on the scenario.
How to use @BeforeStep?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.