diff --git a/README.md b/README.md index 6441d33fb..d462e9340 100755 --- a/README.md +++ b/README.md @@ -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). diff --git a/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java b/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java index cc5d0f5cd..15c3664f7 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java +++ b/karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java @@ -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(); @@ -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+.+"); } diff --git a/karate-core/src/main/java/com/intuit/karate/core/ScenarioRuntime.java b/karate-core/src/main/java/com/intuit/karate/core/ScenarioRuntime.java index caa33b2a4..183912882 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/ScenarioRuntime.java +++ b/karate-core/src/main/java/com/intuit/karate/core/ScenarioRuntime.java @@ -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; @@ -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 @@ -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); + } + } + } diff --git a/karate-core/src/test/java/com/intuit/karate/core/parser/FeatureParserTest.java b/karate-core/src/test/java/com/intuit/karate/core/parser/FeatureParserTest.java index 56bdab00e..21c308834 100644 --- a/karate-core/src/test/java/com/intuit/karate/core/parser/FeatureParserTest.java +++ b/karate-core/src/test/java/com/intuit/karate/core/parser/FeatureParserTest.java @@ -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(); @@ -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"); diff --git a/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name-js.feature b/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name-js.feature new file mode 100644 index 000000000..9720fa411 --- /dev/null +++ b/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name-js.feature @@ -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 = '' + * 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 = '' + * 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" \ No newline at end of file diff --git a/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name.feature b/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name.feature index e7a25885f..d043c8057 100644 --- a/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name.feature +++ b/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name.feature @@ -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 and age is * def name = '' * match name == "#? _ == 'Bob' || _ == 'Nyan'" @@ -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 and age is + * def name = '' + * match name == "#? _ == 'Bob' || _ == 'Nyan'" + * def title = karate.info.scenarioName + +Examples: + | js_data |