From 3d684253b986c6f5a0ac66ac11c2354218d91e2d Mon Sep 17 00:00:00 2001 From: Joel Ramos Date: Mon, 28 Dec 2020 16:44:08 -0500 Subject: [PATCH 1/5] Adding support for JavScript String interpolation in Scenario Names and Descriptions --- README.md | 19 +++ .../java/com/intuit/karate/StringUtils.java | 34 ++++- .../intuit/karate/core/ScenarioIterator.java | 2 +- .../intuit/karate/core/ScenarioRuntime.java | 43 ++++++- .../com/intuit/karate/StringUtilsTest.java | 8 ++ .../karate/core/parser/FeatureParserTest.java | 11 ++ .../core/parser/test-outline-name-js.feature | 119 ++++++++++++++++++ .../core/parser/test-outline-name.feature | 35 ++++++ 8 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name-js.feature diff --git a/README.md b/README.md index 6441d33fb..f5bd4af4f 100755 --- a/README.md +++ b/README.md @@ -4037,6 +4037,25 @@ 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 wrapping the name of the scenario in backticks. + +```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/StringUtils.java b/karate-core/src/main/java/com/intuit/karate/StringUtils.java index bce222653..b2426c9f2 100644 --- a/karate-core/src/main/java/com/intuit/karate/StringUtils.java +++ b/karate-core/src/main/java/com/intuit/karate/StringUtils.java @@ -26,11 +26,13 @@ import java.io.BufferedReader; import java.io.StringReader; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -178,9 +180,17 @@ public static StringUtils.Pair splitByFirstLineFeed(String text) { String right = ""; if (text != null) { int pos = text.indexOf('\n'); + // use backslash to continue in the same line + String pattern = "(.|\\n)+?[^\\\\\\s*](\\n|$)"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(text); + if (m.find( )) { + pos = m.end(); + } + if (pos != -1) { - left = text.substring(0, pos).trim(); - right = text.substring(pos).trim(); + left = trimBetweenNewLinesWithEscape(text.substring(0, pos)).trim(); + right = trimBetweenNewLinesWithEscape(text.substring(pos)).trim(); } else { left = text.trim(); } @@ -188,6 +198,26 @@ public static StringUtils.Pair splitByFirstLineFeed(String text) { return StringUtils.pair(left, right); } + public static String trimBetweenNewLinesWithEscape(String text) { + AtomicBoolean previousLineHasBackslash = new AtomicBoolean(false); + return Arrays.stream(text.split("\\n")).map(s -> { + if(s.matches(".*\\\\\\s*$")) { + // ends with a backslash + String trimmed = s.trim(); // in Java 11 this should be strip() instead of trim() + previousLineHasBackslash.set(true); + return '\\' == trimmed.charAt(trimmed.length() - 1) ? trimmed.substring(0, trimmed.length() - 1) : trimmed; + } else { + String returnStr = s; + if(previousLineHasBackslash.get()) { + returnStr = returnStr.trim(); + } + previousLineHasBackslash.set(false); + return returnStr; + } + + }).collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString(); + } + public static List toStringLines(String text) { return new BufferedReader(new StringReader(text)).lines().collect(Collectors.toList()); } diff --git a/karate-core/src/main/java/com/intuit/karate/core/ScenarioIterator.java b/karate-core/src/main/java/com/intuit/karate/core/ScenarioIterator.java index 3b037abd0..598dd07d6 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/ScenarioIterator.java +++ b/karate-core/src/main/java/com/intuit/karate/core/ScenarioIterator.java @@ -90,7 +90,7 @@ public boolean tryAdvance(Consumer action) { } if (currentScenario.isDynamic()) { if (background == null) { - background = new ScenarioRuntime(featureRuntime, currentScenario); + background = new ScenarioRuntime(featureRuntime, currentScenario, true); background.run(); if (background.result.isFailed()) { // karate-config.js || background failed currentScenario = null; 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 667b615a6..a1c478532 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,19 +23,22 @@ */ 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.FileLogAppender; + import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @@ -56,12 +59,21 @@ public class ScenarioRuntime implements Runnable { public final Map magicVariables; public final boolean selectedForExecution; public final boolean dryRun; + public final boolean isBackgroundRuntime; public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario) { - this(featureRuntime, scenario, null); + this(featureRuntime, scenario, null, false); + } + + public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario, boolean backgroundRuntime) { + this(featureRuntime, scenario, null, backgroundRuntime); } public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario, ScenarioRuntime background) { + this(featureRuntime, scenario, background, false); + } + + public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario, ScenarioRuntime background, boolean backgroundRuntime) { logger = new Logger(); this.featureRuntime = featureRuntime; this.caller = featureRuntime.caller; @@ -93,6 +105,7 @@ public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario, Scenari } dryRun = featureRuntime.suite.dryRun; selectedForExecution = isSelectedForExecution(featureRuntime, scenario, tags); + isBackgroundRuntime = backgroundRuntime; } public boolean isFailed() { @@ -349,6 +362,11 @@ public void beforeRun() { } ScenarioEngine.set(engine); engine.init(); + if(!this.isBackgroundRuntime) { + // don't evaluate names when running the background section + this.evaluateScenarioName(); + this.evaluateScenarioDescription(); + } result.setExecutorName(Thread.currentThread().getName()); result.setStartTime(System.currentTimeMillis() - featureRuntime.suite.startTime); if (!dryRun) { @@ -484,4 +502,23 @@ 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)); + if (wrappedByBackTick) { + String evaluatedScenarioName = this.engine.evalJs(scenarioName).getAsString(); + this.scenario.setName(evaluatedScenarioName); + } + } + + public void evaluateScenarioDescription() { + String scenarioDescription = this.scenario.getDescription(); + boolean wrappedByBackTick = scenarioDescription != null && scenarioDescription.length() > 1 && '`' == scenarioDescription.charAt(0) + && '`' == scenarioDescription.charAt((scenarioDescription.length() - 1)); + if (wrappedByBackTick) { + String evaluatedScenarioDescription = this.engine.evalJs(scenarioDescription).getAsString(); + this.scenario.setDescription(evaluatedScenarioDescription); + } + } + } diff --git a/karate-core/src/test/java/com/intuit/karate/StringUtilsTest.java b/karate-core/src/test/java/com/intuit/karate/StringUtilsTest.java index 744ac71e6..8757d7f72 100644 --- a/karate-core/src/test/java/com/intuit/karate/StringUtilsTest.java +++ b/karate-core/src/test/java/com/intuit/karate/StringUtilsTest.java @@ -106,6 +106,14 @@ void testSplitByFirstLineFeed() { StringUtils.splitByFirstLineFeed("foo")); assertEquals(new Pair("foo", "bar"), StringUtils.splitByFirstLineFeed("foo\nbar")); + assertEquals(new Pair("foo foo2", "bar"), + StringUtils.splitByFirstLineFeed("foo \\\nfoo2\nbar")); + assertEquals(new Pair("foo foo2", "bar test"), // leave the space, it's not an error in the unit test + StringUtils.splitByFirstLineFeed("foo \\\nfoo2\nbar\n test")); + assertEquals(new Pair("foo foo2", "bar test"), + StringUtils.splitByFirstLineFeed("foo \\\nfoo2\nbar \\\n test")); + assertEquals(new Pair("multi line left", "right side can also be multiline and note the concatenated multiline word"), + StringUtils.splitByFirstLineFeed("multi \\\nline \\\n left\nright side \\\n can also be multi\\\nline \\\n and note the concatenated multiline word")); } @Test 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 5305c60e9..179e6d72b 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..615137f80 --- /dev/null +++ b/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name-js.feature @@ -0,0 +1,119 @@ +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" + +# String interpolation allows you to use operators +Scenario: if name is not entirely wrapped in backticks... won't be evaluated `one plus one equals ${1 + 1}` + * match karate.info.scenarioName == "if name is not entirely wrapped in backticks... won't be evaluated `one plus one equals ${1 + 1}`" + +# 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" + +# and if you really really have a need... you can wrap your scenario name with backtick to have a multi-line name +# note that by default any content after the first new line that does not include a backslah will be set as the scenario description +Scenario: `math scenario: should return ${java.lang.Math.pow(2, 2)} \ + because 2 is the base and 2 is the exponent \ + and 2^2=${java.lang.Math.pow(2, 2)}` + and the next new line will be the description of your scenario... \ + which can also be multi-line + * def powResult = java.lang.Math.pow(2, 2) + * match karate.info.scenarioName == "math scenario: should return 4 because 2 is the base and 2 is the exponent and 2^2=4" + + +# if you don't add the backslash at the end of first line, the second line onwards will be the scenario description so in this case +# there's nothing to evaluate as there's no closing backslash +Scenario: `math scenario: should return ${java.lang.Math.pow(2, 2)} + because 2 is the base and 2 is the exponent + and 2^2=${java.lang.Math.pow(2, 2)}` + * def powResult = java.lang.Math.pow(2, 2) + * match karate.info.scenarioName == "`math scenario: should return ${java.lang.Math.pow(2, 2)}" \ 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..5cdfcac8b 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,21 @@ 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 | + +Scenario Outline: name is \ + and age is + * def name = '' + * match name == "#? _ == 'Bob' || _ == 'Nyan'" + * def title = karate.info.scenarioName + + Examples: + | js_data | \ No newline at end of file From 20e3f74bc49e906bb44a35d799400205ae322e66 Mon Sep 17 00:00:00 2001 From: Joel Ramos Date: Sat, 2 Jan 2021 12:32:07 -0500 Subject: [PATCH 2/5] Removing backslash (\) as option to add new line --- README.md | 3 +- .../java/com/intuit/karate/StringUtils.java | 34 ++----------------- .../intuit/karate/core/ScenarioIterator.java | 2 +- .../intuit/karate/core/ScenarioRuntime.java | 31 +++-------------- .../com/intuit/karate/StringUtilsTest.java | 8 ----- .../core/parser/test-outline-name-js.feature | 25 ++------------ .../core/parser/test-outline-name.feature | 9 ----- 7 files changed, 11 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index f5bd4af4f..97920aba4 100755 --- a/README.md +++ b/README.md @@ -4040,8 +4040,7 @@ For another example, see: [`examples.feature`](karate-demo/src/test/java/demo/ou If you're looking for more complex ways of naming your scenarios you can use JavaScript String interpolation by wrapping the name of the scenario in backticks. ```cucumber -Scenario Outline: `name is ${name.first} ${name.last} \ - and age is ${age}` +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 diff --git a/karate-core/src/main/java/com/intuit/karate/StringUtils.java b/karate-core/src/main/java/com/intuit/karate/StringUtils.java index b2426c9f2..bce222653 100644 --- a/karate-core/src/main/java/com/intuit/karate/StringUtils.java +++ b/karate-core/src/main/java/com/intuit/karate/StringUtils.java @@ -26,13 +26,11 @@ import java.io.BufferedReader; import java.io.StringReader; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -180,17 +178,9 @@ public static StringUtils.Pair splitByFirstLineFeed(String text) { String right = ""; if (text != null) { int pos = text.indexOf('\n'); - // use backslash to continue in the same line - String pattern = "(.|\\n)+?[^\\\\\\s*](\\n|$)"; - Pattern r = Pattern.compile(pattern); - Matcher m = r.matcher(text); - if (m.find( )) { - pos = m.end(); - } - if (pos != -1) { - left = trimBetweenNewLinesWithEscape(text.substring(0, pos)).trim(); - right = trimBetweenNewLinesWithEscape(text.substring(pos)).trim(); + left = text.substring(0, pos).trim(); + right = text.substring(pos).trim(); } else { left = text.trim(); } @@ -198,26 +188,6 @@ public static StringUtils.Pair splitByFirstLineFeed(String text) { return StringUtils.pair(left, right); } - public static String trimBetweenNewLinesWithEscape(String text) { - AtomicBoolean previousLineHasBackslash = new AtomicBoolean(false); - return Arrays.stream(text.split("\\n")).map(s -> { - if(s.matches(".*\\\\\\s*$")) { - // ends with a backslash - String trimmed = s.trim(); // in Java 11 this should be strip() instead of trim() - previousLineHasBackslash.set(true); - return '\\' == trimmed.charAt(trimmed.length() - 1) ? trimmed.substring(0, trimmed.length() - 1) : trimmed; - } else { - String returnStr = s; - if(previousLineHasBackslash.get()) { - returnStr = returnStr.trim(); - } - previousLineHasBackslash.set(false); - return returnStr; - } - - }).collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString(); - } - public static List toStringLines(String text) { return new BufferedReader(new StringReader(text)).lines().collect(Collectors.toList()); } diff --git a/karate-core/src/main/java/com/intuit/karate/core/ScenarioIterator.java b/karate-core/src/main/java/com/intuit/karate/core/ScenarioIterator.java index 598dd07d6..3b037abd0 100644 --- a/karate-core/src/main/java/com/intuit/karate/core/ScenarioIterator.java +++ b/karate-core/src/main/java/com/intuit/karate/core/ScenarioIterator.java @@ -90,7 +90,7 @@ public boolean tryAdvance(Consumer action) { } if (currentScenario.isDynamic()) { if (background == null) { - background = new ScenarioRuntime(featureRuntime, currentScenario, true); + background = new ScenarioRuntime(featureRuntime, currentScenario); background.run(); if (background.result.isFailed()) { // karate-config.js || background failed currentScenario = null; 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 a1c478532..89bbcf962 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 @@ -59,21 +59,12 @@ public class ScenarioRuntime implements Runnable { public final Map magicVariables; public final boolean selectedForExecution; public final boolean dryRun; - public final boolean isBackgroundRuntime; public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario) { - this(featureRuntime, scenario, null, false); - } - - public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario, boolean backgroundRuntime) { - this(featureRuntime, scenario, null, backgroundRuntime); + this(featureRuntime, scenario, null); } public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario, ScenarioRuntime background) { - this(featureRuntime, scenario, background, false); - } - - public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario, ScenarioRuntime background, boolean backgroundRuntime) { logger = new Logger(); this.featureRuntime = featureRuntime; this.caller = featureRuntime.caller; @@ -105,7 +96,6 @@ public ScenarioRuntime(FeatureRuntime featureRuntime, Scenario scenario, Scenari } dryRun = featureRuntime.suite.dryRun; selectedForExecution = isSelectedForExecution(featureRuntime, scenario, tags); - isBackgroundRuntime = backgroundRuntime; } public boolean isFailed() { @@ -362,11 +352,6 @@ public void beforeRun() { } ScenarioEngine.set(engine); engine.init(); - if(!this.isBackgroundRuntime) { - // don't evaluate names when running the background section - this.evaluateScenarioName(); - this.evaluateScenarioDescription(); - } result.setExecutorName(Thread.currentThread().getName()); result.setStartTime(System.currentTimeMillis() - featureRuntime.suite.startTime); if (!dryRun) { @@ -380,6 +365,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 @@ -511,14 +500,4 @@ public void evaluateScenarioName() { } } - public void evaluateScenarioDescription() { - String scenarioDescription = this.scenario.getDescription(); - boolean wrappedByBackTick = scenarioDescription != null && scenarioDescription.length() > 1 && '`' == scenarioDescription.charAt(0) - && '`' == scenarioDescription.charAt((scenarioDescription.length() - 1)); - if (wrappedByBackTick) { - String evaluatedScenarioDescription = this.engine.evalJs(scenarioDescription).getAsString(); - this.scenario.setDescription(evaluatedScenarioDescription); - } - } - } diff --git a/karate-core/src/test/java/com/intuit/karate/StringUtilsTest.java b/karate-core/src/test/java/com/intuit/karate/StringUtilsTest.java index 8757d7f72..744ac71e6 100644 --- a/karate-core/src/test/java/com/intuit/karate/StringUtilsTest.java +++ b/karate-core/src/test/java/com/intuit/karate/StringUtilsTest.java @@ -106,14 +106,6 @@ void testSplitByFirstLineFeed() { StringUtils.splitByFirstLineFeed("foo")); assertEquals(new Pair("foo", "bar"), StringUtils.splitByFirstLineFeed("foo\nbar")); - assertEquals(new Pair("foo foo2", "bar"), - StringUtils.splitByFirstLineFeed("foo \\\nfoo2\nbar")); - assertEquals(new Pair("foo foo2", "bar test"), // leave the space, it's not an error in the unit test - StringUtils.splitByFirstLineFeed("foo \\\nfoo2\nbar\n test")); - assertEquals(new Pair("foo foo2", "bar test"), - StringUtils.splitByFirstLineFeed("foo \\\nfoo2\nbar \\\n test")); - assertEquals(new Pair("multi line left", "right side can also be multiline and note the concatenated multiline word"), - StringUtils.splitByFirstLineFeed("multi \\\nline \\\n left\nright side \\\n can also be multi\\\nline \\\n and note the concatenated multiline word")); } @Test 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 index 615137f80..0e02abce3 100644 --- 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 @@ -68,8 +68,7 @@ Examples: | nested_js_data | -Scenario Outline: `name is ${name.first} ${name.last} \ - and age is ${age}` +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 @@ -84,7 +83,6 @@ Examples: Scenario: `one plus one equals ${1 + 1}` * match karate.info.scenarioName == "one plus one equals 2" -# String interpolation allows you to use operators Scenario: if name is not entirely wrapped in backticks... won't be evaluated `one plus one equals ${1 + 1}` * match karate.info.scenarioName == "if name is not entirely wrapped in backticks... won't be evaluated `one plus one equals ${1 + 1}`" @@ -97,23 +95,4 @@ Scenario: `scenario execution (env = ${karate.env})` 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" - -# and if you really really have a need... you can wrap your scenario name with backtick to have a multi-line name -# note that by default any content after the first new line that does not include a backslah will be set as the scenario description -Scenario: `math scenario: should return ${java.lang.Math.pow(2, 2)} \ - because 2 is the base and 2 is the exponent \ - and 2^2=${java.lang.Math.pow(2, 2)}` - and the next new line will be the description of your scenario... \ - which can also be multi-line - * def powResult = java.lang.Math.pow(2, 2) - * match karate.info.scenarioName == "math scenario: should return 4 because 2 is the base and 2 is the exponent and 2^2=4" - - -# if you don't add the backslash at the end of first line, the second line onwards will be the scenario description so in this case -# there's nothing to evaluate as there's no closing backslash -Scenario: `math scenario: should return ${java.lang.Math.pow(2, 2)} - because 2 is the base and 2 is the exponent - and 2^2=${java.lang.Math.pow(2, 2)}` - * def powResult = java.lang.Math.pow(2, 2) - * match karate.info.scenarioName == "`math scenario: should return ${java.lang.Math.pow(2, 2)}" \ No newline at end of file + * 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 5cdfcac8b..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 @@ -35,12 +35,3 @@ Scenario Outline: name is and age is Examples: | js_data | - -Scenario Outline: name is \ - and age is - * def name = '' - * match name == "#? _ == 'Bob' || _ == 'Nyan'" - * def title = karate.info.scenarioName - - Examples: - | js_data | \ No newline at end of file From f3bd1fe858dee396e5d57969c16e5f832ccb3602 Mon Sep 17 00:00:00 2001 From: Joel Ramos Date: Sat, 2 Jan 2021 12:37:25 -0500 Subject: [PATCH 3/5] Removing unnecessary imports --- .../src/main/java/com/intuit/karate/core/ScenarioRuntime.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 9130372fb..087c7add9 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 @@ -31,13 +31,12 @@ 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; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * From a101d7a293a5b1dd18753156b5490ffdd59ec734 Mon Sep 17 00:00:00 2001 From: Joel Ramos Date: Sun, 3 Jan 2021 14:31:38 -0500 Subject: [PATCH 4/5] Simplifying by removing the need to use backticks in scenario name --- .../java/com/intuit/karate/core/ScenarioEngine.java | 5 +++++ .../java/com/intuit/karate/core/ScenarioRuntime.java | 10 ++++++++-- .../karate/core/parser/test-outline-name-js.feature | 10 +++++----- 3 files changed, 18 insertions(+), 7 deletions(-) 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 087c7add9..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 @@ -482,8 +482,14 @@ public String toString() { public void evaluateScenarioName() { String scenarioName = this.scenario.getName(); boolean wrappedByBackTick = scenarioName != null && scenarioName.length() > 1 && '`' == scenarioName.charAt(0) && '`' == scenarioName.charAt((scenarioName.length() - 1)); - if (wrappedByBackTick) { - String evaluatedScenarioName = this.engine.evalJs(scenarioName).getAsString(); + 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/test-outline-name-js.feature b/karate-core/src/test/java/com/intuit/karate/core/parser/test-outline-name-js.feature index 0e02abce3..9720fa411 100644 --- 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 @@ -80,19 +80,19 @@ Examples: # String interpolation allows you to use operators -Scenario: `one plus one equals ${1 + 1}` +Scenario: one plus one equals ${1 + 1} * match karate.info.scenarioName == "one plus one equals 2" -Scenario: if name is not entirely wrapped in backticks... won't be evaluated `one plus one equals ${1 + 1}` - * match karate.info.scenarioName == "if name is not entirely wrapped in backticks... won't be evaluated `one plus one equals ${1 + 1}`" +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})` +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)}` +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 From 725e9f03d96efe1914e621dc01593a8a344a7cfd Mon Sep 17 00:00:00 2001 From: Joel Ramos Date: Sun, 3 Jan 2021 14:34:13 -0500 Subject: [PATCH 5/5] Updating documentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 97920aba4..d462e9340 100755 --- a/README.md +++ b/README.md @@ -4037,10 +4037,10 @@ 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 wrapping the name of the scenario in backticks. +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}` +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