Calling a feature and passing in javascript variables - karate

Been trying to figure this out for a while, please could someone help.
I have a set of 5 lines which I'd like to make reusable.
The lines do a "check event XXX has fired".
The lines make use of the "karate" variable and also the "json" command.
They're of the form:
* def message = myUtils.grabEvent(karate, myMessageListener)
* json event = message.text
* match event contains { ... some json in here ... }
* json eventPayload = event.payload
* match event contains { ... some payload json in here ... }
How do I go about making this reusable?
I have tried:
(A) Putting it all into a Javascript function
This failed because I don't know how to replicate the "json" command in Javascript
(B) Putting it all into a .feature file and calling that
This failed because I don't know how to pass the "karate" and "myMessageListener" variables into parameters of the .feature file.
Is it possible to put this into a reusable code block, please?
TIA

Yes I would recommend making this a reusable feature. Refer the documentation here: https://github.com/intuit/karate#calling-other-feature-files
And passing parameters is simple it would look like:
* def result = call read('reusable.feature')
Because by default, the "called" feature will "inherit" the variables of the calling feature.

Related

how to read a test data file ( a json file) in a feature file only once

Karate has callonce that will call a function or feature only once for all scenerios in a feaure file? Is there a similar feature for reading a json file only once in a feature file before executing all scenarios. Can this be achieved by passing a function to karate.callonce() and that function will then just use read function to read the json file. Kindly answer how can I do this correctly?
I do not want to use another feature file for this. Should be able to pass a function name to the callonce.
I tried karate.callSingle and pass read function to read the json file.
Personally I think reading a JSON file from the file-system is so cheap that you are un-necessary worrying about this.
The only way that I know of is like this:
Feature:
Background:
* def dataFn = function(){ return read('data.json') }
* def data = callonce dataFn
Scenario: one
* print data
Scenario: two
* print data
But you are quite likely to complain here that we are initializing the function dataFn for every Scenario ;) In that case, you may need to look for another framework.
And I personally think calling a re-usable feature (for data set-up) is fine. Programming languages do this kind of re-use all the time.
EDIT: well, I just remembered that this would work:
* def data = callonce read 'data.json'
Explained here: https://github.com/karatelabs/karate#call-vs-read

Ability to use environmental variables in Feature and Scenario Names

How to append Configuration variable in Feature name or in Scenario name. For Instance need to provide Info in reports based on environment run.
I saw there is an option available to add the Examples variable in Scenario outline name. on a similar note, do we have option to append Environment variable in Feature name?
Yes, in 1.0 onwards - if a variable exists in scope, it will be substituted in the Scenario name using the JS string interpolation syntax.
For example if your karate-config.js is like this:
function fn() {
return { test: 'foo' };
}
It means that the variable test will be available when the Scenario is processed. If not, note that the test will fail.
So if your feature is like this:
Feature:
Scenario: ${test}
* print test
You will see this in the report:
So it is up to you how you set up variables in the configuration init.

Can we append URL parameters to an endpoint based on values in a test case?

Is it possible to send input parameters to the chatbot from test cases, both with and without user input? What I have in mind is that I should be able to do this in the test cases:
Test case 1
#me
Hello
INPUT_PARAMETER sttConfidence : 0.58
INPUT_PARAMETER callerCountry : GB
#bot
Hi human! I see that you sent some input parameters. Thank you!
...
...
The input parameters need to be appended to the endpoint, so the URL would look like this:
https://MyChatBotsEndpoint.com/?userinput={{msg.messageText}}&sttConfidence=0.58& callerCountry=GB
The values that we send need to be of type string.
Is this possible to achieve in Botium? And if yes, are there any native tools in Botium that can achieve this, or do we need to develop our own function?
Edit:
This is what happens when I added the piece of code:
Example of how input parameter merges with input message
Ideally I would like it to look like this:
This is what it looks like if I manually send &countryCaller=GB to our endpoint
There is nothing like that included, but with the right combination of a logic hook and the HTTP/JSON request hook it is possible.
The UPDATE_CUSTOM logic hook will copy the parameters from the convo file to the internal Botium message object:
Test case 1
#me
Hello
UPDATE_CUSTOM QUERY_PARAM|sttConfidence|0.58
UPDATE_CUSTOM QUERY_PARAM|callerCountry|GB
#bot
Hi human! I see that you sent some input parameters. Thank you!
...
The SIMPLEREST_REQUEST_HOOK capability will then use the parameters to adapt the request url accordingly with a little Javascript code:
...
"SIMPLEREST_REQUEST_HOOK": "if (msg.QUERY_PARAM) requestOptions.uri = requestOptions.uri + '&' + Object.keys(msg.QUERY_PARAM).map(key => key + '=' + msg.QUERY_PARAM[key]).join('&')",
...
Alternative Approach
If you don't like the SIMPLEREST_REQUEST_HOOK Javascript code, you could also use Mustache templates to add the query parameters to the URL:
...
"SIMPLEREST_URL": "https://MyChatBotsEndpoint.com/?userinput={{msg.messageText}}&sttConfidence={{msg.QUERY_PARAM.sttConfidence}}&callerCountry={{msg.QUERY_PARAM.callerCountry}}"
...
(you will have empty parameters, but you can tune this mustache template as you want to exclude empty parameters).

Is it possible to add tags or have multiple BeforeTestRun hooks in Specflow

So I currently have an automation pack that I have created using Selenium/Specflow.
I wanted to know whether it is possible to have multiple BeforeTestRun hooks?
I've already tried: [BeforeTestRun("example1")] but I receive an error stating BeforeTestRunAttribute does not contain a constructor that takes 1 arguments
I tried the following but that also failed:
[BeforeTestRun]
[Scope(Tag = "example1")]
And referenced the above in the .feature file like this:
#example1
Scenario: This is an example
Given...
When...
Then...
Is there a way to implement this correctly such that in one .feature file I can have two scenarios that can use different [BeforeTestRun]?
If you cannot use [BeforeScenario] like suggested, you can try to manually check for tags using if statements. To get the current tags and compare them to the ones you need, try this:
var tags = ScenarioContext.ScenarioInfo.Tags;
if (tags.Any(x => x.Equals("MyTag")))
{
DoWork();
}
More info here: https://stackoverflow.com/a/42417623/9742876

How can I access query string parameters for requests I've manually dispatched in Laravel 4?

I'm writing a simple API, and building a simple web application on top of this API.
Because I want to "consume my own API" directly, I first Googled and found this answer on StackOverflow which answers my initial question perfectly: Consuming my own Laravel API
Now, this works great, I'm able to access my API by doing something like:
$request = Request::create('/api/cars/'.$id, 'GET');
$instance = json_decode(Route::dispatch($request)->getContent());
This is great! But, my API also allows you to add an optional fields parameter to the GET query string to specify specific attributes that should be returned, such as this:
http://cars.com/api/cars/1?fields=id,color
Now the way I actually handle this in the API is something along the lines of this:
public function show(Car $car)
{
if(Input::has('fields'))
{
//Here I do some logic and basically return only fields requested
....
...
}
I would assume that I could do something similar as I did with the query string parameter-less approach before, something like this:
$request = Request::create('/api/cars/' . $id . '?fields=id,color', 'GET');
$instance = json_decode(Route::dispatch($request)->getContent());
BUT, it doesn't seem so. Long story short, after stepping through the code it seems that the Request object is correctly created (and it correctly pulls out the fields parameter and assigns id,color to it), and the Route seems to be dispatched OK, but within my API controller itself I do not know how to access the field parameter. Using Input::get('fields') (which is what I use for "normal" requests) returns nothing, and I'm fairly certain that's because the static Input is referencing or scoping to the initial request the came in, NOT the new request I dispatched "manually" from within the app itself.
So, my question is really how should I be doing this? Am I doing something wrong? Ideally I'd like to avoid doing anything ugly or special in my API controller, I'd like to be able to use Input::get for the internally dispatched requests and not have to make a second check , etc.
You are correct in that using Input is actually referencing the current request and not your newly created request. Your input will be available on the request instance itself that you instantiate with Request::create().
If you were using (as you should be) Illuminate\Http\Request to instantiate your request then you can use $request->input('key') or $request->query('key') to get parameters from the query string.
Now, the problem here is that you might not have your Illuminate\Http\Request instance available to you in the route. A solution here (so that you can continue using the Input facade) is to physically replace the input on the current request, then switch it back.
// Store the original input of the request and then replace the input with your request instances input.
$originalInput = Request::input();
Request::replace($request->input());
// Dispatch your request instance with the router.
$response = Route::dispatch($request);
// Replace the input again with the original request input.
Request::replace($originalInput);
This should work (in theory) and you should still be able to use your original request input before and after your internal API request is made.
I was also just facing this issue and thanks to Jason's great answers I was able to make it work.
Just wanted to add that I found out that the Route also needs to be replaced. Otherwise Route::currentRouteName() will return the dispatched route later in the script.
More details to this can be found on my blog post.
I also did some tests for the stacking issue and called internal API methods repeatedly from within each other with this approach. It worked out just fine! All requests and routes have been set correctly.
If you want to invoke an internal API and pass parameters via an array (instead of query string), you can do like this:
$request = Request::create("/api/cars", "GET", array(
"id" => $id,
"fields" => array("id","color")
));
$originalInput = Request::input();//backup original input
Request::replace($request->input());
$car = json_decode(Route::dispatch($request)->getContent());//invoke API
Request::replace($originalInput);//restore orginal input
Ref: Laravel : calling your own API