Issues observed in " karate-junit5 " library when migrated from version 1.2.0 to 1.3.1 - karate

Recently I upgraded the version of karate-junit5 library from 1.2.0 to 1.3.1.
I have a common feature file which contains some common functions used all other scenarios like below sample.
common.feature
#Common
Scenario: Common scenario
* def someFunction =
"""
function(){
karate.log('In some function..');
var textArray = driver.locateAll("//*[#role='button']").map( x => x.text)
karate.log('Button text is: ', textArray);
}
"""
* def getDateInFormat =
"""
function(date){
var curr_date = date.getDate();
if(curr_date < 10)
curr_date = '0' + curr_date;
var curr_month = date.getMonth() + 1;
if(curr_month < 10)
curr_month = '0' + curr_month;
var curr_year = date.getFullYear();
var mod_date = curr_date + '/' + curr_month + '/' + curr_year;
return mod_date;
}
"""
* def sortFunction =
"""
function(sortArr, sortType){
if(sortType == 'asc')
sortArr.sort();
else
sortArr.reverse();
return sortArr;
}
"""
I call above common scenarios from other feature files whenever required. A sample calling feature file would be as below.
Test.feature
Feature: Test feature
Background:
* callonce read('common.feature#Common')
Scenario: Test 1
* print "Today's date is: ", getDateInFormat(new Date())
Scenario: Test 2
* print "Sorted data by A to Z is: ", sortFunction(['Dog', 'Cat', 'Horse'], 'asc')
Scenario: Test 3
* configure driver = { type: 'chrome', addOptions: ['--ignore-certificate-errors', '--incognito', '--disable-gpu', '--window-size=1920,1080', '--no-sandbox'], headless: false }
* configure retry = { count: 10, interval: 2000 }
* driver 'https://www.google.com/'
* someFunction()
All the scenarios 1,2 and 3 are executed properly for 1.2.0 version but throw below errors for when library version is upgraded to 1.3.1 :
For Scenario 1:
" org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (getDate) on java.util.LinkedHashMap#6a969fb8 failed due to: Unknown identifier: getDate "
For Scenario 2:
" org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (sort) on java.util.ArrayList#ff6077 failed due to: Arity error - expected: 1 actual: 0 "
For Scenario 3:
" org.graalvm.polyglot.PolyglotException: ReferenceError: "driver" is not defined "
Can somebody explain what has changed in karate-junit5 library version 1.3.1 from 1.2.0 ?
For Scenario 2, sortArr.reverse() is working properly even in 1.3.1 karate version. Above error is observed for sortArr.sort().
For Scenario 3, if I change the someFunction() to accept driver , like someFunction(driver), then it is executed properly on 1.3.1 version as well.

Related

Karate js file: org.graalvm.polyglot.PolyglotException: Cannot read the array length because "src" is null

I'm using this util feature:
Feature: Set headers
Scenario:
* def authToken = call read('classpath:basic-auth.js') api.credentials
With the javascript basic-auth.js:
function auth(credentials){
var temp = credentials.username + ':' + credentials.password;
var Base64 = Java.type('java.util.Base64');
var encoded = Base64.getEncoder().encodeToString(temp.bytes);
return 'Basic ' + encoded;
}
That worked fine until I update the Karate version to 1.2.0.RC4 and Java version to 17. Now I'm getting the following exception while running the tests:
org.graalvm.polyglot.PolyglotException: Cannot read the array length because "src" is null
- java.base/java.util.Base64$Encoder.encode(Base64.java:291)
- java.base/java.util.Base64$Encoder.encodeToString(Base64.java:345)
- <js>.auth(Unnamed:4)
Thanks in advance!
I fix it replacing:
var encoded = Base64.getEncoder().encodeToString(temp.bytes);
with:
var encoded = Base64.getEncoder().encodeToString(temp.getBytes());
Maybe someone can help me explaining why this works now.

Karate | String Split for CLI output [duplicate]

I'm unsure about how I can split the response string from an already created feature to obtain the response header "Location" value.
What I've tried
1)
Feature: Create Tariff
Background:
* def result = call read('../../get-user-token.feature')
* def serviceId = call read('create-service.feature')
Scenario: Create Tariff
Given url 'https://app-dev.topbox.pro/tariff-svc/api/v1/tariffs'
And header Authorization = result.response.token_type + " " + result.response.access_token
And request
"""
{
serviceTypeId: '#(serviceId.responseHeaders['Location'].split('/')[1])',
owner: 1,
type: 0,
pencePerMile: '69.69',
minMileage: '1.00',
minCost: 5,
zoneFrom: '',
zoneTo: '',
fixedCost: 0
}
"""
When method POST
Then status 201
Which resulted in...
IntegrationTests.TestSetup.create-tariff: create-tariff.feature:10 -
net.minidev.json.parser.ParseException: Unexpected token L at position
46.
2)
Feature: Create Tariff
Background:
* def result = call read('../../get-user-token.feature')
* def serviceId = call read('create-service.feature').responseHeaders['Location'].split('/')[1]
Scenario: Create Tariff
Given url 'https://app-dev.topbox.pro/tariff-svc/api/v1/tariffs'
And header Authorization = result.response.token_type + " " + result.response.access_token
And request
"""
{
serviceTypeId: '#(serviceId)',
owner: 1,
type: 0,
pencePerMile: '69.69',
minMileage: '1.00',
minCost: 5,
zoneFrom: '',
zoneTo: '',
fixedCost: 0
}
"""
When method POST
Then status 201
Which resulted in...
failed features: IntegrationTests.TestSetup.create-tariff: -unknown-:5
- javascript evaluation failed: read('create-service.feature').responseHeaders['Location'].split('/')1,
TypeError: Cannot read property "Location" from undefined in at
line number 1
NOTE
The specified feature "create-service.feature" does indeed work when isolated and does produce the response header, as shown below
Use lastIndexOf instead of split:
* def location = responseHeaders['Location'][0]
* def serviceId = location.substring(location.lastIndexOf('/') + 1)
You need to use a Javascript function : https://github.com/intuit/karate#javascript-functions
* def greeter = function(name){ return 'hello ' + name }
* assert greeter('Bob') == 'hello Bob'
EDIT:
* def service = { key : "someinfo/myServiceId"}
* def func = function(service){return service.key.split('/')[1]}
* def serviceId = func(service)
* match serviceId == "myServiceId"
I think the first error is due to single quotes inside your expression, try escaping that
like,
And request
"""
{
serviceTypeId: '#(serviceId.responseHeaders.Location[0].split(\'/\')[1])',
owner: 1,
type: 0,
pencePerMile: '69.69',
minMileage: '1.00',
minCost: 5,
zoneFrom: '',
zoneTo: '',
fixedCost: 0
}
"""
Edit: Just now noted each value in responseHeader has a list type value so access it like Location[0]
And your second Approach should be something like this,
* def serviceId = call read('create-service.feature').responseHeaders.Location[0].split('/')[1]
I just face the same issue (.split is not a function), and in my case, I need to convert the data to string first, before using split function.
Here is the custom code from adrien answer:
* def service = { key : "someinfo/myServiceId"}
* def func = function(service){return service.key.toString().split('/')[1]}

How to access karate ui automation driver details like sessionid in afterScenario? [duplicate]

This question already has an answer here:
KARATE integration with SauceLabs
(1 answer)
Closed 1 year ago.
I am reading the json file which contains the array of configurations of browserstack browsers or devices on which I want run my UI automation tests and defining it in karate-config.js as a global variable, for ex: config.envrironments. That variable I am calling it in Examples Table as envrironments and using it in dynamic scenario outline to initialize driver for different browser/device sessions as envrironments[__num]. The execution is working fine as expected.
After execution, I want to call the browserstack api which updates the test scenario status as passed or failed with a reason in their dashboard or in the browserstack report integrated in jenkins for clear understanding. The api requires driver sessionId as api path param, status(passed or failed) and reason(if failed) as body.
I am able to get the status and reason from karate.info.errorMessage after configuring afterScenario for the feature. But my problem is to get the browserstack sessionId.
To get the driver.sessionId in afterScenario, i'm getting error as "driver" is not defined. I guess driver object is getting killed in between each execution of scenario outline and the afterScenario.
Is there any way to keep the driver alive until the afterScenario is completed?
or
any other alternatives like implementing ExecutionHook class or any other class through which we can get the driver details?
Please help. Thanks in advance.
UPDATE:
I'm using Java 8 and karate version 0.9.6
Below is my code:
Test Feature -
Feature: Test Feature
Background:
* def scenarioStatus =
"""
{
"status": "passed",
"reason": ""
}
"""
* configure afterScenario =
"""
function(){
if(karate.info.errorMessage){
scenarioStatus.status = 'failed';
scenarioStatus.reason = karate.info.errorMessage;
}
karate.log('session id : ' + karate.get('sessionId'));
// karate.call('browserstack.feature', scenarioStatus);
}
"""
#dummyMobileBrowser
Scenario Outline: Dummy Scenario
* def browserTestName = karate.info.scenarioName + ' - '
* call read('driver.feature#initializeDriver')
* def sessionId = driver.sessionId
* call read('loginPage.feature#login')
# I'm defining array of device or browser configs in karate-config.js
Examples:
| deviceConfigs |
driver.feature
Feature: Driver Related Feature
Background:
* def jsUtils = read('classpath:jsUtils.js')
* def getDriverConfig =
"""
function(){
if(browserstack == "yes"){
var configResult = karate.call('driver.feature#createBrowserStackConfig');
var browserstackConfig = configResult.browserstackConfig;
return browserstackConfig;
}else{
return deviceConfigs[__num];
}
}
"""
#initializeDriver
Scenario: Initialize Driver
* def driverConfig = getDriverConfig()
* configure driver = driverConfig
* print 'Driver Config: ', driverConfig
* driver envHost
# also tried to def here
# * def sessionId = driver.sessionId
# * eval scenarioStatus.browserstackSessionId = sessionId
# * karate.write(sessionId, 'classpath:browserstackSessionId.txt')
* driver.fullscreen()
#takeScreenshot
Scenario: Take screenshot
* driver.screenshot()
#createBrowserStackConfig
Scenario: Create Browserstack Config
* def deviceCapabilities = deviceConfigs[__num]
* def driverUrl = 'https://' + browserstackUsername + ':' + browserstackKey + '#' + browserstackUrl + '/wd/hub'
* eval commonCapabilities.build = (karate.match(typeof browserstackBuildName, 'undefined').pass) ? commonCapabilities.build + currentEpochTime : browserstackBuildName
* def desiredCapabilities = karate.merge(deviceCapabilities, commonCapabilities)
* def driverType = (karate.match(desiredCapabilities.browserName, "#notnull").pass) ? desiredCapabilities.browserName : desiredCapabilities.browser + 'driver'
* eval driverType = (karate.match(driverType, 'firefoxdriver').pass) ? 'geckodriver' : driverType
* eval desiredCapabilities.name = (karate.match(desiredCapabilities.browserName, "#notnull").pass) ? desiredCapabilities.browserName : desiredCapabilities.browser
* eval desiredCapabilities.name = browserTestName + desiredCapabilities.name
* def capabilities = karate.merge(deviceCapabilities, commonCapabilities)
* eval capabilities.name = (karate.match(capabilities.browserName, "#notnull").pass) ? capabilities.browserName : capabilities.browser
* eval capabilities.name = browserTestName + capabilities.name
* def browserSession = { desiredCapabilities: '#(desiredCapabilities)', capabilities: '#(capabilities)' }
* def browserstackConfig = { type: '#(driverType)', webDriverSession: '#(browserSession)', start: false, webDriverUrl: '#(driverUrl)' }
Even if the driver is not callable - you should be able to access variables, right ?
So if you did this within the normal test-flow:
* def sessionId = driver.sessionId
Then the sessionId variable should be directly accessible in the after-hooks. Do karate.get('sessionId') if needed, try it and please comment below if it works.

Can execute JS function in the browser at KarateDriver?

When using KarateDriver, I want to define and execute JS function in the browser.
Is is possible?
I want to define it like:
* def someFn =
"""
function(param) {
// DOM operation in the browser
// Event handling in the browser
return
}
"""
* assert someFn('param1') == '<span>param1</span>'
Edit1:
I define and execute;
* def keyword = 'karate'
* def formSubmit =
"""
function(formId) {
var formElem = document.getElementById(formId);
formElem.submit();
}
"""
Given driver 'https://github.com/search'
And driver.input('input[name=q]', keyword)
When driver.eval(formSubmit('search_form'))
Then eval driver.waitUntil(driver.location == 'https://github.com/search?utf8=%E2%9C%93&q=' + keyword + '&ref=simplesearch')
but this feature is failure.
javascript evaluation failed: driver.eval(formSubmit('search_form')), ReferenceError: "document" is not defined in <eval> at line number 2
Can it use DOM operations?
Edit2:
I can define and execute the JS function:
* def getSubmitFn =
"""
function(formId) {
return "var formElem = document.getElementById('" + formId + "');"
+ "formElem.submit();"
}
"""
You can do driver.eval() where the argument is raw javascript code as a string. I think this is sufficient for your needs:
* match driver.eval("location.href") == webUrlBase + '/page-01'
* assert driver.eval('1 + 2') == 3
EDIT: the JS engine for Karate and the Browser JS engine is different and there is no connection. So you have to pass JS as raw strings to driver.eval() here is an example that works for submitting a form.
* def getSubmitFn =
"""
function(formId) {
return "document.getElementById('" + formId + "').submit()"
}
"""
* def temp = getSubmitFn('eg02FormId')
* print temp
* driver.eval(temp)
EDIT: I just remembered, * driver.eval() is valid, no need to do * eval karate.eval()
Typically what you pass to driver.eval() can be simple, but it has to be a string, and you cannot use Karate variables (you have to hard-code them when creating the JS dynamically). You can use DOM objects and functions. You can have multiple statements of JS separated by ;.

Cannot convert to map when re-use one feature on other directory

I have read: https://stackoverflow.com/search?q=%5Bkarate%5Dcannot+convert+to+map and https://github.com/intuit/karate/issues/544
I am using karate-0.8.0
I have one feature which will be re-used on A directory, the content like:
#ignore
Feature:
Background:
* url baseUrl
* def Sign = Java.type('cruiser.token.Sign')
* configure afterScenario =
"""
function() {
if (karate.info.errorMessage != null) {
karate.log(karate.info.errorMessage);
}
}
"""
Scenario:
* def ck = Sign.execute('#(uid)')
* path '/rest/n/rt/upload'
* cookies ck
* multipart fields '#(fo)'
* multipart file rt = { read: 'classpath:cruiser/http/rt/A/123.mp3', filename: '123.mp3', contentType: 'audio/mp3' }
* method post
* status 200
* match response contains { result: 1 }
And have other one feature file on B directory, content like this:
Feature:
Background:
Scenario:
* def fo =
"""
{
'title': '你好!',
'description': '很好听哦'
}
"""
* def x = call read('classpath:cruiser/http/rt/A/upload-base.feature') { uid: 33, fo: '#(fo)' }
* match x.response contains { result: 1 }
* print x.response.feed.id
its runner name is XRunner.java
when mvn test -Dtest=XRunner, the error info:
Running cruiser.http.rt.B.XRunner
11:25:33.138 [main] INFO com.intuit.karate.junit4.Karate - Karate version: 0.8.0
11:25:33.896 [main] ERROR com.intuit.karate - feature call failed: classpath:cruiser/http/rt/A/upload-base.feature
arg: {uid=33, fo={title=你好!, description=很好听哦}}
cannot convert to map: '#(fo)'
Failed scenarios:
cruiser/http/rt/B/x.feature:3 # Scenario:
Both these lines are wrong:
* def ck = Sign.execute('#(uid)')
* multipart fields '#(fo)'
Read this: https://github.com/intuit/karate#rules-for-embedded-expressions
In Karate, expressions are pure JS by default. So just do this:
* def ck = Sign.execute(uid)
* multipart fields fo