-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding support for JavaScript String interpolation in Scenario Names a… #1416
Changes from 1 commit
3d68425
20e3f74
33d49df
f3bd1fe
0453664
a101d7a
725e9f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<String, Object> 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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'd like to keep it simple :| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what I meant when I said we need to care about multiple lines is I believe the first line always goes into the for those who want extra / dynamic docs - the new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when I thought about 2 lines I was thinking about wrapping lines - different developers might have different views in terms of the size of a line / line wrap. I agree that anything past 2 lines is probably bad practice (and reports won't look pretty at all) but what do you think about still supporting any lines and leaving the "good practice" of keeping the name short and simple to the test scripters? I'm just thinking that might need to throw an exception if we want to limit to 2 lines and one day someone will want "just" the 3 lines :) The description was already in the code (saw it in the replace). Are you planning on removing it all together? I agree with not evaluating as the scenario outline should always do the same validations so a static description should suffice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hear ya. I'd be okay if this wasn't cucumber, the syntax is biased towards "one line at a time", the only way to span multiple lines is to use the triple-quotes so I'd really like to stick to one-line for the name. I also made this commit to verify what I was saying, that the parser already gives you the name and description separate: 77310ac not planning to remove the description but we have now created a JSON for representing "an executed scenario" - see the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so yes, good point - if the description is already being included in the replace - we can decide to not do it anymore. and one reason is we have a new way of adding fancy descriptions in-line. the name is important to stand out in reports There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. got it - will remove the usage of \ altogher There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will remove the option of using string interpolation in the description but for now will leave the replace on the description - can probably be marked as deprecated or something but might be important to keep it for backwards compability |
||
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); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = '<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" | ||
|
||
# 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)}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need the extra boolean won't it be a property of the
ScenarioRuntime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When running Scenario Outlines the Background runs wrapped in a ScenarioRuntime - while when running a scenario the background section is just included in the steps. That background ScenarioRuntime doesn't have a scenario associated with thus I had a nullpointer.
I think there was a way to figure out (like null scenario maybe or something) that I can use instead of the boolean to avoid evaluating the scenario name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try
scenario.isDynamic()
you can see it used elsewhere in the same file