Conditional testing

Conditional testing is strongly discouragedopen in new window in Cypress. But if you must do something conditionally in Cypress depending on the page, here are few examples.

Toggle checkbox

Let's say that you have a checkbox when the page loads. Sometimes the checkbox is checked, sometimes not. How do you toggle the checkbox?

<div>
  <input type="checkbox" id="done" />
  <label for="done">Toggle me!</label>
</div>
<script>
  // sometimes the checkbox is checked
  const dice = Math.random()
  if (dice < 0.5) {
    document.querySelector('input').checked = true
  }
</script>
// first solution using jQuery $.prop
cy.get('input').then(($checkbox) => {
  const initial = Boolean($checkbox.prop('checked'))
  // let's toggle
  cy.log(`Initial checkbox: **${initial}**`)
  // toggle the property "checked"
  $checkbox.prop('checked', !initial)
})

Alternative solution

<div>
  <input type="checkbox" id="done" />
  <label for="done">Toggle me!</label>
</div>
<script>
  // sometimes the checkbox is checked
  const dice = Math.random()
  if (dice < 0.5) {
    document.querySelector('input').checked = true
  }
</script>
cy.get('input')
  .as('checkbox')
  .invoke('is', ':checked') // use jQuery $.is(...)
  .then((initial) => {
    cy.log(`Initial checkbox: **${initial}**`)
    if (initial) {
      cy.get('@checkbox').uncheck()
    } else {
      cy.get('@checkbox').check()
    }
  })

Element does not exist or is invisible

Imagine we want to pass the test if the element does not exist or is invisible. We will use Cypress.dom utility methods and retry the assertion using .should(cb) method.

<div id="either">
  <div id="disappears">Should go away</div>
  <div id="hides">Should not see me</div>
</div>
<script>
  setTimeout(() => {
    const disappears = document.getElementById('disappears')
    disappears.parentNode.removeChild(disappears)
  }, 500)

  setTimeout(() => {
    const hides = document.getElementById('hides')
    hides.style.display = 'none'
  }, 1000)
</script>
const isNonExistentOrHidden = ($el) =>
  !Cypress.dom.isElement($el) || !Cypress.dom.isVisible($el)

// let's assert an element does not exist
cy.get('#either #disappears').should(($el) => {
  expect(isNonExistentOrHidden($el)).to.be.true
})
// let's assert an element becomes invisible
cy.get('#either #hides').should(($el) => {
  expect(isNonExistentOrHidden($el)).to.be.true
})

Click a button if present

Let's say that we want to click a button if it is present on the page. We need to avoid triggering the built-in existence assertion in the cy.get or cy.contains commands, and click the button only after checking it ourselves.

<div>
  <p>The button might appear here</p>
  <div id="output"></div>
</div>
<script>
  if (Math.random() < 0.5) {
    const output = document.getElementById('output')
    const btn = document.createElement('button')
    btn.innerHTML = 'Click Me'
    output.appendChild(btn)
    btn.addEventListener('click', () => {
      console.log('Clicked')
    })
  }
</script>
// get the button but disable the built-in cy.contains assertions
// by appending our own dummy .should() assertion
cy.contains('button', 'Click Me')
  .should((_) => {})
  .then(($button) => {
    if (!$button.length) {
      // there is no button
      cy.log('there is no button')
      return
    } else {
      cy.window().then((win) => {
        cy.spy(win.console, 'log').as('log')
      })
      cy.wrap($button).click()
      cy.get('@log').should(
        'have.been.calledOnceWith',
        'Clicked',
      )
    }
  })

Click a button if not disabled

Let's say that we want to click a button if it not disabled. Otherwise, just print a message to the console.log

<div>
  <p>
    The button might be disabled
    <button id="btn">Click Me</button>
  </p>
</div>
<script>
  const btn = document.getElementById('btn')
  btn.addEventListener('click', function () {
    alert('Clicked')
  })

  if (Math.random() < 0.5) {
    btn.setAttribute('disabled', 'disabled')
  }
</script>

Let's prepare a stub around window.alert method.

// spy on the "window.alert"
cy.window().then((win) => {
  cy.stub(win, 'alert').as('alert')
})
cy.contains('#btn', 'Click Me')
  // cy.contains has a built-in "existence" assertion
  // thus by now we know the button is there
  .then(($btn) => {
    if ($btn.attr('disabled')) {
      console.log('Cannot click a disabled button')
    } else {
      cy.wrap($btn).click()
      cy.get('@alert')
        .should('have.been.calledOnce')
        // we can immediately reset the stub call history
        .invoke('resetHistory')
    }
  })

You can even use the jQuery helper method isopen in new window to check if the button is currently disabled.

cy.contains('#btn', 'Click Me').then(($btn) => {
  if ($btn.is(':disabled')) {
    cy.log('Button is disabled')
  } else {
    cy.log('Can click it')
  }
})

jQuery has :enabled check too which you can use. It is the opposite of :disabled check.

cy.contains('#btn', 'Click Me').then(($btn) => {
  if ($btn.is(':enabled')) {
    cy.log('Clicking...')
    cy.wrap($btn).click()
    cy.get('@alert').should('have.been.calledOnce')
  } else {
    cy.log('Button is disabled')
  }
})

Click a button if a class is present

Sometimes you want to click the button, but only if the element has a class

<button id="first" class="urgent important" disabled="disabled">
  Click NOW
</button>
<script>
  // notice that at first the button is disabled
  // and we need to use the built-in action checks in cy.click
  const first = document.getElementById('first')
  first.addEventListener('click', () => {
    console.log('first click')
  })
  // remove the disabled property
  setTimeout(() => {
    first.disabled = false
  }, 1000)
</script>
// spy on the console.log to make sure it is called
cy.window()
  .its('console')
  .then((console) => cy.spy(console, 'log').as('log'))

cy.get('#first').then(($first) => {
  if ($first.hasClass('important')) {
    // note that we cannot simply use jQuery .click() method
    // since it won't wait for the button to be enabled
    // $first.click()
    // instead we wrap the element and use the cy.click() command
    cy.wrap($first).click()
  }
})
// confirm the button was called correctly
cy.get('@log').should('be.calledOnceWith', 'first click')

Getting a cookie using cy.getCookieopen in new window command does not retry, thus you can simply work with the yielded value.

function printCookieMaybe(cookie) {
  if (cookie) {
    cy.log(`Found the cookie with value: ${cookie.value}`)
  } else {
    cy.log('No cookie for you')
  }
}
cy.getCookie('my-cookie').then(printCookieMaybe)
cy.setCookie('my-cookie', 'nice')
cy.getCookie('my-cookie').then(printCookieMaybe)

Perform different actions depending on the URL

As always, when getting something from the page, you get its value in the .then(callback). If you get the current URL using cy.locationopen in new window or cy.urlopen in new window, you can decide what to do next based on its value:

cy.location('pathname').then((pathname) => {
  if (pathname.includes('/about/')) {
    cy.log('At the About page')
  } else {
    cy.log('Another page')
  }
})

Skip the rest of the test if an element exists

<output id="app" />
<script>
  // in half of the cases, the element will be there
  if (Math.random() < 0.5) {
    document.getElementById('app').innerHTML = `
      <div data-dynamic="true">Dynamic</div>
    `
  }
</script>

Our test first checks the element with id "app". If it has at that moment a child with text "Dynamic", then we confirm that element has an attribute "data-dynamic=true". If the #app element does not have a child element with text "Dynamic" then we stop the test by not executing any more Cypress commands

// using the CSS :has selector to find the element
cy.get('#app:has(div)')
  // we don't know if the element exists or not
  // so we bypass the built-in existence assertion
  // using the no-operator should(callback)
  .should(Cypress._.noop)
  .then(($el) => {
    if (!$el.length) {
      cy.log('No element, stopping')
      // note: the test cannot have any more commands
      // _after_ this cy.then command if you really
      // want to stop the test
      return
    }

    // the element exists, let's confirm something about it
    cy.contains('#app div', 'Dynamic').should(
      'have.attr',
      'data-dynamic',
      'true',
    )
    // equivalent assertion without using Cypress chain
    // and just using jQuery and Chai-jQuery combination
    // (no retry-ability, immediate assertion)
    expect($el.find('div'), 'has the attribute').to.have.attr(
      'data-dynamic',
      'true',
    )
  })

Note: we only skip the rest of the test commands inside the callback. If you want to really stop the test at run-time, see the cypress-skip-testopen in new window plugin.

cypress-if

If you MUST use conditional commands in your tests, check out my cypress-ifopen in new window plugin.

import 'cypress-if'

cy.get('#agreed')
  .if('not.checked')
  .click() // IF path
  .else()
  .log('The user already agreed') // ELSE path

Read the blog post Conditional Commands For Cypressopen in new window.

More examples