Click Button If Enabled

The same conditional test implemented using plain Cypress syntax, "cypress-if", and "cypress-await" plugins.

Imagine you have a button on the page. This button sometimes is enabled and sometimes is disabled. The test does not control the button (that would be too simple). The test must click the button only if it is enabled. How would you write this Cypress test?

The button can be disabled

Conditional testing is an anti-pattern, but Cypress can do it. Here is how to solve this problem in several ways.

Plain Cypress syntax

📺 Watch the first solution explained in the video Check If A Button Is Enabled Before Clicking.

Let's use plain Cypress command. We first get the button, then check its attribute inside cy.then(callback). We can schedule more commands in the callback function.

1
2
3
4
5
6
7
8
cy.contains('#btn', 'Click Me')
.then(($btn) => {
if ($btn.attr('disabled')) {
cy.log('Nothing to click')
} else {
cy.wrap($btn).click().should('have.text', 'Clicked')
}
})

Remember: any time you get something from the app, you must pass it forward to the next command, assertion, or cy.then(callback).

If the button is enabled, the test clicks and checks the updated text.

The test clicked on the enabled button

If the button happened to be disabled, the test simply logged a message and finished.

The test did not click on the disabled button

We can rewrite the test to use jQuery is method to check if the button is enabled using the jQuery pseudo-selector :enabled.

1
2
3
4
5
6
7
8
9
10
11
cy.contains('#btn', 'Click Me')
.invoke('is', ':enabled')
.then((enabled) => {
if (enabled) {
cy.contains('#btn', 'Click Me')
.click()
.should('have.text', 'Clicked')
} else {
cy.log('**not clicking**')
}
})

I like this syntax because it makes it clear what we are getting at; we are checking if the button is enabled. This is better than "hiding" the effects in the cy.then(callback) via $btn.attr('disabled') or even $btn.is(':enabled') which are invisible to Cypress Command Log. If we are using cy.invoke command, we can click on it to see what it produced.

Debugging the cy.invoke is command

You can find the above examples under recipes on my Cypress examples site.

cypress-if solution

📺 Watch the next two solutions shown in the video Click Button If Enabled Using cypress-if And cypress-await Plugins.

I have introduced my plugin cypress-if in the blog post Conditional Commands For Cypress. It lets you do conditional "if / else" chains of commands based on the current subject. Here is how the above solution looks when using cypress-if. The commands cy.if and cy.else are from the cypress-if plugin

1
2
3
4
5
6
7
8
9
10
11
12
// https://github.com/bahmutov/cypress-if
import 'cypress-if'

it('clicks the button if enabled', () => {
cy.visit('cypress/enabled-button.html')
cy.contains('button', 'Click Me')
// tests using Chai assertion "be.enabled"
.if('enabled')
.click()
.should('have.text', 'Clicked')
.else('Nothing to click')
})

The button was enabled

When the current subject passes the jQuery assertion if('enabled'), the logic takes the "IF" command chain path, executing all commands between cy.if and cy.else. Let's see how it behaves when the button is disabled.

The button was disabled

So, so easy. Tip: want to run more commands? Just stick cy.then(callback) inside the if / else chain:

1
2
3
4
5
6
7
8
9
cy.contains('button', 'Click Me')
.if('enabled')
.then($btn => {
cy.log('Clicking the button')
cy.wrap($btn).click()
cy.contains('button', 'Clicked')
.should('be.visible')
})
.else('Nothing to click')

I really enjoy using cypress-if, to be honest.

cypress-await solution

If you don't like working with the Cypress subjects and chains, I have cypress-await plugin for you. It sets up a spec file preprocessor that rewrites specs:

1
2
3
4
5
6
7
8
9
10
11
// in your spec
const value = cy.get(...).should(...).find(...).invoke(...)
// => use value
// becomes
cy.get(...)
.should(...)
.find(...)
.invoke(...)
.then(value => {
// => use value
})

It looks synchronous, but under the hood all Cypress retries are still executing for each chain before assigning the value. Our conditional test can be rewritten like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
it(
'clicks on the button if it is enabled',
() => {
cy.visit('cypress/fixtures/enabled-button.html')
const enabled = cy.contains('button', 'Click Me').invoke('is', ':enabled')
cy.log(`enabled? ${enabled}`)
if (enabled) {
cy.contains('button', 'Click Me').click().should('have.text', 'Clicked')
} else {
cy.log('Nothing to click')
}
},
)

The conditional test using cypress-await

You can find more lessons on how to use cypress-if and cypress-await in my Cypress Plugins course.