I need this so that i can pass the required arguments to the class before executing that method for reporting, logging,etc., so that test cases can be executed in parallel. I am in the process of converting my keyword driven selenium framework to BDD Gherkin selenium framework .In Keyword driven framework, i get the keywords from Excel , pass the required parameters to the class through Java reflection before executing the method
String [] argv = new String[]{
"-g"
,"businesscomponents"
,"./Features"
//,"-t","@P1,@P2"
,"-n","Validate_login_search_using_sku_id"
};
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
byte exitstatus = Main.run(argv, contextClassLoader);
This was much easier to do than I thought. This just required modifying couple of lines of one class (Runtime.java) to figure out all the hook and step methods. All it then needed was to run in dryrun mode set to true to get all the details.
Override Runtime.java behaviour -
Download the 1.2.5 class from this link https://github.com/cucumber/cucumber-jvm/blob/v1.2.5/core/src/main/java/cucumber/runtime/Runtime.java
Create a package cucumber.runtime in the classpath and copy the Runtime class inside it.
3.In private method runHooks(line 209) comment out lines 210 and 214.
private void runHooks(List<HookDefinition> hooks, Reporter reporter, Set<Tag> tags, boolean isBefore) {
//if (!runtimeOptions.isDryRun()) {
for (HookDefinition hook : hooks) {
runHookIfTagsMatch(hook, reporter, tags, isBefore);
}
//}
}
if(!runtimeOptions.isDryRun())
hook.execute(scenarioResult);
CustomFormatter - This will now store all the details in a ThreadLocal variable.
public class CustomFormatter implements Reporter, Formatter {
private Map<String, Map<Scenario, List<StepDefinitionMatch>>> featureDetails;
private Map<Scenario, List<StepDefinitionMatch>> scenarioDetails;
private List<StepDefinitionMatch> stepDetails;
private String featureUri = "";
private Scenario scenario;
private Map<Scenario, List<Match>> beforeHooks;
private Map<Scenario, List<Match>> afterHooks;
private List<Match> beforeMatches;
private List<Match> afterMatches;
public CustomFormatter() {
featureDetails = new LinkedHashMap<>();
beforeHooks = new LinkedHashMap<>();
afterHooks = new LinkedHashMap<>();
}
@Override
public void before(Match match, Result result) {
beforeMatches.add(match);
}
@Override
public void result(Result result) {
}
@Override
public void after(Match match, Result result) {
afterMatches.add(match);
}
@Override
public void match(Match match) {
//ThreadLocalStepDefinitionMatch.set((StepDefinitionMatch) match);
stepDetails.add((StepDefinitionMatch) match);
}
@Override
public void embedding(String mimeType, byte[] data) {
}
@Override
public void write(String text) {
}
@Override
public void syntaxError(String state, String event,
List<String> legalEvents, String uri, Integer line) {
}
@Override
public void uri(String uri) {
featureUri = uri;
}
@Override
public void feature(Feature feature) {
scenarioDetails = new LinkedHashMap<>();
}
@Override
public void scenarioOutline(ScenarioOutline scenarioOutline) {
}
@Override
public void examples(Examples examples) {
}
@Override
public void startOfScenarioLifeCycle(Scenario scenario) {
this.scenario = scenario;
stepDetails = new ArrayList<>();
beforeMatches = new ArrayList<>();
afterMatches = new ArrayList<>();
}
@Override
public void background(Background background) {
}
@Override
public void scenario(Scenario scenario) {
}
@Override
public void step(Step step) {
}
@Override
public void endOfScenarioLifeCycle(Scenario scenario) {
scenarioDetails.put(this.scenario, stepDetails);
beforeHooks.put(this.scenario, beforeMatches);
afterHooks.put(this.scenario, afterMatches);
}
@Override
public void done() {
}
@Override
public void close() {
/*System.out.println(featureDetails);
System.out.println(beforeHooks);
System.out.println(afterHooks);*/
StepHookDetails shd = new StepHookDetails(featureDetails, beforeHooks, afterHooks);
ThreadLocalStepHookDetails.set(shd);
}
@Override
public void eof() {
featureDetails.put(featureUri, scenarioDetails);
}
}
If you uncomment the print lines in the close() method you can see the output as something like this.
Step Methods Match - Corresponds to Map - Map<String, Map<Scenario, List<StepDefinitionMatch>>> featureDetails in CustomFormatter class
{src/test/resources/features/format2.feature={gherkin.formatter.model.Scenario@38d8f54a=[cucumber.runtime.StepDefinitionMatch@6236eb5f, cucumber.runtime.StepDefinitionMatch@7c1e2a9e], gherkin.formatter.model.Scenario@49e53c76=[cucumber.runtime.StepDefinitionMatch@fa36558, cucumber.runtime.StepDefinitionMatch@672872e1]}}
Before Hook Matches -Corresponds to Map - Map<Scenario, List<Match>> beforeHooks in CustomFormatter class
{gherkin.formatter.model.Scenario@38d8f54a=[gherkin.formatter.model.Match@32910148], gherkin.formatter.model.Scenario@49e53c76=[gherkin.formatter.model.Match@3f56875e]}
After Hook Matches - -Corresponds to Map - Map<Scenario, List<Match>> afterHooks in CustomFormatter class
{gherkin.formatter.model.Scenario@38d8f54a=[gherkin.formatter.model.Match@2b4bac49], gherkin.formatter.model.Scenario@49e53c76=[gherkin.formatter.model.Match@fd07cbb]}
This is for a run of a single feature file as below. If you have multiple feature files you will have multiple feature keys in featureDetails
map. The hooks will be added to the hook maps.
Feature: Validating sample Two
Background:
Given user gets count from "Car0"
@Format
Scenario: Scenario Two
And user gets count from "Car1"
@Format
Scenario: Scenario Two
And user gets count from "Car1"
Important - The key which binds the three together is the Scenario
object. Pretty easy to iterate the featureDetails
map and get the before hook if available, then available steps and then after hook.
For getting hook method names use
ThreadLocal Class -
public class ThreadLocalStepHookDetails {
private static final ThreadLocal<StepHookDetails> threadStepHookDetails = new InheritableThreadLocal<StepHookDetails>();
private ThreadLocalStepHookDetails() {
}
public static StepHookDetails get() {
return threadStepHookDetails.get();
}
public static void set(StepHookDetails match) {
threadStepHookDetails.set(match);
}
public static void remove() {
threadStepHookDetails.remove();
}
}
StepHookDetails class -
public class StepHookDetails {
private Map<String, Map<Scenario, List<StepDefinitionMatch>>> featureDetails;
private Map<Scenario, List<Match>> beforeHooks;
private Map<Scenario, List<Match>> afterHooks;
public StepHookDetails() {}
public StepHookDetails(
Map<String, Map<Scenario, List<StepDefinitionMatch>>> featureDetails,
Map<Scenario, List<Match>> beforeHooks,
Map<Scenario, List<Match>> afterHooks) {
this.featureDetails = featureDetails;
this.beforeHooks = beforeHooks;
this.afterHooks = afterHooks;
}
public Map<String, Map<Scenario, List<StepDefinitionMatch>>> getFeatureDetails() {
return featureDetails;
}
public void setFeatureDetails(
Map<String, Map<Scenario, List<StepDefinitionMatch>>> featureDetails) {
this.featureDetails = featureDetails;
}
public Map<Scenario, List<Match>> getBeforeHooks() {
return beforeHooks;
}
public void setBeforeHooks(Map<Scenario, List<Match>> beforeHooks) {
this.beforeHooks = beforeHooks;
}
public Map<Scenario, List<Match>> getAfterHooks() {
return afterHooks;
}
public void setAfterHooks(Map<Scenario, List<Match>> afterHooks) {
this.afterHooks = afterHooks;
}
}
Check details in store -
You can use the below code as a sample to see the details inside the maps and in order. ie before steps after.
StepHookDetails sd = ThreadLocalStepHookDetails.get();
sd.getFeatureDetails().entrySet().stream().forEach(
e -> e.getValue().entrySet().stream().forEach(
ee -> {
System.out.println(ee.getKey().getLine());
sd.getBeforeHooks().get(ee.getKey()).stream().forEach(bh -> System.out.println(bh.getLocation()));
ee.getValue().stream().forEach(s -> System.out.println(s.getLocation() + " " + s.getArguments()));
sd.getAfterHooks().get(ee.getKey()).stream().forEach(ah -> System.out.println(ah.getLocation()));
}));
For the above feature file got this output
Scenario Line Number 8 Before Hook SampleSteps.before() Steps SampleSteps.userGetsCountFromAndStores(String) [Car0] Steps SampleSteps.userGetsCountFromAndStores(String) [Car1] After Hook SampleSteps.afterOne(Scenario) Scenario Line Number 15 Before Hook SampleSteps.before() Steps SampleSteps.userGetsCountFromAndStores(String) [Car0] Steps SampleSteps.userGetsCountFromAndStores(String) [Car1] After Hook SampleSteps.afterOne(Scenario)
This will also work for scenariooutlines.
UPDATED -
Feature: UI Automation
@P1
Scenario: Validate_Home_page
Given the customer opens launches the homepage
@Given("^the customer opens launches the homepage$")
public void openbrowser() { }
public static void main (String args[]) throws IOException {
String[] argv = new String[] { "-g", "cucumpar.test.stepdefs", "src/test/resources/features/features.feature",
"-t", "@P1" , "-p", "cusform.CustomFormatter","-d"};
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
byte exitstatus = Main.run(argv, contextClassLoader);
----------------
StepHookDetails sd = ThreadLocalStepHookDetails.get();
----------------
}
Results
{src/test/resources/features/features.feature={gherkin.formatter.model.Scenario@6771beb3=[cucumber.runtime.StepDefinitionMatch@51399530]}} {gherkin.formatter.model.Scenario@6771beb3=[]} {gherkin.formatter.model.Scenario@6771beb3=[]} Scenario Line Number 4 Steps SampleSteps.openbrowser() []
UPDATED
Need to add some code to the StepDefinitionMatch class. Similar to what u had done for Runtime.java download the 1.2.5 class at - https://github.com/cucumber/cucumber-jvm/blob/v1.2.5/core/src/main/java/cucumber/runtime/StepDefinitionMatch.java. Copy this class to the existing cucumber.runtime package. Then add the following method --
public List<List<String>> getDataTableData() {
List<List<String>> data = new ArrayList<>();
if(step.getRows()!=null)
step.getRows().forEach(row -> data.add(row.getCells()));
return data;
}
That is it. Call this method in the existing print method like below.
ee.getValue().stream().forEach(s -> System.out.println("Steps " + s.getLocation() + " " + s.getArguments() + " " + s.getDataTableData()));
For this feature file
Scenario: Scenario Four
And car gets count
| car | owner |
| Car1 | OwnerOne |
| Car2 | OwnerTwo |
| Car3 | OwnerThree |
Got this output - The first line is the header, customize as required.
SampleSteps.carGetsCount(DataTable) [] [[car, owner], [Car1, OwnerOne], [Car2, OwnerTwo], [Car3, OwnerThree]]
UPDATE
public Map<String, List<String>> getDataTableData() {
Map<String, List<String>> mapData = new HashMap<>();
if(step.getRows()!=null) {
List<List<String>> data = new ArrayList<>();
step.getRows().forEach(row -> data.add(row.getCells()));
List<String> keys = data.remove(0);
data.stream().forEach(row -> IntStream.range(0, keys.size()).forEach(index -> {
List<String> tempList = new ArrayList<>();
tempList.add(row.get(index));
mapData.compute(keys.get(index), (key, val) -> {
if(val==null)
return tempList;
val.addAll(tempList);
return val;
});
}));
}
return mapData;
}