Multiple aliases

Remove pyramid of callbacks

Let's get several elements at once using aliases and this properties.

<style>
  .dollars::before {
    content: '$';
    margin-right: 2px;
  }
  .dollars {
    width: 6rem;
    text-align: right;
  }
</style>
<div id="subtotal" class="dollars">20.15</div>
<div id="tax" class="dollars">1.70</div>
<div id="tip" class="dollars">3.00</div>
<div id="total" class="dollars">24.85</div>

We could use multiple .then callbacks to get each value.

cy.get('#subtotal')
  .invoke('text')
  .then(parseFloat)
  .then((subtotal) => {
    cy.get('#tax')
      .invoke('text')
      .then(parseFloat)
      .then((tax) => {
        cy.get('#tip')
          .invoke('text')
          .then(parseFloat)
          .then((tip) => {
            // get the total and compare
          })
      })
  })

But a much better approach is to save the values as Cypress aliases using cy.as commands, and then use a single function callback to get all values via this object.

cy.get('#subtotal')
  .invoke('text')
  .then(parseFloat)
  .as('subtotal')
cy.get('#tax').invoke('text').then(parseFloat).as('tax')
cy.get('#tip').invoke('text').then(parseFloat).as('tip')
cy.get('#total')
  .invoke('text')
  .then(parseFloat)
  .as('total')
  // we need to use the "function () {...}" callback
  // to have the "this" object point at the text context
  // where each set alias is a property
  // for example: .as('tax') can be accessed via "this.tax"
  .then(function () {
    expect(this.total, 'total').to.be.closeTo(
      this.subtotal + this.tax + this.tip,
      0.01,
    )
  })

See the above test explained in the video Use Multiple Aliases To Avoid Pyramid of Doom Callbacksopen in new window.

Element is delayed

<div id="element-delayed"></div>
<script>
  setTimeout(function () {
    const parent = document.getElementById('element-delayed')
    parent.innerHTML = '<div id="child">New element</div>'
  }, 3000)
</script>
// while we create an alias to a non-existing element
// it really is an empty alias
cy.get('#child').should('not.exist').as('newElement')
cy.get('@newElement').should('not.exist')
// and this command chain fails with "null" subject
cy.get('@newElement').should('be.visible')
// instead, overwrite the alias when the element is created
cy.get('#child').should('not.exist').as('newElement')
cy.get('@newElement').should('not.exist')
// at some point element with id "child" exists
// and will be saved as the alias, overwriting the null value
cy.get('#child').as('newElement')
cy.get('@newElement').should('be.visible')