Establishing a global variable in Cypress through a function

My current workflow involves the following steps:

1) Extracting a field value from one page:

 var myID;
 cy.get('#MYID').
        then(($txt) => {
            myID=  $txt.text();
        })
       .should('not.equal', null); 

2) Moving to a new page and attempting to verify if this id exists there:

cy.get('#myTable').find('td').contains(myID);

An error is thrown stating that myID is not defined. I understand that the first function is asynchronous, and according to the documentation, an alias can be used. However, due to certain constraints, using the beforeEach() function for aliases is not possible in this particular test case. I also tried utilizing async/await but encountered issues with the variable still being undefined.

Answer №1

The root issue here lies in the asynchronous nature of Cypress commands running independently from the test code that initiates them. This becomes evident when incorporating console logs into your code,

var myID;
cy.get('#MYID')
  .then(($txt) => {
    myID=  $txt.text();
    console.log('1', myID);
  })
 .should('not.equal', null); 

console.log('2', myID);

This results in the following output:

2 undefined
1 myText

To address this, using an alias allows for passing a value down the command chain.

Refer to this section of the documentation, which showcases a similar code pattern as demonstrated in the DO NOT USE THIS example.

However, aliases are cleared between tests, so setting up a beforeEach() function is necessary to obtain a fresh copy of the required ID for each test.

Another issue arises in how you retrieve the text value.

Without a return statement, the .then() command forwards whatever subject it receives to the subsequent command. Check then- Yields

Moreover, if there is no return present, the result of the last Cypress command within the callback function will be yielded as the new subject and flow into the next command.

Subsequently, the .should('not.equal', null) is confirming that the element is not null rather than verifying the non-null status of the text itself.

A more effective approach involves using .invoke('text'), equivalent to $txt.text(), providing the text value to the .should().

Additionally, .should('not.equal', null) does not check for content presence since an empty element returns an empty string via element.text(). Utilize .should('not.equal', '') instead.

Saving via an Alias

describe('grabbing ID for use in multiple tests', () => {

  beforeEach(() => {
    cy.visit('my-page-1.html')
    cy.get('#MYID')
      .invoke('text')
      .as('mySavedID')
  })

  it('ID should not be null', () => {

    cy.get('@mySavedID')
      .should('not.equal', '')

  })

  it('ID should be found in table', () => {

    cy.visit('app/navigate-to-new-page-2.html');
    cy.get('@mySavedID').then(myID => {
      cy.get('#myTable').find('td').contains(myID);
    })

  })
})

Saving by queuing the setting of the variable

If visiting page #1 proves time-consuming, the alias pattern might not be ideal.

In such cases, saving the variable through a custom command is viable. The code previously placed within the .then() now resides within a queued command, mitigating the async problem's occurrence.

describe('grabbing ID for use in multiple tests', () => {

  let savedVariable;

  Cypress.Commands.add("saveVariable", {prevSubject: true}, (value) => {
    savedVariable = value;
  });

  it('id should not be null', () => {

    cy.visit('my-page-1')
    cy.get('#someId')
      .invoke('text')
      .should('not.equal', '')
      .saveVariable()

    // OR test the variable separately like this

    cy.wrap(savedVariable)
      .should('not.equal', '')
  })

  it('id should be found in table', () => {

    cy.visit('my-page-2');
    cy.get('#myTable').find('td').contains(savedVariable);

  })
})

NOTE
The above method holds validity if both pages reside within the same domain, e.g., two pages of a single-page application (SPA). If encountering a new domain, the test runner resets itself, resulting in the loss of all javascript variables.

Answer №2

Store and retrieve data at the plugins level for specific tasks. Data is preserved when using cy.visit, unlike other levels that get reset. Include data and task definitions in plugins/index.js:

// plugins/index.js   
/// <reference types="cypress" />
module.exports = (on, config) => {

  // store data here
  const data = {};

  // define tasks
  on('task', {
    setValue: (params) => {
      const { key, value } = params;
      data[key] = value;
      return value;
    },
    getValue: (params) => {
      const { key } = params;
      return data[key] || null;
    }
  })        
}

Utilize this functionality in a test scenario:

// my.spec.js
/// <reference types="cypress" />
context('preserve data across page changes', () => {
  it('goes to first page and saves data', () => {
    return cy.visit('https://google.com').then(() => {
      // save data 
      return cy.task('setValue', { key: 'visited', value: 'google' });
    })
  });

  it('navigates to another page and verifies availability of saved data', () => {
    return cy.visit('https://example.com').then(() => {
      // retrieve data
      return cy.task('getValue', { key: 'visited' });
    }).then((value) => {
      expect(value).to.equal('google');
    });
  })
});

Answer №3

If you're looking for a unique approach, one suggestion is to store data on the Cypress global object.

context('Storing data using global object in Cypress', () => {

  before(() => {
    Cypress._savedData = {}
  })

  it('Loads first page and stores data', () => {

    cy.visit('https://google.com');
    cy.get('#MYID')
      .should('not.equal', null)
      .then($el => Cypress._savedData[myID] = $el.text() )

  });

  it('Loads another page and verifies stored data', () => {

    cy.visit('https://example.com');
    cy.get('#myTable').find('td').contains(Cypress._savedData[myID]);

  })
});

Answer №4

If you are encountering page navigations, using aliases may not be the best solution for your situation. This is particularly true if the variable is accessed before the alias is created due to asynchronous execution and queuing of then blocks.

One straightforward way to set a variable is by utilizing the browser's window object and assigning the value to sessionStorage.

Here is a snippet demonstrating this:

var myID;
 cy.get('#MYID').
        then(($txt) => {
            window.sessionStorage.setItem(myID, $txt.text());
        })
       .should('not.equal', null); 

You can access this variable within the test file (even from another test case) like so:

let myID = window.sessionStorage.getItem('myID');
cy.get('#myTable').find('td').contains(myID);

It is important to note that relying on one test's output in another is considered an anti-pattern and should be avoided, as highlighted in the Cypress Best Practices.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to show a loading indicator while waiting for ajax to finish loading?

While waiting for my messages to finish loading, I'd like to display a loading spinner. The loading spinner is implemented in my Message.vue: import backend from '...' export default { mounted: function() { this.loadMessages(); }, ...

Convert your Airbnb short link into the full link

I am currently developing an application that utilizes Airbnb links as part of its input. So far, I have identified two types of links: Long form, for example: . These are commonly used on desktop. Short form, such as: . These shorter links are often shar ...

The `Route` component is expecting a `function` for the `component` prop, but instead it received an `object`

For a while now, I've been grappling with an issue that seems to be unique to me. The problem lies within my component and container setup for the start screen rendering at the initial route. components/DifficultySelection.jsx import React from &apo ...

AngularJs FileList Drag and Drop Feature

Being brand new to front-end development, I decided it would be a fun challenge to implement drag and drop functionality on an existing upload page. However, as I began integrating ng-flow (a directive that helps with drag and drop), I encountered difficul ...

Determine the character's position in an input field by tracking mouse movements

I am in need of a way to determine the position of a character in an input field based on mouse movement. For example, if the input field contains the value 'abcde' and I hover my mouse over the character 'a', the position should be ...

Loading information in a directive by utilizing promises in a service with AngularJS

Can anyone lend a hand? I've been struggling to solve this issue. I created a directive (see below) to display a pre-written ul-list on a page using html data fetched asynchronously from a database server. Both the Directive and The Service are funct ...

Error in Angular form validation: Attempting to access property 'name' of an undefined value

Recently, I encountered an issue with my form while implementing Angular validation. The goal was to ensure that the input fields were not left blank by using an if statement. However, upon testing the form, I received the following error message: Cannot ...

Tips for displaying previous values when discarding changes to a record in a material-ui table

How can I prevent changes from reflecting in a material-ui table when clicking on the X icon while editing a row? Is there a way to only save the edited record on the check (_) icon instead? Any suggestions or solutions would be greatly appreciated as I am ...

Is randomly pairing 2 datapairs after slicing a JSON array easy or challenging?

There is a JSON file containing an unlimited number of users [{ "fname": "Hubert", "lname": "Maier", "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bd ...

The REST API request returns a response code of 0

I have been attempting to make an API call to the Insightly API using this code: var x = new XMLHttpRequest(); x.open("GET", "https://api.insight.ly/v2.2/Projects/Search?status=in%20progress&brief=false&count_total=false", true); x.setRequestHeade ...

Navigating through a multistep form in AngularJS using UI Router and arrow keys for seamless movement

Is there a way to navigate to the next or previous form step using arrow keys in AngularJS UI Router? The code provided below is currently allowing navigation with previous and next buttons. .config(function($stateProvider, $urlRouterProvider) { $stat ...

Adapt appearance according to the length of the text

Currently, I have an array that stores multiple strings based on displayed charts. My objective is to find the longest string within this array. So far, this task has been executed without any issues. The code snippet for this process is as follows: var ...

Tips for Loading a Selected Option from an Ajax Call in Angular JS on Page Load

How do I automatically set the selected option from an ajax call when the page loads? I am fetching zones using an ajax call and I want to trigger the change function of the zones on page load in order to set the selected option of the cities select box. ...

Adding a PHP file into an HTML page using JavaScript include

Recently, I was assigned to collaborate with a third-party vendor who displays movie times on their platform. We were given the opportunity to co-brand our website on their platform by creating a wrapper for our site. This wrapper would allow the movie tim ...

The JOI validation process is failing to return all error messages, even though the "abort early" option

I have been encountering an issue while trying to validate my payload using a joi schema. Instead of returning the errors specified in the schema, only one error is being displayed. Even when I provide a payload with name as "int", it only shows one custom ...

jQuery's Offset().left is experiencing some issues and not functioning correctly

Do you have a question about the jQuery offset() function? On my website, I utilize it to show an "email a friend" window when the email icon is clicked. However, the problem is that the window ends up stuck to the right side of the browser's window ...

Restrict page scrolling to the vertical position of a specified div [Using jQuery, javascript, and HTML]

Currently, I am in the process of developing a personal website that features numerous expandable items. My goal is to restrict the page scrolling to the height of the expanded item once it is opened. I do not want users to be able to scroll above or below ...

What is the method utilized by Redux to store data?

I am currently developing a small application to enhance my understanding of how to utilize redux. Based on my research, redux allows you to store and update data within the store. In my application, I have implemented an HTML form with two text inputs. Up ...

"Enhancing Hangman by updating the object-oriented array of values

I'm currently working on developing a hangman game using PHP and JS, but I've encountered some issues. In my project, there are two arrays involved - the answer array containing the correct letters and the user answer array storing the user' ...

Are elements in React Native PanResponder capable of being clicked?

While I have movable panresponders, I also require the ability to click on one for an onPress event. Is it achievable? At present, they are <View> elements. If I attempt to switch them to <TouchableOpacity> or a similar element, it results in ...