Karate Automation Test Framework - need access to list of features included in run from karate-config.js - testing

From karate-config.js I need to know what .feature files are included in the run. Is there an easy way to access that?
As a work around, have discovered that if I re-write Karate's Runner.parallel function, I can pass the feature names in an argument:
...
Map<String, Object> args = new HashMap<String,Object>();
...
String featureList = "";
for (int i = 0; i < count; i++) {
Resource resource = resources.get(i);
Feature feature = FeatureParser.parse(resource);
featureList = featureList + feature.getName();
}
args.put("featureList", featureList);
...
CallContext callContext = CallContext.forAsync(feature, options.hooks, options.hookFactory, args, false);
However, I dont know how to access the featureList argument from karate-config.js.
Is there an easy way to access the list of feature for a test run from karate-config.js?

This is not supported today. This is unlikely to be supported unless you contribute code because no one has ever requested this.
Do note that if you are anyway creating a list of features before you start the test, you can pass that to karate-config.js by means of a Java singleton.

Related

Creating common test data for multiple feature files

My requirement is as follows:
I have a couple of .feature files. I want to create test data that would be common to all of these feature files. Once the test data is created the scenarios will be run from the feature files.
I also want some info back after the test data is created. eg., ids of the data that i have created. So i can use this info to call the api's, add in payloads in my scenarios.
I think we could do this by:
1. Creating a junit java file. I define a static method with #BeforeClass in there and use Karate's runner() to run my create-test-data.feature file (I can use Karate to hit application api to create some data). I define a property in my java class of type Object and set it with the result of Runner.runFeature().
Then I create a separate feature file test-data-details.feature. I define my Java Interop code here. eg.,
def test_data =
"""
var JavaOutput = Java.type('com.mycompany.JavaFile');
var testData = JavaOutput.propertyName;
"""
Now that I have my test data object in my test-data-details.feature file. I call this .feature file (callonce) in the Background section of my feature files that have test scenarios in. So I can retries the test data details like id, name. etc that I can then use in the api request paths and payloads.
I am not sure if the above design is the correct way to go ahead. I tried but getting some issues in my Java file where getClass() below complains that it cannot be used in static method.
#RunWith(Karate.class)
public class AccountRunner {
public static Object job = null;
#BeforeClass
public static void create_job(){
Map<String, Object> result = Runner.runFeature(getClass(), "test-data.feature", null, true);
job = result.get("job");
}
}
Now all of the above can be totally wrong. Need help on how to tackle this scenario in Karate.
Thanks
From your question I understand you have a common test data feature file, which you want to run before all the test and hold that response in a variable that can be used in all of the test features.
You can also achieve this in karate-config.js using karate.callSingle()
In your karate-config.js
config["testdata"] = karate.callSingle("test-data.feature")
Your test-data.feature will be executed once before all the tests and store the response in testdata you can use this variable directly in your feature.
So i have implemented the following design:
I have created two methods one with BeforeClass and other with AfterClass annotation in my TestRunner.java file. In these methods I am able to call the specific data creation and clean-up feature files and pass args as Json object.
#RunWith(Karate.class)
#KarateOptions(tags = {"~#ignore"})
public class AccountRunner {
public static Map<String, Object> result = null;
#BeforeClass
public static void create_job() throws IOException, ParseException {
Class clazz = AccountRunner.class;
URL file_loc = clazz.getResource("create-test-data-1.json");
File file = new File(file_loc.getFile());
JSONParser parser = new JSONParser();
Object obj = parser.parse(new FileReader(file));
JSONObject jsonObject = (JSONObject) obj;
Map<String, Object> args = new HashMap();
args.put("payload", jsonObject);
result = Runner.runFeature(CommonFeatures.class, "create-data.feature", args, true);
}
#AfterClass
public static void delete_investigation() {
Map<String, Object> args = new HashMap();
args.put("payload", result);
Runner.runFeature(CommonFeatures.class, "delete-job.feature", args, true);
}
}
To run these tests via command line using "mvn test" command, i have done following changes in pom.xml.
`<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<includes>
<include>**/*Runner.java</include>
</includes>
</configuration>
</plugin>`
With this solution I able to run my tests in IDE by executing the runner directly or by command line. However, I have not found a way to run all of my tests by following the karate suggested approach where I have a *Test.java file at test suite level and I use default maven configuration with "mvn test". The features fails to run as the .feature file is called before the Runner file is executed which has method to create test data for tests.
Maybe someone can suggest what else, I could do to use Karate approach of running *Test.java file instead of each *Runner.java file.

Reading parameters from TestNG file

I successfully implemented several tests within TestNG framework, where parameters are being read from xml file.
Here is the example block that is executed as first:
#Parameters({ "country" })
#BeforeSuite(alwaysRun = true)
public void prepareRequest(String country, ITestContext cnt) {
LoginInfoRequestParm loginParms = new LoginInfoRequestParm(country);
Headers reqHeaders = new Headers();
reqHeaders.setHeaders(loginParms);
}
The problem/question is, why does it work only if the ITestContext is specified? Once it is removed, the overall suite is broken and it will never come to the specified method prepareRequest(). I was not able to debug it, because I cannt set breakpoint before the method to be able to see what is going on in TestNG itself.
Thank you for your explanation.
To get out of this situation, try something like this
String myPar = context.getCurrentXmlTest().getParameter("country");
if (myPar == null) {
myPar = "INDIA";
}
now myPar can be used, only thing here is if you run class for debug or any other purpose then we are using INDIA. if we run from testng.xml file then it will take values from that file.

Plugin: How to insert new method in existing PHP class?

I'm trying to create a IntelliJ plugin (mostly for learning purposes). My aim is that by pressing a keyboard shortcut the plugin will generate a corresponding PHP unit test method stub in the test file.
So let's say Db.php is open, the upon pressing Ctrl+Shift+U the plugin will create a unit test stub in DbTest.php.
So far I've figured out how to get the method name at cursor and how to locate the corresponding Unit test file (i.e. Db => DbTest) as PsiFile.
PsiFile[] search = FilenameIndex.getFilesByName(project, testFileName, scope); //scope is the test directory
PsiFile testFile = search[0];
What I cannot figure out is how to insert the generated new method stub this in testFile and then save the changes?
P.S. I see there exists a createMethodFromText function but how do I get the PsiClass from PsiFile? Also how do I save the changes?
There're just a few simple steps.
Find PhpClass you want to insert a new method in. As you already have PsiFile you can either traverse a tree manually or use PhpElementVisitor.
1.1. To travers a tree manually you can use PsiTreeUtil#findChildOfType method. In your case you'll need to find GroupStatement first, then the class you need.
1.2. Invoke PsiElement#accept method (PsiFile is an instance of PsiElement) provided with PhpElementVisitor with overridden #visitPhpGroupStatement and #visitPhpClass methods.
Use PhpPsiElementFactory#createMethod to create the new method from text. Note that this class isn't a part of the public API, so theoretically it can be easily changed/moved/removed/whatever in the future.
Use PsiElement#add (PhpClass is also an instance of PsiElement) to insert the method into the class.
That's all. You don't need to explicitly save the changes.
Here is what worked for me in the end. Thanks everyone for the help
for (int i = 0; i < found.getTextLength(); i++) {
PsiElement ele = found.findElementAt(i);
PhpClass phpClass = PsiTreeUtil.getParentOfType(ele, PhpClass.class);
if (phpClass != null) {
Method methodExists = findMethod(phpClass, methodName);
if (methodExists == null) {
new WriteCommandAction.Simple(phpClass.getProject(), phpClass.getContainingFile()) {
#Override
protected void run() throws Throwable {
PsiElement brace = phpClass.getLastChild();
if (brace != null) {
Method method = PhpPsiElementFactory.createMethod(phpClass.getProject(), "public function " + methodName + "() {\n\n}");
CodeStyleManager styleManager = CodeStyleManager.getInstance(getProject());
styleManager.reformat(method);
PsiElement newMethod = phpClass.addBefore(method, brace);
PsiNavigateUtil.navigate(newMethod);
}
}
}.execute();
} else {
PsiNavigateUtil.navigate(methodExists);
}
break;
}
}

Ability to set the context of the expression

Is there a way to set the context of the expression in Dynamic Expresso library, so that we can do something like the following:
interpreter.Eval("FirstName", new Parameter("person", new { FirstName="Homer", LastName="Simpson"}));
rather than
interpreter.Eval("person.FirstName", new Parameter("person", new { FirstName="Homer", LastName="Simpson"}));
Maybe we could have a another option that would say that the first parameter is to be used as the context for the expression.
I guess there could also be another version of Parse and Eval methods that simply takes the expression text and a simple object value that will serve as the expression context.
Other than that and the lack of support for dynamic types, I am really liking this library. I had worked on something similar, but had not added support for extension methods and generic method calls.
Thanks for the great library,
Neal
There isn't a built-in solution but you can simulate it in many ways:
Option 1: Inject an expression
var workingContext = new { FirstName = "homer" };
var workingContextExpression = Expression.Constant(workingContext);
var firstNameExpression = Expression.Property(workingContextExpression, "FirstName");
var interpreter = new Interpreter();
interpreter.SetExpression("FirstName", firstNameExpression);
Assert.AreEqual(workingContext.FirstName, interpreter.Eval("FirstName"));
Basically I inject an expression using SetExpression method. The injected expression is the property that you want to be available.
Option 2: Use this/me/it variable
You can inject a variable that will contain your working object. I usually call it this (or me or it depending on the application).
var workingContext = new { FirstName = "homer" };
var interpreter = new Interpreter();
interpreter.SetVariable("this", workingContext);
Assert.AreEqual(workingContext.FirstName, interpreter.Eval("this.FirstName"));
Option 3: A combination of the previous solutions
var workingContext = new { FirstName = "homer" };
var interpreter = new Interpreter();
interpreter.SetVariable("this", workingContext);
var firstNameExpression = interpreter.Parse("this.FirstName").LambdaExpression.Body;
interpreter.SetExpression("FirstName", firstNameExpression);
Assert.AreEqual(workingContext.FirstName, interpreter.Eval("FirstName"));
Equal to the first solution but I generate the expression using the parser itself.
Consider that all solutions assume that you must have an Interpreter instance for each context.
Disclaimer: I'm the author of Dynamic Expresso library.
Starting with DynamicExpresso v2.13.0, it's possible to define a variable named "this", that will be used for implicit resolution:
var target = new Interpreter();
target.SetVariable("this", new { FirstName="Homer", LastName="Simpson"});
// 'this' variable is used implicitly
Assert.AreEqual("Homer", target.Eval("FirstName"));
// 'this' variable can also be used explicitly
Assert.AreEqual("Homer", target.Eval("this.FirstName"));

Scoping in embedded groovy scripts

In my app, I use Groovy as a scripting language. To make things easier for my customers, I have a global scope where I define helper classes and constants.
Currently, I need to run the script (which builds the global scope) every time a user script is executed:
context = setupGroovy();
runScript( context, "global.groovy" ); // Can I avoid doing this step every time?
runScript( context, "user.groovy" );
Is there a way to setup this global scope once and just tell the embedded script interpreter: "Look here if you can't find a variable"? That way, I could run the global script once.
Note: Security is not an issue here but if you know a way to make sure the user can't modify the global scope, that's an additional plus.
Shamelessly stolen from groovy.codehaus :
The most complete solution for people
who want to embed groovy scripts into
their servers and have them reloaded
on modification is the
GroovyScriptEngine. You initialize the
GroovyScriptEngine with a set of
CLASSPATH like roots that can be URLs
or directory names. You can then
execute any Groovy script within those
roots. The GSE will also track
dependencies between scripts so that
if any dependent script is modified
the whole tree will be recompiled and
reloaded.
Additionally, each time you run a
script you can pass in a Binding that
contains properties that the script
can access. Any properties set in the
script will also be available in that
binding after the script has run. Here
is a simple example:
/my/groovy/script/path/hello.groovy:
output = "Hello, ${input}!"
import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
String[] roots = new String[] { "/my/groovy/script/path" };
GroovyScriptEngine gse = new GroovyScriptEngine(roots);
Binding binding = new Binding();
binding.setVariable("input", "world");
gse.run("hello.groovy", binding);
System.out.println(binding.getVariable("output"));
This will print "Hello, world!".
Found: here
Would something like that work for you?
A simple solution is to use the code from groovy.lang.GroovyShell: You can precompile the script like so:
GroovyCodeSource gcs = AccessController.doPrivileged( new PrivilegedAction<GroovyCodeSource>() {
public GroovyCodeSource run() {
return new GroovyCodeSource( scriptCode, fileName, GroovyShell.DEFAULT_CODE_BASE );
}
} );
GroovyClassLoader loader = AccessController.doPrivileged( new PrivilegedAction<GroovyClassLoader>() {
public GroovyClassLoader run() {
return new GroovyClassLoader( parentLoader, CompilerConfiguration.DEFAULT );
}
} );
Class<?> scriptClass = loader.parseClass( gcs, false );
That's was the expensive part. Now use InvokeHelper to bind the compiled code to a context (with global variables) and run it:
Binding context = new javax.script.Binding();
Script script = InvokerHelper.createScript(scriptClass, context);
script.run();