I have just started implementing karate UI (v0.9.5). Have already implemented api testing using karate and it works perfectly.
Following the HTTP basic auth strategy on this page - https://github.com/intuit/karate#http-basic-authentication-example the basic auth handling works for api tests. I set the HTTP headers once and run all api tests.
Now for the UI testing, the URL that I open brings up the basic auth pop-up as shown below:
So I thought that I could use the same strategy that I used for api tests to handle this. In the background section of my feature file, i call the feature file that does the authentication and sets headers as below:
The called feature file to set headers (admin-headers.feature). This feature file gets the token after admin user login is performed via karate-config.js. Then assigns the token along with the Base64 encoded basic auth to the headers calling headers.js. The Base64 user and password are being input as maven arguments and read via karate-config variables.
(/admin-headers.feature)
Feature: karate-config.js will perform one time login for admin and
set the session token for all subsequent requests
Background:
* def session = adminAuthInfo.authSession
* def basic_auth = call read('classpath:basic-auth.js') { username: '#(basicAuthUser)', password: '#(basicAuthPassword)' }
* configure headers = read('classpath:headers.js')
Scenario: One-time login for user and set the
session token in request header
The js code for returning Auth and Cookie to above feature file (/headers.js).
function() {
var session = karate.get('session');
var basic_auth = karate.get('basic_auth');
if(session){
return {
Authorization: basic_auth,
Cookie: "SESSION=" + session
};
} else {
return {};
}
}
My UI test feature file (/ui-test.feature):
Feature: Login test
Background:
# Authorise via api
* callonce read('classpath:common/headers/admin-headers.feature')
* configure driver = { type: 'chrome' }
Scenario: Test login
Given driver 'https://test.internal.mysite.com/names'
Running the above feature file still shows the auth pop-up.
I then tried to set the cookies while I am initialising the driver (which I think is probably not the right way?) as below:
Feature: Login test
Background:
# Authorise via api
* def login = callonce read('classpath:common/headers/admin-headers.feature')
* def uiCookie = { name: 'SESSION', value: '#(login.userAuthInfo.authSession)', domain: 'test.internal.mysite.com' }
* configure driver = { type: 'chrome', cookie: '#(uiCookie)' }
Scenario: Test login
Given driver 'https://test.internal.mysite.com/names'
The above also does not work. What is it that I am doing wrong here? the pop-up keeps coming up because the cookie is not set when the driver is initialised and then opens the specified url?
Help is much appreciated.
I think you raised a very good feature request, that configure driver should take cookies also, so that you can navigate to the page and set cookies in one-shot, and I opened a feature request: https://github.com/intuit/karate/issues/1053
So try this sequence, refer docs for cookie(): https://github.com/intuit/karate/tree/master/karate-core#cookieset
* driver 'about:blank'
* cookie(uiCookie)
* driver 'https://test.internal.mysite.com/names'
And now it should work !
Feature: Windows Authentication feature
Background:
* configure driver = { type: 'chrome' }
Scenario: Windows Authentication Valid Login test case
Given driver 'http://the-internet.herokuapp.com/basic_auth'
And delay(3000)
And screenshot()
* robot {}
* robot.input('admin' + Key.TAB)
* robot.input('admin')
* robot.click('Sign in')
And delay(3000)
And screenshot()
works fine with chrome, edge
Related
I understand that Cypress does not allow flipping from one domain to another domain because it will error with:
chrome-error://chromewebdata/
However, I need a workaround. I am providing a test set for multiple environments: STAGE, DEMO, PROD.
With DEMO and PROD, during the authentication phase (username/password), stay within the same domain:
VISIT: https://[demo|www].foo.com
AUTH: https://account.foo.com/auth >> username >> password
CONSENT: https://[demo|www].foo.com/action...
With STAGE, the authentication phase flips to another domain:
VISIT: https://[stage].foo.com
AUTH: https://account.bar.com/auth >> username >> password
CONSENT: https://[stage].foo.com/action...
Thereby, Cypress fails to redirect from VISIT to AUTH because of domain flip. This is blocking testing of STAGE.
What recommended workaround approaches?
Puppeteer?
Native Cypress using cy.request()?
Referenced:
Handling Cypress url redirect
Error with authentication in e2e tests using cypress: chrome-error://chromewebdata
Thank you, much appreciate the assistance.
The approach I took is similar to the Cypress Recipe Login with CSRF token
As mentioned, each deployment has its own website URL followed by account login URL.
Needed first is the CSRF token from account login page, which its URL is referred here as $baseUrl:
Cypress.Commands.add('cmdGetCSRF', ($baseUrl) => {
cy.log('COMMAND cmdGetCSRF');
expect($baseUrl)
.to.be.a('string')
.not.empty
cy.request($baseUrl)
.its('body')
.then(($body) => {
const $html = Cypress.$($body)
cy.log('html', $html)
const csrf = $html.find('input[name="__RequestVerificationToken"]').val()
expect(csrf)
.to.be.a('string')
.not.empty
return cy.wrap(csrf)
})
})
Then within body of requestOptions provided to cy.request() which will performing the login, provide one-time CSRF token:
const body: { [key: string]: string } = {
email: $envCypress.account_username,
password: $envCypress.account_password,
__RequestVerificationToken: $csrfToken
};
Cypress has recently add a new experimental feature for running test on multiple origins here. There are examples for cross origin login too. More on experimentalSessionAndOrigin feature:
https://docs.cypress.io/api/commands/origin
https://docs.cypress.io/api/commands/session
var url = "https://web-site_name/page/?format=json&var_data-organization_dates&xlsexport=true";
var payload =
{
"login" : "login",
"password" : "pass",
};
var options =
{
"method" : "post",
"payload" : payload,
"followRedirects" : false
};
var login = UrlFetchApp.fetch("https://web-site_name/page/" , options);
var sessionDetails = login.getAllHeaders()['Set-Cookie'];
Logger.log(login.getAllHeaders());
here is the part of the code I try to use, to automate export of the data from web-site, i do have proper login and password and able to download file in json (opened in xsl) manually, I've got the address to the downloaded file in network in developer tools, but i have a problem on the first stage - when trying to authorize to the web-site - access denied. I've tried the code, given in answers on stackoverflow, but it still doesn't work.
How to make an url fetch request correctly, depends on the website you want to access and the authentication they uses
In the simplest case, your website requires HTTP basic authentification, in this case the correct syntax would be
var authHeader = 'Basic ' + Utilities.base64Encode(login + ':' + pass);
var options = {
headers: {Authorization: authHeader}
}
If your website uses a different authentication form, you might need to provide an access token.
In any case: the authentication credentials go into headers, not into payload!
payload is the data that you want to post = upload to the website.
If you want export data from the website - that is download data - you do not need a payload and the correct method would be get, not post. Btw., if the method is get, you do not need to specify it.
Please see here for more information and samples.
I am calling login feature file from other feature file from where I am passing url, username and password but it is not working for me. I am not using Background key here and i do not want also.
#CallAnotherFeature
Feature: Call Login Feature
Scenario: Calling Login Test
* def config = { endPointURL: 'https://qa1.testurl.com/login',username: 'user123', password: 'password123' }
* def result= call read('Login.feature') config
* print result.response
* print 'Sign In-'+signIn
* print 'Sign In Reponse-'+signIn.response
Feature: Login Feature
Scenario: Test Login for different users
* print 'Starting Test','#(endPointURL)'
Given url '#(endPointURL)'
* print 'user name','#(username)'
* print 'Password ','#(password)'
#And form field username = '#(username)'
#And form field password = '#(password)'
And request { username: '#(username)', password: '#(password)'}
When method post
Then status 200
* print response
* match response.loginSuccess == true
In Login.feature I tried to pass username and password as form data also even though these did not work. Could someone tell me what mistake I am making here.
I am using latest karate version 0.9.0
I see a few issues in your scripts,
1. Call Login Feature
1.1) I don't see signIn variable initialized anywhere in this feature nor from your login feature but you are trying to print it.
1.2) = Should be placed properly ;)
* def result = call read('Login.feature') config
2. Login Feature
2.1) I think you misunderstood the concept of embedded expressions. only for templating it into a JSON you may use it. but for calling it you can simply use the variable name.
eg.
Given url endPointURL
And form field username = username
And request { username: '#(username)', password: '#(password)'}
NOT
Given url '#(endPointURL)'
And form field username = '#(username)'
I will be more clear for you if you read the karate documentation from here -> karate Doc and refer karate Demos
I have Karate Tests for the APIs which are on Amazon API Gateway. As such, in my Karate Tests I have to provide client_id and client_secret for authentication. I was wondering if there is a way I could store the credentials outside my github repository and use it at run time. Is it possible to do that in Karate ?
Here is how my tests look like:
Feature: API Test all endpoints using Karate
Background:
* configure ssl = true
* url baseUrl
* def res = (env == 'qa'? 'classpath:endpoints/user-login.feature' : 'classpath:endpoints/user-login-dev.feature')
* def token = call read(res)
* def headerData = {Authorization: #(token.nextGen),Accept: 'application/json;v=1'}
* headers headerData
Here is the user-login.feature file
Feature: API Test using Karate
Background:
* configure ssl = true
Scenario: Get authorization header
Given url 'https://api.cloud.xyz.com/oauth2/token?client_id=****&client_secret=****&grant_type=client_credentials'
When method get
Then status 200
And def tokenType = response.token_type
And def accessToken = response.access_token
* def nextGen = tokenType + ' '+ accessToken
* def headerData = {Authorization: nextGen,Accept: 'application/json;v=1'}
Any pointers on how I can have the client_id and client_secret passed to the tests at run time and not stored in github.
Thanks in advance!
The easiest way would be to pass them as Java system properties via the command-line, which you can very easily do from a test or from a CI triggered run.
Refer to the documentation here: https://github.com/intuit/karate#dynamic-port-numbers
An example of how it could look like in your case:
Given url 'https://api.cloud.xyz.com/oauth2/token'
And param client_id = karate.properties['client.id']
And param client_secret = karate.properties['client.secret']
And param grant_type = 'client_credentials'
And on the command-line:
mvn test -DargLine="-Dclient.id=**** -Dclient.secret=**** -Dkarate.env=qa"
I have some authentication requried to hit a particular url. In browser I need to login only once, as for other related urls which can use the session id from the cookie need not required to go to the login page.
Similarly, can I use the cookie generated in the cookie file using --cookies-file=cookies.txt in the commandline in phantomjs to open other page which requires the same cookie detail.
Please suggest.
Phantom JS and cookies
--cookies-file=cookies.txt will only store non-session cookies in the cookie jar. Login and authentication is more commonly based on session cookies.
What about session cookies?
To save these is quite simple, but you should consider that they will likely expire quickly.
You need to write your program logic to consider this. For example
Load cookies from the cookiejar
Hit a URL to check if the user is logged in
If not logged in
Log in, Save cookies to cookiejar
continue with processing
Example
var fs = require('fs');
var CookieJar = "cookiejar.json";
var pageResponses = {};
page.onResourceReceived = function(response) {
pageResponses[response.url] = response.status;
fs.write(CookieJar, JSON.stringify(phantom.cookies), "w");
};
if(fs.isFile(CookieJar))
Array.prototype.forEach.call(JSON.parse(fs.read(CookieJar)), function(x){
phantom.addCookie(x);
});
page.open(LoginCheckURL, function(status){
// this assumes that when you are not logged in, the server replies with a 303
if(pageResponses[LoginCheckURL] == 303)
{
//attempt login
//assuming a resourceRequested event is fired the cookies will be written to the jar and on your next load of the script they will be found and used
}
});
The file created by the option --cookies-file=cookies.txt is serialized from CookieJar: there are extra characters and it's sometimes difficult to parse.
It may looks like:
[General]
cookies="#Variant(\0\0\0\x7f\0\0\0\x16QList<QNetworkCookie>\0\0\0\0\x1\0\0\0\v\0\0\0{__cfduid=da7fda1ef6dd8b38450c6ad5632...
I used in the past phantom.cookies. This array will be pre-populated by any existing Cookie data stored in the cookie file specified in the PhantomJS startup config/command-line options, if any. But you can also add dynamic cookie by using phantom.addCookie.
A basic example is
phantom.addCookie({
'name': 'Valid-Cookie-Name', /* required property */
'value': 'Valid-Cookie-Value', /* required property */
'domain': 'localhost', /* required property */
'path': '/foo',
'httponly': true,
'secure': false,
'expires': (new Date()).getTime() + (1000 * 60 * 60) /* <-- expires in 1 hour */
});
With these methods, it's not so difficult to implement your own cookie management logic.