Refactor Cypress Modal Tests

An example refactoring a few random alert and modal popup tests.

Recently I noticed a video in a Linkedin post showing a few Cypress tests. The tests confirmed an alert is happening with right text, plus a modal was showing the right text. Here is a screenshot of the original post.

The original tests video

I noticed a few things about those tests that might be improved. Here is my refactoring of each of the three tests.

🎁 You can find the original source code in my repo bahmutov/webdriverSite. You can see the final solution in the branch solution. 📺 You can watch a recording of me refactoring all tests in the video Refactor Cypress Tests Checking Alerts And Modals.

Test one

The first test confirms the application calls the alert(...) with the right text.

1
2
3
4
5
6
7
8
9
10
11
12
describe('Lidando com Popups e Alerts', () => {
beforeEach(() => {
cy.visit('/Popup-Alerts/index.html')
})

it('Javascript Alert', () => {
cy.get('#button1').click()
cy.on('window:alert', (txt) => {
expect(txt).to.contains('I am an alert box!')
})
})
})

Above the test I added my notes about what could be improved in this test. We never actually confirm that the application calls alert(...). You could comment out the .click() and the test would still work. Here is the improved test that uses cy.stub command to confirm the app does call alert with the right text argument.

1
2
3
4
5
6
7
8
// need to ensure the alert actually happens
it('Javascript Alert', () => {
cy.get('#button1').click()

const text = 'I am an alert box!'
cy.on('window:alert', cy.stub().as('alert'))
cy.get('@alert', { timeout: 1000 }).should('have.been.calledOnceWith', text)
})

The first test refactored

For more stub examples and checking the alert behavior, see my stubs examples page.

Test two

1
2
3
4
5
6
7
8
9
10
11
12
13
14
it('Modal Popup', () => {
cy.get('#button2').click()
// Removendo espaços em branco do início e do final da string
cy.get('.modal-body')
.invoke('text')
.then((text) => {
const txtEsperado =
'We can inject and use JavaScript code if all else fails! ...'
const txtNaPagina = text.trim()

expect(txtNaPagina).to.equal(txtEsperado)
})
cy.get('.modal-content').find('.modal-footer').contains('button', 'Close').click()
})

We can take advantage of cy.contains command to find elements by text. We should also confirm the modal disappears after clicking the "Close" button.

1
2
3
4
5
6
7
8
9
10
11
// refactor to use cy.contains command
// and confirm the dialog disappears when closed
it('Modal Popup', () => {
cy.get('#button2').click()
// Removendo espaços em branco do início e do final da string
const txtEsperado =
'We can inject and use JavaScript code if all else fails! ...'
cy.contains('.modal-body', txtEsperado).should('be.visible')
cy.get('.modal-content').find('.modal-footer').contains('Close').click()
cy.get('.modal-body').should('not.be.visible')
})

The second test refactored

Test three

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
it('Outra maneira para o Modal Popup', () => {
cy.intercept('/**').as('googleAnalytics')
cy.get('p > a').click()
cy.get('.loader').should('not.exist')
cy.wait('@googleAnalytics')
cy.get('#button1 > p').click()
cy.get('.modal-body')
.invoke('text')
.then((text) => {
const txtEsperado =
'The waiting game can be a tricky one; ...'
const txtNaPagina = text.trim()

expect(txtNaPagina).to.equal(txtEsperado)
})
cy.contains('Close').click()
})

In the test above we are spying on the network requests, but the spy is too permissive. We can limit it to only spy on the calls to the Google Analytics website. To learn more about network intercepts, see my course Cypress Network Testing Exercises

We can also fix several selectors to be precise. We also need to better check the behavior of the Loader element that first must appear and the go away.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// use a better selector for to click the ajax loader
// confirm the loader is visible before checking that it does not exist
// use precise network matcher for google analytics, otherwise everything matches
// use cy.contains command
// use precise selector for the close button
// confirm the modal does not exist after closing it
it('Outra maneira para o Modal Popup', () => {
cy.intercept({
hostname: 'www.google-analytics.com',
}).as('googleAnalytics')
cy.contains('.thumbnail', 'Ajax Loader').find('a').click()
cy.get('#loader').should('be.visible')
cy.get('#loader').should('not.be.visible')
cy.wait('@googleAnalytics')
cy.contains('#button1', 'CLICK ME!').click()
const txtEsperado =
'The waiting game can be a tricky one; ...'
cy.contains('.modal-body', txtEsperado).should('be.visible').wait(1000)
cy.contains('button', 'Close').click()
cy.contains('.modal-body').should('not.exist')
})

The third test refactored

For more details, read the blog posts Be Careful With Negative Assertions and Negative Assertions And Missing States. To learn more about testing different application states and confirming the application behaviors, see my course Testing The Swag Store

Tip: have a complex Cypress test you are unhappy with? Is it public? If yes, send it my way, and maybe I will record a video refactoring it to make the spec elegant, precise, and solid.