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

What is the best way to enlarge text size with jquery?

I am attempting to: $('a[data-text="size-bigger"]').click(function () { a=$('span'); b=$('span').css('font-size'); a.css('font-size', b+1); } ); Initially, I ha ...

Accordion Tuning - The Pro and Cons

Here is a functional accordion that I've implemented, you can view it here This is the JavaScript code I am using: $(document).ready(function ($) { $('#accordion').find('.accordion-toggle').click(function () { //Expa ...

JavaScript error message stating that the setAttribute function is null

As a newcomer to JS, I am currently working on creating a task list webpage that allows users to submit projects and create task lists within each project with designated due dates. In order to generate unique ID tags for projects and tasks, I utilized UU ...

Why isn't res.send() in Node.js sending the message?

Despite numerous attempts, I am unable to send my response message. It was working briefly, but after some code changes, it stopped functioning. Can anyone provide a solution to this challenging issue? exports.listBoth = function(req, res) { ...

Focus on targeting each individual DIV specifically

Is there a way to toggle a specific div when its title is clicked? I have tried the following code snippet: $( '.industrial-product' ).find('.show-features').click(function() { $('.industrial-product').find('.ip-feat ...

How to convert deeply nested object structures containing arrays in JavaScript

Despite the less-than-desirable name of this inquiry, the question is fairly straightforward. I have a particular object: let test = { date1: [ { time: 1, value: 5, }, { time: 2, value: 6, }, ], date2: [ { ...

Using Google Apps Script to input data into the next available row

Utilizing Google Apps Script, I am retrieving data from another spreadsheet and storing it daily in a sheet named "DATABASE". Although my current solution is basic, it keeps overwriting existing data. I wish to enhance the script to copy data from the imp ...

Avoid displaying passwords in source code

Currently, I am working on developing a password manager web application similar to LastPass. One issue that has come to my attention is the visibility of variables containing decrypted passwords in the source code after retrieving them from a database u ...

Achieve horizontal bar movement by utilizing google.visualization.DataTable in a left-to-right motion

I am exploring the possibility of reversing the direction of a chart(bar) using google.visualization.DataTable. In the current setup, the bar progresses from left to right, but I wish for it to move from right to left instead. Below is what I have attempte ...

Updating results in Angular.js based on variable changes

After setting some data in the window, I am facing issues with updating an angular.js table created using the smart table module/plugin. The data is structured like this: window.checker={};window.checker.checked = [{'ip':9,'port':0}] ...

Unable to invoke parent method from child component in Vue causing issue

I am facing an issue where I am trying to trigger a method in the parent component from the child component using $emit, but for some reason, it is not working as expected. Can someone please help me troubleshoot this problem? Parent Component <templat ...

Using `popWin()` in conjunction with `echo php` in Javascript

How can I create a JavaScript popup window inside an echo line? I have tried the following code but the popup window does not work: echo '<td> <a href="javascript:popWin(edit.php?id='.$row[id].')">Edit</a></td>&apos ...

Change the z-index of divs when clicked

When a button is clicked, I want to iterate through a group of absolutely positioned children divs with varying z-indexes. The goal is for the z-index of the currently visible div to decrease on each click, creating a looping effect where only one div is v ...

"Troubleshooting the issue of nested jQuery Ajax requests failing to execute properly

UPDATE: I'm getting an error saying that my li tag is considered an illegal token. I'm unsure how to resolve this issue as I believed I was appending it within a ul tag I'm tasked with fetching a JSON array of public Github repositories and ...

Using Golang to encode JSON for parsing in JavaScript

I am working with a struct that looks like this: type User struct { Login string `json:",string"` PasswordNonce Nonce `json:",string"` PasswordHash HashValue `json:",string"` CreatedOn time.Time `json:",string"` Email ...

Tips for implementing lazy loading with an owl carousel featuring background images

Is there a way to add lazy loading to a semi custom owl carousel that uses background images instead of regular img tags? I've tried using Owl carousel's function for lazy loading but it doesn't seem to work. How can I achieve this? This is ...

Utilizing JSON API data to populate Leaflet maps

I am currently working on fetching JSON data from an API call, extracting the latitude, longitude, and name variables to create a GeoJSON array, and then displaying it on a Leaflet map. Despite not encountering any errors in the console, the geojson appea ...

Vulnerability protection against AngularJS JSON is not removed

I am currently working on an Angular app that communicates with an API. The JSON responses from the API are prefixed with )]}', as recommended in Angular's official documentation. The issue I am facing is that my browser seems to try decoding th ...

The image source failed to load the image using the function

Initially, I set up a sandbox to work on this issue: https://codesandbox.io/s/naughty-almeida-u66mlt?file=/src/App.js The main problem arises when the requested image fails to display after the function is called and the URL is returned. Here are my atte ...

Issues with the Node Package Manager's watch command functionality

Having installed the frontend dependency in my Vue.js project, I attempted to run the command npm run watch. I updated the scripts section in package.json as shown below- "scripts": { "dev": "npm run development", &qu ...