12

I have a Rest API which generates a token. This session token is used across multiple REST API's as an authorization Bearer token. I used this as reference: https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/logging-in__jwt/cypress/integration/spec.js

However, in that example, the function to generate token is embedded in the test. I tried to create a custom command for which should store locally but it is not being picked up by the test. Note that no return value is included in the custom command.

My code below under support/commands.js:

let identity
Cypress.Commands.add('postToken', () => {
    cy.request({
        method: 'POST',
        url: Cypress.env('api_identity_url'), //get from cypress.env.json
        form: true, //sets to application/x-www-form-urlencoded
        body: {
            grant_type: 'client_credentials',
            scope: 'xero_all-apis'
        },
        auth: {
            username: Cypress.env('api_identity_username'),
            password: Cypress.env('api_identity_password')
        }
    })
        .its('body')
        .then((response) => {
            identity = response
            window.localStorage.setItem('identity', JSON.stringify(identity))
            cy.log(identity.access_token)
        })
})

My test:

context('Check token details', () => {
  it('Check token', () => {
      cy.postToken()
      const bToken = window.localStorage.getItem('identity')
      cy.log(bToken)
  })
})

When I run the test, the log shows null value for 'identity'. However, it shows the current value in the custom command where I placed cy.log(identity.access_token) I tried using cy.writeFile but I don't think this is a clean method. There must be some way data can be passed between functions, and different classes.

Sample JSON format:

{
  "token": "<this is the value I would like to use for other API's authorisation bearer token>",
  "expires_in": 1200,
  "token_type": "Bearer"
}
ebanster
  • 886
  • 1
  • 12
  • 29

3 Answers3

15

You can use the cypress-localstorage-commands package to persist localStorage between tests.

In support/commands.js:

import "cypress-localstorage-commands";

Cypress.Commands.add('postToken', () => {
  cy.request({
    method: 'POST',
    url: Cypress.env('api_identity_url'), //get from cypress.env.json
    form: true, //sets to application/x-www-form-urlencoded
    body: {
      grant_type: 'client_credentials',
      scope: 'xero_all-apis'
    },
    auth: {
      username: Cypress.env('api_identity_username'),
      password: Cypress.env('api_identity_password')
    }
  })
  .its('body')
  .then(identity => {
    cy.setLocalStorage("identity_token", identity.token);
  });
});

Inside your tests:

describe("postToken", ()=> {
  before(() => {
    cy.postToken();
    cy.saveLocalStorage();
  });

  beforeEach(() => {
    cy.restoreLocalStorage();
  });

  it("should exist identity in localStorage", () => {
    cy.getLocalStorage("identity_token").should("exist");
    cy.getLocalStorage("identity_token").then(token => {
      console.log("Identity token", token);
    });
  });

  it("should still exist identity in localStorage", () => {
    cy.getLocalStorage("identity_token").should("exist");
    cy.getLocalStorage("identity_token").then(token => {
      console.log("Identity token", token);
    });
  });
});
Javier Brea
  • 1,345
  • 12
  • 16
  • Thanks. This is half way there which is good. However, I would like to use that *value* (identity) to be passed on between API tests. For example, I'd like to use `identity.token` (I have included in my post the sample JSON format) as a authorisation bearer token for another API. I can't find it really clear from the documentation. – ebanster Nov 24 '19 at 11:47
  • In my example I used the "should" command to check that localStorage "identity" was really set, but you can also use the "then" subcommand to get the real localStorage value. I update the code example to show you how. – Javier Brea Nov 24 '19 at 18:56
  • Thanks Javier. I got it now. Quite new with the async language of JS and Cypress. FYI, I added `var obj = JSON.parse(token)` in my code to capture the data. – ebanster Nov 24 '19 at 22:06
  • In my example `JSON.parse(token)` was not necessary because the token was the unique property being saved to localStorage, not the full object, but, if you are saving the full objet using `JSON.stringify` then that's right, you'll need to call `JSON.parse` to convert the object again when reading it. – Javier Brea Nov 25 '19 at 17:14
  • Hi @javier-brea I know that the local storage has resolved my issue but what if I have to capture the response of the API that calls the token in its request. How to I call that in my test? Example: cy.newAPI(token) then I want to save the response JSON details of that newAPI into another set of localStorage function? I am also happy to post this separately but I felt it would be best to put it here so you get notified. – ebanster Jan 02 '20 at 05:16
  • Hi, @er123. I don't know if I have understood you, but if you are trying to use the localStorage as an "storage" where saving your api responses with the sole purpose of accessing them afterwards to write assertions, then my response is that you shouldnt. The usage of localStorage have sense when your webpage is using it to store some data, as the user token. Then it has sense to persist it or even to check if it has been set correctly, but for api responses, I think you should test the data that the web interface is showing related to the api request. – Javier Brea Jan 03 '20 at 11:44
  • Got it. Thanks. Yes, you understand it correctly :) I am using it once for tokens but returning the response for other API's using the cy command is giving me a bit of challenge. I posted another question via if you are able to assist: https://stackoverflow.com/questions/59558827/cypress-get-token-from-api-then-save-in-local-storage-and-use-in-another-apis – ebanster Jan 05 '20 at 22:47
0

Thank you Javier for showing me the cypress-localstorage-commands package. I started using it. Until that I used to get the login token like this.

describe('Record audit', () => {
    let token = null;

    before(() => {
        cy.login().then((responseToken) => { // or postToken() in your case
            token = responseToken;
        });
    });

    it('I can use the token here', () => {
        cy.log(token);
    });
});

The only difference is that my login command returns the token. It should look like this in your code

// commands.js

.then((response) => {
    identity = response
    window.localStorage.setItem('identity', JSON.stringify(identity))
    cy.log(identity.access_token)
    return identity.access_token
})
jiri.hofman
  • 127
  • 1
  • 3
  • 10
  • If I have multiple values from different API's I'd like to capture (e.g. user ID, token ID, etc.), is localstorage still the best option to use? From your example above as well, `token = responseToken`, I assume that cy.login always returns a "responseToken" value – ebanster Dec 08 '19 at 12:41
0

I used this code in commmand.js

var headers_login = new Headers()
headers_login.append('Content-Type', 'application/json')

Cypress.Commands.add('get_token', (username, password)=>{
var token = ""
cy.request({
    method: 'POST',
    url: Cypress.env("api") + "users/getToken",
    failOnStatusCode: false,
    json: true,
    form: true,
    body: {username: username, password: password},
    headers: headers_login
 }).then((json) => {
    //cy.setLocalStorage('token', json.body.response.data.token)   
    token = json.body.response.data.token
    return token;
 }) }) 

After in your test add this code

describe('test', ()=>{
 before(()=>{
    cy.get_token('username', 'password').then(youToken => {
        cy.visit('/', {
             onBeforeLoad: function (window) {
                window.localStorage.setItem('token', youToken);
             }
         })
     }) 
     cy.close_welcome()        
 })
 it('test 001', ()=>{
       // contain test 
 })})
afterEach(()=>{cy.clearLocalStorage('token')})