1

I want to somehow get some price values from the page I am on.
I would like to do with cypress the following:

  1. get the text from the page element,
  2. turn it to a number
  3. then save it on the price JS object
  4. return the price JS object

This is what I did so far:

  getCheckoutPricesBeforeApplyingVoucher() {
    let prices = {
      subtotalPrice: 0,
      totalPrice: 0,
      itemPriceOrderOverview: 0,
      totalPriceOrderOverview: 0,
      totalWithVATOrderOverview: 0
    }
      prices.subtotalPrice = this.getPrice('[data-cy=subtotalPrice]')
      prices.totalPrice = this.getPrice('[data-cy=totalPrice]')
      prices.itemPriceOrderOverview = this.getPrice('[data-cy=itemPriceOrderOverview]');
      prices.totalPriceOrderOverview = this.getPrice('[data-cy=totalPriceOrderOverview]');
      prices.totalWithVATOrderOverview = this.getPrice('[data-cy=totalWithVATOrderReview]');
      cy.log(prices.subtotalPrice);
      cy.log(prices.totalPrice);
      cy.log(prices.itemPriceOrderOverview);
      cy.log(prices.totalPriceOrderOverview);
      cy.log(prices.totalWithVATOrderOverview);
      return prices;
  }

   getPrice(selector) {
    return cy.get(selector)
      .invoke('text')
      .then((text) => {
        return Number(text);
      })
  }

But the values that my prices JS object has are all 0 when written in the console with for example cy.log(prices.subtotalPrice). They are not set to the corresponding values from UI elements.
Do you have an advise how to do this properly? Maybe my whole approach should go in different direction?
Thanks!

[EDIT] I tried this as well, but the result for some reason is still 0 for each value from JS object.

  getCheckoutPricesBeforeApplyingVoucher() {
    let prices = {
      subtotalPrice: 0,
      totalPrice: 0,
      itemPriceOrderOverview: 0,
      totalPriceOrderOverview: 0,
      totalWithVATOrderOverview: 0
    }

    cy.get('[data-cy=subtotalPrice]').invoke('text').as('subtotalPrice');
    cy.get('[data-cy=totalPrice]').invoke('text').as('totalPrice');
    cy.get('[data-cy=itemPriceOrderOverview]').invoke('text').as('itemPriceOrderOverview');
    cy.get('[data-cy=totalPriceOrderOverview]').invoke('text').as('totalPriceOrderOverview');
    cy.get('[data-cy=totalWithVATOrderReview]').invoke('text').as('totalWithVATOrderReview');

    prices.subtotalPrice = this.subtotalPrice;
    prices.subtotalPrice = this.totalPrice;
    prices.subtotalPrice = this.itemPriceOrderOverview;
    prices.subtotalPrice = this.totalPriceOrderOverview;
    prices.subtotalPrice = this.totalWithVATOrderReview;

    cy.log(prices.subtotalPrice);
    cy.log(prices.totalPrice);
    cy.log(prices.itemPriceOrderOverview);
    cy.log(prices.totalPriceOrderOverview);
    cy.log(prices.totalWithVATOrderOverview);
    return prices;
  }
mismas
  • 1,236
  • 5
  • 27
  • 55
  • 1
    Does this answer your question? [How to return the response from an asynchronous call](https://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) – Heretic Monkey Jul 07 '21 at 14:55
  • For Cypress specifically, which uses something that looks like but _isn't_ a promise: https://docs.cypress.io/guides/core-concepts/variables-and-aliases – jonrsharpe Jul 07 '21 at 14:56
  • @jonrsharpe I tried it with aliases but it still doesn't work for some reason. I have updated my question. Can you help? Any ideas? – mismas Jul 07 '21 at 15:15
  • There's a specific example in the docs telling you that won't work, see https://docs.cypress.io/guides/core-concepts/variables-and-aliases#Accessing-Fixtures - _"this.users is not defined because the 'as' command has only been enqueued - it has not run yet"_. – jonrsharpe Jul 07 '21 at 15:34
  • @jonrsharpe aha, sorry I missed that one. So do you have any suggestions how to approach this? I cannot find my way from the link that Heretic Monkey posted. What would it look like when applying Promises and then principle? – mismas Jul 07 '21 at 16:49
  • @jonrsharpe I need to save those values so I can operate with them later on in the code. I don't need to use them right away in then function. Somehow I would like to save them to this JS object. Is that even possible? – mismas Jul 07 '21 at 16:52

2 Answers2

1

Cypress commands like cy.get(...) are asynchronous in order to provide retry for asynchronous elements, for example if the data comes from an API.

Mixing Cypress commands with Page Object methods is problematic, because the methods then have to become asynchronous as well.

If you are absolutely sure that the data is present in the page, you can switch to the synchronous Cypress.$(...) which will directly set your object properties.

getCheckoutPricesBeforeApplyingVoucher() {
  return {
    subtotalPrice: +Cypress.$('[data-cy=subtotalPrice]').text(),
    totalPrice: +Cypress.$('[data-cy=totalPrice]').text(),
    itemPriceOrderOverview: +Cypress.$('[data-cy=itemPriceOrderOverview]').text(),
    totalPriceOrderOverview: +Cypress.$('[data-cy=totalPriceOrderOverview]').text(),
    totalWithVATOrderOverview: +Cypress.$('[data-cy=totalWithVATOrderReview]').text()
  }
}
Ackroydd
  • 1,482
  • 1
  • 13
  • 14
  • Thanks for replying. What approach would you use better? Aliases or synchronous accessor? What would be better practice in your opinion? – mismas Jul 08 '21 at 00:40
  • The alias approach looks too noisy and over-complicated. I can't figure out what the test flow is. – Ackroydd Jul 08 '21 at 01:24
  • Sorry for being pushy, but I want to understand the pros and cons of both approaches. So the readability is apparently alias con. But what are possible pitfalls I could get into if using this synchronous accessor? Why do I need to be absolutely sure that the data is present in the page? Can you please explain a bit :) Thanks! – mismas Jul 08 '21 at 06:19
0

I managed to figure this out using this article

Here it goes - it has a bit of additional code but I think it is ok as an example:

verifyVoucherApplied(voucher) {
    cy.get('[data-cy=textVoucherCode]').should('be.visible').and('have.text', voucher.CHF30.code);
    cy.get('[data-cy=textVoucherValue]').should('be.visible').and('have.text', voucher.CHF30.value);

    this.checkDiscountedPrice('subtotalPrice', voucher.CHF30.value);
    this.checkDiscountedPrice('totalPrice', voucher.CHF30.value);

    cy.get('@itemPriceOrderOverview').then(itemPriceOrderOverviewSaved => {
      cy.get('[data-cy=itemPriceOrderOverview]').invoke('text')
        .then(itemPriceOrderOverview => {
          expect(itemPriceOrderOverview).to.eq(Number(itemPriceOrderOverviewSaved).toFixed(2));
        });
    });

    cy.get('[data-cy=discountOrderOverview]').invoke('text')
      .then(discountOrderOverview => {
        expect(discountOrderOverview.replace(/\s/g, '')).to.eq(voucher.CHF30.value);
      });

    this.checkDiscountedPrice('totalPriceOrderOverview', voucher.CHF30.value);
    this.checkDiscountedPrice('totalWithVATOrderReview', voucher.CHF30.value);
  }

  saveCheckoutPricesBeforeApplyingVoucher() {
    this.saveTextAsNumber('subtotalPrice');
    this.saveTextAsNumber('totalPrice');
    this.saveTextAsNumber('itemPriceOrderOverview');
    this.saveTextAsNumber('totalPriceOrderOverview');
    this.saveTextAsNumber('totalWithVATOrderReview');
  }
  saveTextAsNumber(selector) {
    cy.get(`[data-cy=${selector}]`).invoke('text')
      .then(text => {
        cy.wrap(Number(text)).as(`${selector}`);
      });
  }
  checkDiscountedPrice(selector, discount) {
    cy.get('@' + selector).then(priceBeforeApplyingDiscount => {
      let priceDiscounted = this.applyFixedDiscount(priceBeforeApplyingDiscount, Number(discount));
      cy.get(`[data-cy=${selector}]`).invoke('text')
        .then(price => {
          expect(price).to.eq(priceDiscounted);
        });
    });
  }
  applyFixedDiscount(price, discount) {
    return Number(price + discount).toFixed(2);
  }
mismas
  • 1,236
  • 5
  • 27
  • 55
  • ".as(`${selector}`)" can be ".as(selector)", you are not templating anything there. –  Jul 08 '21 at 00:03
  • Also, you might as well use the synchronous accessor. –  Jul 08 '21 at 00:06