Test State Management in Cypress (Part 7)

When I first started building this Cypress framework, I was barely exploring what things cypress has to offer out of the box and few missing things which can be obtained with the help of external NPM packages. Once I was confident enough with the framework I started experiencing other challenges while trying to reuse my generated data; so the need for State Management arose.
Test State Management is essentially how to manage the data which is generated by the the framework which is to be used in subsequent test steps

The Challenge
So I had a framework which was running and consuming and generating test data.
In order to consume data which was generated from a step in subsequent step, I was lost for how to do so with Cypress.
Cypress does provide something called Variables & Aliases or Environment Variables out of the box, but using which to derive the expected state/resource could be a cumbersome process.
For instance, you created a user and wanted to use its generated user_id in subsequent API test step:
//Step #1 : Create a user
cy.request({
method: 'POST',
url: '/user',
headers: {
'Content-Type': 'application/json'
},
body: {
"userName":Cypress.env('users')[userType.toUpperCase()].email,
"password":Cypress.env('users')[userType.toUpperCase()].password,
"Name": Cypress.env('users')[userType.toUpperCase()].password}
}).as('createUser')
//Step #2 : retrieve user ID to update profile image
cy.get('@createUser').then((response)=> {
expect(response.status).to.eq(201);
let user_id = response.body.user_id;
//using user_id here to update profile image
})
This may seem simpler from the example, but as the steps increase and you need to keep track of all the aliases which were created to get data and reference them each and every time. We will look how this can be tackled by creating a simple JSON store.
The Solution:

In order to start you can clone the repo here, as I will be using this as a base for building our Test State Management.
We'll start by adding an empty store.json file under cypress/fixtures/state folder.

Now in order to get and store values inside this json file we will add 2 custom commands inside support/command.js file
First install lodash package using command line:
npm install lodash --save-dev
The saveState() requires a key-value pair to save inside the store.json
//import Lodash to commands.js
const _ = require('lodash');
/**
* This Command stores a value to Test State
*
* @param {String} Key - Key to be stored
* @param {String} Value - Value to be stored
*/
Cypress.Commands.add("saveState", (key, value) => {
cy.log(key, value);
if(key.includes(">")){
let keyItems = key.split(">");
cy.readFile('cypress/fixtures/state/store.json').then((currState) => {
let newState = currState;
_.set(newState, keyItems, value);
cy.writeFile('cypress/fixtures/state/store.json', newState);
})
}else{
cy.readFile('cypress/fixtures/state/store.json').then((currState) => {
currState[key] = value;
cy.writeFile('cypress/fixtures/state/store.json', currState);
})
}
});
Usage:
cy.saveState("page_id", response.body.page.id); //stores as JSON page_id key value
cy.saveState("user>user_id", response.body.payload.id); //stores user_id inside user JSON object
Result:
{
"page_id": "asd67asdfsa",
"user":{
"user_id": "Ka6ds"
}
}
Here ">" is the special character which is used for signifying the hierarchy
Now similarly we'll add another command getState() to retrieve a stored value by using a stored key
/**
* This Command retrieves a param value stored in Test State
*
* @param {String} Key - stored param key
*/
Cypress.Commands.add("getState", (key) => {
if(key.includes(">")){
let keyItems = key.split(">");
cy.readFile('cypress/fixtures/state/store.json').then((state) => {
return _.get(state, keyItems);
})
}else{
cy.readFile('cypress/fixtures/state/store.json').then((state) => {
return state[key];
})
}
});
Usage:
//retrieve page_id from the store
cy.getState("page_id").then(value => {
let pageId = value;
console.log(pageID); // it will print asd67asdfsa
// now we can use pageId where ever its needed
});
//retrieve user_id from user json object
cy.saveState("user>user_id").then(value => {
let userId = value;
console.log(userID); // it will print Ka6ds
});
Once above changes are made into the framework we will start by adding a new feature file.

@API
Feature: Pokemon GET /pokemon/{id} by id
@smoke @test
Scenario: Fetch data for a pokemon using API and verify it
Given As a user I want to execute Pokemon GET api for Pokemon "pikachu"
Then Verify '@get_pokemon_data' response status code is 200
When I save the user id in Test Store
And I make a GET request on '/pokemon/{id}' endpoint with the stored id
Then Verify '@get_pokemon_data_by_id' response status code is 200
And Verify response details for Pokemon "pikachu"
in apiSteps.js add following code for stepDefinition for 2 new steps
When('I save the user id in Test Store', () => {
cy.get('@get_pokemon_data').then((response)=> {
//save pokemon id
cy.saveState("PokemonData>PokemonID", response.body.id)
})
});
When('I make a GET request on {string} endpoint with the stored id', () => {
cy.getState("PokemonData>PokemonID").then(pokeID => {
cy.request({
method: 'GET',
url: 'https://pokeapi.co/api/v2/pokemon/' + pokeID,
headers: {
'Content-Type': 'application/json'
},
failOnStatusCode:false
}).as('get_pokemon_data_by_id')
})
});
That's it. When you run this feature file from the cypress runner that it first fetches the data from GET /pikachu/{name} endpoint and stores the pokemon_id from the response into our store.json and then in subsequent step uses this stored value to fetch data from GET /pikachu/{id} endpoint

Final Notes:
We could additionally add a clearState() command which could be run at the beginning or end of a test run.
/**
* This Command clears the Test State
*
*/
Cypress.Commands.add("clearState", () => {
cy.readFile('cypress/fixtures/state/store.json').then((currState) => {
currState = {};
cy.writeFile('cypress/fixtures/state/store.json', currState);
})
cy.log("Test state was reset");
});
Caution: using cy.clearState() will remove everything from store.json
Full code on:
https://github.com/far11ven/Cypress-TestFramework/tree/develop/Part 07