Parse price

Utility function

Let's say that in the element's text we have a price that we need to find and extract as a number. The price could be simple $1.99 or could have - sign in front of it discount coupon -$2.12 applied.

Watch explanation for this recipe in the video How To Unit Test Find And Parse The Price Function That Needs A jQuery Objectopen in new window.

function parsePrice($el) {
  const text = $el.text()
  const matched = text.match(/(?<price>-?\$\d+\.\d{2})/)
  const priceText = Cypress._.get(matched, 'groups.price')
  if (!priceText) {
    return NaN
  }
  return parseFloat(priceText.replace('$', ''))
}

A convenient way to test multiple DOM elements is to construct them on the fly using Cypress.$(html) and cy.wrapopen in new window command.

cy.wrap(Cypress.$('<p>$1.99</p>'))
  .then(parsePrice)
  .should('equal', 1.99)
cy.wrap(Cypress.$('<p>-$1.99</p>'))
  .then(parsePrice)
  .should('equal', -1.99)
cy.wrap(Cypress.$('<p>Final price $10.99</p>'))
  .then(parsePrice)
  .should('equal', 10.99)
cy.wrap(Cypress.$('<p>Total discount -$10.99 applied</p>'))
  .then(parsePrice)
  .should('equal', -10.99)

Testing cases without a price

cy.wrap(Cypress.$('<p>loading...</p>'))
  .then(parsePrice)
  .should('be.a.NaN')
cy.wrap(Cypress.$('<p>20</p>'))
  .then(parsePrice)
  .should('be.a.NaN')
cy.wrap(Cypress.$('<p>$20.1</p>'))
  .then(parsePrice)
  .should('be.a.NaN')

Fluent chain

You can rewrite the above command to be a fluent chain of queries (except for cy.then step).

cy.wrap(Cypress.$('<p>Final price $10.99</p>'))
  .invoke('text')
  .invoke('match', /(?<price>-?\$\d+\.\d{2})/)
  .its('groups.price')
  .invoke('replace', '$', '')
  .then(parseFloat)
  .should('equal', 10.99)

Tip: instead of cy.then(parseFloat) use cy.apply(parseFloat) from cypress-mapopen in new window to preserve retry-ability of the entire query chain.

Reusable query

Let's move our utility function into parsePrice query that we can call on demand.

Cypress.Commands.addQuery('parsePrice', () => {
  return (subject) => {
    const text = subject.text()
    const matched = text.match(/(?<price>-?\$\d+\.\d{2})/)
    const priceText = Cypress._.get(matched, 'groups.price')
    if (!priceText) {
      return NaN
    }
    return parseFloat(priceText.replace('$', ''))
  }
})

Compared to cy.then(parsePrice), the query approach can retry parsing the price while the element is updated.

const $el = Cypress.$('<p>Final price --</p>')
setTimeout(() => {
  $el.text('Final price $10.99')
}, 1000)
cy.wrap($el).parsePrice().should('equal', 10.99)