Skip to content

Commit

Permalink
Merge pull request #1416 from joelpramos/feature/string-interpolation…
Browse files Browse the repository at this point in the history
…-scenario-name-#1413

Adding support for JavaScript String interpolation in Scenario Names a…
  • Loading branch information
ptrthomas authored Jan 4, 2021
2 parents cb41a7b + 725e9f0 commit 56d90af
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 2 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4037,6 +4037,24 @@ Scenario Outline: inline json

For another example, see: [`examples.feature`](karate-demo/src/test/java/demo/outline/examples.feature).

If you're looking for more complex ways of naming your scenarios you can use JavaScript String interpolation by and include placeholders in your scenario name.

```cucumber
Scenario Outline: name is ${name.first} ${name.last} and age is ${age}
* match name.first == "#? _ == 'Bob' || _ == 'Nyan'"
* match name.last == "#? _ == 'Dylan' || _ == 'Cat'"
* match title == karate.info.scenarioName
Examples:
| name! | age | title |
| { "first": "Bob", "last": "Dylan" } | 10 | name is Bob Dylan and age is 10 |
| { "first": "Nyan", "last": "Cat" } | 5 | name is Nyan Cat and age is 5 |
```

String interpolation will support variables in scope and/or Examples (including functions defined globally, but not functions defined in the background), operators and even the Java Interop access and access to the Karate API.

For some more examples check [`test-outline-name-js.feature`](karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name-js.feature).

### The Karate Way
The limitation of the Cucumber `Scenario Outline:` (seen above) is that the number of rows in the `Examples:` is fixed. But take a look at how Karate can [loop over a `*.feature` file](#data-driven-features) for each object in a JSON array - which gives you dynamic data-driven testing, if you need it. For advanced examples, refer to some of the scenarios within this [demo](karate-demo): [`dynamic-params.feature`](karate-demo/src/test/java/demo/search/dynamic-params.feature#L70).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,7 @@ public Match.Result match(Match.Type matchType, Object actual, Object expected)
private static final String VARIABLE_PATTERN_STRING = "[a-zA-Z][\\w]*";
private static final Pattern VARIABLE_PATTERN = Pattern.compile(VARIABLE_PATTERN_STRING);
private static final Pattern FUNCTION_PATTERN = Pattern.compile("^function[^(]*\\(");
private static final Pattern JS_PLACEHODER = Pattern.compile("\\$\\{.*?\\}");

public static boolean isJavaScriptFunction(String text) {
return FUNCTION_PATTERN.matcher(text).find();
Expand All @@ -1803,6 +1804,10 @@ public static boolean isValidVariableName(String name) {
return VARIABLE_PATTERN.matcher(name).matches();
}

public static boolean hasJavaScriptPlacehoder(String exp) {
return JS_PLACEHODER.matcher(exp).find();
}

public static final boolean isVariableAndSpaceAndPath(String text) {
return text.matches("^" + VARIABLE_PATTERN_STRING + "\\s+.+");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@
*/
package com.intuit.karate.core;

import com.intuit.karate.RuntimeHook;
import com.intuit.karate.ScenarioActions;
import com.intuit.karate.FileUtils;
import com.intuit.karate.KarateException;
import com.intuit.karate.LogAppender;
import com.intuit.karate.Logger;
import com.intuit.karate.RuntimeHook;
import com.intuit.karate.ScenarioActions;
import com.intuit.karate.http.ResourceType;
import com.intuit.karate.shell.StringLogAppender;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -349,6 +350,10 @@ public void beforeRun() {
featureRuntime.suite.hooks.forEach(h -> h.beforeScenario(this));
}
}
if(!this.scenario.isDynamic()) {
// don't evaluate names when running the background section
this.evaluateScenarioName();
}
}

@Override
Expand Down Expand Up @@ -474,4 +479,19 @@ public String toString() {
return scenario.toString();
}

public void evaluateScenarioName() {
String scenarioName = this.scenario.getName();
boolean wrappedByBackTick = scenarioName != null && scenarioName.length() > 1 && '`' == scenarioName.charAt(0) && '`' == scenarioName.charAt((scenarioName.length() - 1));
boolean hasJavascriptPlaceholder = ScenarioEngine.hasJavaScriptPlacehoder(scenarioName);
if (wrappedByBackTick || hasJavascriptPlaceholder) {
String eval = scenarioName;
if(!wrappedByBackTick) {
eval = '`' + eval + '`';
}

String evaluatedScenarioName = this.engine.evalJs(eval).getAsString();
this.scenario.setName(evaluatedScenarioName);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ class FeatureParserTest {
static final Logger logger = LoggerFactory.getLogger(FeatureParserTest.class);

static FeatureResult execute(String name) {
return execute(name, null);
}

static FeatureResult execute(String name, String env) {
Feature feature = Feature.read("classpath:com/intuit/karate/core/parser/" + name);
Runner.Builder builder = Runner.builder();
builder.karateEnv(env);
builder.tags("~@ignore");
FeatureRuntime fr = FeatureRuntime.of(new Suite(builder), feature);
fr.run();
Expand Down Expand Up @@ -146,6 +151,12 @@ void testOutlineName() {
match(map.get("title"), "name is Nyan and age is 5");
}

@Test
void testOutlineNameJs() {
FeatureResult result = execute("test-outline-name-js.feature", "unit-test");
assertFalse(result.isFailed());
}

@Test
void testTagsMultiline() {
FeatureResult result = execute("test-tags-multiline.feature");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
Feature:

Background:
* def sum = function(x,y){ return x + y; }
* def js_data =
"""
[
{
"name": "Bob",
"age": 10,
"title": "name is Bob and age is 10"
},
{
"name": "Nyan",
"age": 5,
"title": "name is Nyan and age is 5"
}
]
"""

* def nested_js_data =
"""
[
{
"name": {
"first": "Bob",
"last": "Dylan"
},
"age": 10,
"title": "name is Bob and age is 10"
},
{
"name": {
"first": "Nyan",
"last": "Cat"
},
"age": 5,
"title": "name is Nyan and age is 5"
}
]
"""

Scenario Outline: `name is ${name} and age is ${age}`
* def name = '<name>'
* match name == "#? _ == 'Bob' || _ == 'Nyan'"
* match title == karate.info.scenarioName

Examples:
| name | age | title |
| Bob | 10 | name is Bob and age is 10 |
| Nyan | 5 | name is Nyan and age is 5 |


Scenario Outline: `name is ${name} and age is ${age}`
* def name = '<name>'
* match name == "#? _ == 'Bob' || _ == 'Nyan'"
* match title == karate.info.scenarioName

Examples:
| js_data |


Scenario Outline: `name is ${name.first} and age is ${age}`
* match name.first == "#? _ == 'Bob' || _ == 'Nyan'"
* match title == karate.info.scenarioName

Examples:
| nested_js_data |


Scenario Outline: `name is ${name.first} ${name.last} and age is ${age}`
* match name.first == "#? _ == 'Bob' || _ == 'Nyan'"
* match name.last == "#? _ == 'Dylan' || _ == 'Cat'"
* match title == karate.info.scenarioName

Examples:
| name! | age | title |
| { "first": "Bob", "last": "Dylan" } | 10 | name is Bob Dylan and age is 10 |
| { "first": "Nyan", "last": "Cat" } | 5 | name is Nyan Cat and age is 5 |


# String interpolation allows you to use operators
Scenario: one plus one equals ${1 + 1}
* match karate.info.scenarioName == "one plus one equals 2"

Scenario: `one plus one equals ${1 + 1}`
* match karate.info.scenarioName == "one plus one equals 2"

# can even access the karate object
Scenario: scenario execution (env = ${karate.env})
# the env is set on the unit test in FeatureParserTest.java
* match karate.info.scenarioName == "scenario execution (env = unit-test)"

# functions can also be used, including access to the Java Interop API
Scenario: math scenario: should return ${java.lang.Math.pow(2, 2)}
* def powResult = java.lang.Math.pow(2, 2)
* match karate.info.scenarioName == "math scenario: should return " + powResult
* match karate.info.scenarioName == "math scenario: should return 4"
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
Feature:

Background:
* def js_data =
"""
[
{
"name": "Bob",
"age": 10,
"title": "name is Bob and age is 10"
},
{
"name": "Nyan",
"age": 5,
"title": "name is Nyan and age is 150"
}
]
"""

Scenario Outline: name is <name> and age is <age>
* def name = '<name>'
* match name == "#? _ == 'Bob' || _ == 'Nyan'"
Expand All @@ -9,3 +26,12 @@ Examples:
| name | age | title |
| Bob | 10 | name is Bob and age is 10 |
| Nyan | 5 | name is Nyan and age is 5 |


Scenario Outline: name is <name> and age is <age>
* def name = '<name>'
* match name == "#? _ == 'Bob' || _ == 'Nyan'"
* def title = karate.info.scenarioName

Examples:
| js_data |

0 comments on commit 56d90af

Please sign in to comment.