Sometimes when working with cucumber, the static nature of the tool limits your options quite a bit. For example, you can declare variables but these variables are static. If for example, every time you run a test you want to generate unique variable (i.e ID) you are limited to an option of implementing this functionality in a step rather than the test case itself. In my opinion, all steps should be as generic as possible and follow DRY development pattern, therefore it’s important to exclude some of the code which is specific to one scenario and make steps as generic as possible.
In this blog post, I will try to cover a way of implementing javascript support in your JAVA Cucumber feature files, to make them more dynamic and flexible.
Bellow, you can check an example of how this functionality work. Essentially, when you declare your variable there is an option to include a javascript function which allows you to generate a dynamic variable.
Example:
Feature: Test to load a js file in Genie js context
@load.js.file.in.genie.context
Scenario: Load a local js file in genie js context
Given I load this js file "/data/test.js" in genie's js context
Given I define the following user defined variables:
| currentDate | $js{getDate()} |
| repeatedString | $js{repeat('A',5)} |
| sum | $js{sum(4,5)} |
| randomNum | $js{ranNum = Math.floor((Math.random() * Math.pow(10,4)) + 1).toString();} |
How can this be used in a test case? Simple just pass it as variable in the test case:
And I send this event to Kafka:
| unique_ID_SEND | name | sum |
| QA_TEST_${randomNum} | ${repeatedString} | ${sum} |
The trick how to do it is to use the Cucumber data table, convert it into a map, and evaluate every variable to check if it’s a dynamic “$js{}” or it’s a regular one. If it is, in fact, a dynamic js variable, evaluate the expression. A quick snippet from my personal code stack:
@Given("^I define the following user defined variables:$")
public void iDefineTheFollowingUserDefinedVariables(DataTable dataTable) {
dataTable.asMap(String.class, String.class).forEach((k, v) ->
{ v = VariableUtil.evaluateEnvVariable(v);
if (v.equals("${UUID}"))
v = UUID.randomUUID().toString();
else if (v.equals("${ShortID}"))
v = MessagingUtils.getInstance().getShortNumericID();
else if (v.equals("${AlphaNumericShortID}"))
v =MessagingUtils.getInstance().getShortAlphaNumericID(15);
else
v = VariableUtil.evaluateJSExpressionOnFrameworkVariable(v).toString();
HTMLLogUtil.addLongMessage(String.format("Set the value to UDF variable '%s' as '%s'", k, v));
setCentralStorage(k, v);
});
}
As you noticed, when I recognise it follows dynamic variable declaration As you noticed, when I recognize it follows dynamic variable declaration pattern then I try to evaluate JavaScript with using this static method: VariableUtil.evaluateJSExpressionOnFrameworkVariable(v)
In this utility we start the evaluation JavaScript expression:
public static String evaluateJSExpressionOnFrameworkVariable(String str) {
str = replaceAllFrameworkVariableValues(str).replace("\n", "");
String subMatch = null;
String expression = null;
String subValue = null;
if(str.startsWith("$js{function ")) {
expression = PluginUtils.getInstance().getJSExpression(str);
return jsUtils.getInstance().execute(expression).toString();
}
Matcher matcher = JS_PATTERN.matcher(str);
while(matcher.find()) {
subMatch = matcher.group();
subMatch = matchCurlyBrackets(str, subMatch);
LOGGER.debug("Evaluating JS expression : " + subMatch);
expression = PluginUtils.getInstance().getJSExpression(subMatch);
subValue = jsUtils.getInstance().execute(expression).toString();
str = str.replace(subMatch, subValue);
matcher = JS_PATTERN.matcher(str);
}
return str;
}
To recognize a pattern of JavaScript expression this quick utility can be used:
public boolean isJsExpression(String jsExpression) {
return jsExpression.matches("[$][j][s][{](.*?)[}]$");
}
public String getJSExpression(String str) {
Pattern pattern = Pattern.compile("[$][j][s][{](.*?)[}]$");
Matcher matcher = pattern.matcher(str);
if(matcher.find()) {
return matcher.group(1);
} else {
LOGGER.info("Not a JS Expression");
return "";
}
}
To evaluate JavaScript expression you have plenty of options, like using native JAVA inbuilt ScriptEngine or an external library. A good example of evaluation would be:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
Object result = engine.eval("4*5");
On top of all that if you want to push it even further it is possible to use ENVIRONMENT_VARIABLES as a javascript expression which enables so much more dynamic functions (like CI integration and all that good stuff):
private static String environmentVarPattern = "\\$Env\\{(.*?)\\}";
private static boolean isEnvVariable(String var){
return var.matches("(.*?)" + environmentVarPattern + "(.*?)");
}
private static String getEnvVariableName(String varString){
Pattern pattern = Pattern.compile("(.*?)" + environmentVarPattern + "(.*?)");
Matcher matcher = pattern.matcher(varString);
if (matcher.find()) {
String envVarName = matcher.group(2);
String envVarValue = ConfigUtil.getString(envVarName);
return varString.replace(String.format("$Env{%s}",envVarName),envVarValue);
}else{
LOGGER.error("Error in evaluating the Env Variable");
throw new RuntimeException("Error in evaluating the Env Variable: " + varString);
}
}
All in all, by utilizing a cucumber data table it is possible to create very dynamic test cases that support javascript expressions or can read environment variables.
This can be used in many ways if you want a unique ID, if you want to generate today’s date, if you want to grab the environment from your environment variables, if you want to grab a variable from Jenkins (env variable). All this can be easily achieved with this clever solution.