DEV Community

Cover image for Cypress Tip: Don't Overuse the Visibility Assertion
Mike Cataldo
Mike Cataldo

Posted on • Originally published at iheartjs.dev

Cypress Tip: Don't Overuse the Visibility Assertion

Cypress makes assertions easy with the should command and an intuitive way to leverage the ubiquitous Chai assertion library.

Let's say, for example, that our application is the Real World App and we need to assert that upon navigating to Home the avatar at the top left is visible. We could write the test like this:

describe('Home', () => {
  before('sign in and navigate to Home', () => {
    // ...
  })
  it('see avatar', () => {
    cy.get('#avatar')
      .should('be.visible')
  })
})
Enter fullscreen mode Exit fullscreen mode

This is 🔥 fine, but on multiple occasions, I've seen an overuse of this visibility assertion. For example, when filling out a form field, you might assert visibility between the query and the action like this:

describe('Sign In', () => {
  before('navigate to Sign In', () => {
    // ...
  })
  it('sign in', () => {
    cy.get('#username')
      .should('be.visible')
      .type('iheartjs')
    cy.get('#password')
      .should('be.visible')
      .type('password')
    cy.get('button#sign-in')
      .should('be.visible')
      .click()
    // ...
  })
})
Enter fullscreen mode Exit fullscreen mode

It makes sense why we might do this: to avoid interacting with an element before it's visible. However, this is usually unnecessary and could be considered bad practice.

First, it's unnecessary because of two implicit behaviors of Cypress: actionability assertions and command retry-ability. Cypress will not attempt to perform certain actions on an element unless it's visible. If it isn't visible, Cypress repeatedly retries this assertion until either the assertion passes and the next command is executed or the timeout is reached and it fails.
Now the test can be written this way:

describe('Sign In', () => {
  before('navigate to Sign In', () => {
    // ...
  })
  it('sign in', () => {
    cy.get('#username')
      .type('iheartjs')
    cy.get('#password')
      .type('password')
    cy.get('button#sign-in')
      .click()
    // ...
  })
})
Enter fullscreen mode Exit fullscreen mode

Not only is there less code to manage, but now there's less noise in the Command Log!

To be clear, we're assuming that the example is not a component test; and while we want to ensure that a user is able to sign in by filling in the fields and clicking a button, we have other tests to ensure form components work as intended. In that vein, it could also be considered bad practice to assert visibility even if it were necessary for a working test.
The alternative? Assert visibility using the :visible selector (jQuery's extension to native CSS pseudo-classes).

When you run the iteration below, Cypress will only perform the action on the element until it's visible.

describe('Sign In', () => {
  before('navigate to Sign In', () => {
    // ...
  })
  it('sign in', () => {
    cy.get('#username:visible')
      .type('iheartjs')
    cy.get('#password:visible')
      .type('password')
    cy.get('button#sign-in:visible')
      .click()
    // ...
  })
})
Enter fullscreen mode Exit fullscreen mode

While this isn't necessary in cases like this, in the real world, you might run into a situation where you need to make the assertion solely for the purpose of test resiliency or debuggability.

In addition to visibility, there are several other default assertions - like ensuring the element is not disabled - that Cypress makes to determine actionability. Check out the Cypress docs for more in-depth content.

Top comments (0)