https://cypress.io logo
Variable is undefined
# i-need-help
m
I was refactoring a code I did some time ago. My test looked like this:
Copy code
typescript
describe('Shopping articles in the SWAG Labs store', () => {

  let items: object;

  beforeEach(() => {
    cy.visit('https://www.saucedemo.com/');
    cy.loginForm(Cypress.env('valid_user').username, Cypress.env('valid_user').password);

    cy.fixture("products").then((product) => {
      items = product;
    });
  });

  it('should purchase items from the store', () => {
    cy.get('.title').should('have.text', 'Products'); // Checking if I'm in the Products page

    // adding the items to the cart
    cy.addProductsToCart();
    cy.get('#shopping_cart_container').click();

    // checking the products I added
    cy.get('div[class="inventory_item_name"]')
      .then(($item) => {
        return (Cypress.$.makeArray($item)).map((el) => el.innerText)
      })
      .should('deep.equal', items);

    cy.get('#checkout').click();
    cy.checkoutForm('Eduardo', 'Videgaray', '89000');
    cy.get('#finish').click();
    cy.get('.complete-header').should('have.text', 'THANK YOU FOR YOUR ORDER')
  });
});
I decided to move the logic of checking if the products are already in the cart into a command. I did the following:
Copy code
typescript
Cypress.Commands.add('validateProductsInCart', () => {
    let items: object;

    cy.fixture("products").then((product) => {
        items = product;
    });

    cy.get('div[class="inventory_item_name"]').then(($item) => {
        return (Cypress.$.makeArray($item)).map((el) => el.innerText)
    }).should('deep.equal', items);

    cy.get('#checkout').click();
});
but when I used that approach, my
items
variable came
undefined
. I put a log inside the fixture, just to see if my data was retrieved, and indeed: the data was retrieved. I put a log outside the fixture, and the
items
variable came
undefined
. I don't know why. The I tried using a
cy.wrap()
inside the fixture, but I got the same behaviour (
undefined
). (cont.)
Copy code
typescript
cy.fixture("products").then((product) => {
        cy.wrap(product).as('product');
    });
    cy.get('div[class="inventory_item_name"]').then(($item) => {
        return (Cypress.$.makeArray($item)).map((el) => el.innerText)
    }).should('deep.equal', this.product);
Then I followed this approach:
Copy code
typescript
Cypress.Commands.add('validateProductsInCart', () => {
    cy.fixture("products").then((product) => {
        cy.get('div[class="inventory_item_name"]').then(($item) => {
            return (Cypress.$.makeArray($item)).map((el) => el.innerText)
        }).should('deep.equal', product);
    });

    cy.get('#checkout').click();
});
and worked. My questions are: Why was my variable undefined if I stored the data in it? Why if I used
cy.wrap()
the variable behaved like in the first scenario? If the problem is related to scope, why was in my spec file working fine? (because in there I was declaring the variable as global, I used the variable in a
beforeEach()
hook to save the data from the fixture, and then I used the variable in my test)
h
My intuition is that
items
inside
.should('deep.equal', items)
is read before it's assigned inside
cy.fixture().then()
. And if you are trying to log
items
after
cy.fixture()
, then it's also undefined, because
.then()
is executed in async manner. Try to put more `console.log`s with numeration, so you will know the exact order of execution of each command there
g
m
So the reason of my
undefined
value is because the data only exists inside the scope where is called. Is there a way to store the data and then use it into a different scope?
g
sure. Can you provide a complete spec (remove all private information) that shows how you get the item and how you use it? Since you have a bunch of fragments above, it is hard to say what is your actual spec
m
My original test:
Copy code
typescript
describe('Shopping articles in the SWAG Labs store', () => {

  let items;
  let items: object;

  beforeEach(() => {
    cy.visit('https://www.saucedemo.com/');
    cy.loginForm(Cypress.env('valid_user').username, Cypress.env('valid_user').password);
    cy.fixture("products").then((product) => {
      items = product;
    });
  });
  it('should purchase items from the store', () => {
    cy.get('.title').should('have.text', 'Products'); // Checking if I'm in the Products page
    // adding the items to the cart
    cy.addProductsToCart();
    cy.get('#shopping_cart_container').click();
    // checking the products I added
    cy.get('div[class="inventory_item_name"]')
      .then(($item) => {
        return (Cypress.$.makeArray($item)).map((el) => el.innerText)
      })
      .should('deep.equal', items);
    cy.get('#checkout').click();
    cy.checkoutForm('Eduardo', 'Videgaray', '89000');
    cy.get('#finish').click();
    cy.get('.complete-header').should('have.text', 'THANK YOU FOR YOUR ORDER')
  });
});
My refactored test:
Copy code
typescript
describe('Shopping articles in the SWAG Labs store', () => {

  beforeEach(() => {
    cy.visit('https://www.saucedemo.com/');
    cy.loginForm(Cypress.env('valid_user').username, Cypress.env('valid_user').password);
  });

  it('should purchase items from the store', () => {
    cy.addProductsToCart();
    cy.validateProductsInCart();
    cy.checkoutForm('Eduardo', 'Videgaray', '89000');
    
    cy.get('#finish').click();
    cy.get('.complete-header').should('have.text', 'THANK YOU FOR YOUR ORDER')
  });
});
command.-
Copy code
typescript
Cypress.Commands.add('validateProductsInCart', () => {
    cy.fixture("products").then((product) => {
        cy.get('div[class="inventory_item_name"]').then(($item) => {
            return (Cypress.$.makeArray($item)).map((el) => el.innerText)
        }).should('deep.equal', product);
    });

    cy.get('#checkout').click();
});
the things I attempted to do.-
Copy code
typescript
// first approach
cy.fixture("products").then((product) => {
        cy.wrap(product).as('product');
    });
    cy.get('div[class="inventory_item_name"]').then(($item) => {
        return (Cypress.$.makeArray($item)).map((el) => el.innerText)
    }).should('deep.equal', this.product);

// second approach
Cypress.Commands.add('validateProductsInCart', () => {
    let items: object;

    cy.fixture("products").then((product) => {
        items = product;
    });

    cy.get('div[class="inventory_item_name"]').then(($item) => {
        return (Cypress.$.makeArray($item)).map((el) => el.innerText)
    }).should('deep.equal', items);

    cy.get('#checkout').click();
});
g
and you goal just to confirm is to add a few items to the cart, then confirm and check out?
m
ajam
l
> Is there a way to store the data and then use it into a different scope? I am not an expert programmer, but I do know of some options. 1. Use an alias. Not advisable in your case, because your working example is cleaner. These work a bit like global variables, and sometimes using them can cause more clutter than just doing the nesting. But they do get the job done. Just be aware that, as of Cypress 12, they will be reevaluated when you access them unless you specifically tell them not to by providing
{ type: 'static' }
to the
.as()
command. 2. Use a global variable. (Again, don't do this for your example.) Sometimes I piggyback off the Cypress object, haha. I'm sure there's good reasons not to do this. 3. If you plan on following this pattern a lot in your code, you can come up with custom commands with
prevSubject
set to
true
and then come up with nice ways of chaining them. In my own case, I have a
cy.newUser()
custom command that yields a
user
object. If I want to get at the email address or the customer ID, I have little custom commands I created for those too. They work like this:
Copy code
js
cy.newUser().email().then(email => doSomethingWith(email))
// or...
cy.newUser().customerId().then(customerId => doSomethingWith(customerId))
m
I used an alias and a global variable, but didn't work
3 Views