Detect page reload from Cypress test

How to detect from Cypress test when a page reloads using object property assertions.

Cypress Test Runner has built-in retry-ability that is so sweet. Instead of hard-coding waits into your tests, all you need is figure out how to query something and how to follow with an assertion. The test will keep querying until the assertion passes.

Do you need to detect when a new property gets added to an object? Wrap the object and assert that the new property is preset.

1
2
3
4
5
6
7
8
// an object without a property
const o = {}
// property "id" gets added after a delay
setTimeout(() => {
o.id = 'abc123'
}, 500)
// detects when property "id" get added to the object "o"
cy.wrap(o).should('have.property', 'id')

Notice how the test continues after 500ms - when the property gets added and assertion should('have.property', 'id') finally passes.

Should have property test passes

What if you want to detect when the property has a specific value and is not just present on an object? Sure, just add the value you expect to see to the assertion.

1
2
3
4
5
6
7
8
9
10
// an object with an id
const o = {
id: 'initial'
}
// set "o.id" after delay
setTimeout(() => {
o.id = 'abc123'
}, 500)
// detects property "o.id" has specific value
cy.wrap(o).should('have.property', 'id', 'abc123')

Notice in the video below how the wrapped object shows "{id: initial}" in the assertion and then switches to "{id: abc123}" - because the object is "live" - Cypress re-evaluates the assertion over and over until it passes or times out.

Should have property with give value test

What about adding and deleting a property on the window object? Let's do this! Let's add a property and delete property.

1
2
3
4
5
6
7
8
9
10
// asynchronously add and delete a property
setTimeout(() => {
window.customProp = 'here'
}, 1000)
setTimeout(() => {
delete window.customProp
}, 2000)

cy.window().should('have.property', 'customProp')
cy.window().should('not.have.property', 'customProp')

Property added and deleted

Notice in the above example that we have to use 2 cy.window() commands, with a single assertion each. You cannot attach both assertions to a single cy.window(). To understand why, notice what the Command Log shows. So here is the test with single command and 2 assertions:

1
2
3
cy.window()
.should('have.property', 'customProp')
.should('not.have.property', 'customProp')

The test finishes suspiciously quickly - in just 1 second, while the asynchronous code executes in 2 seconds. So we know something is wrong, and look at the assertions; it gives you a clue.

Command log shows the problem

The second assertion tries to assert that a string "here" does not have property "customProp". Of course it does not, so it immediately passes, and the test completes.

Assertion should('have.property') is one of those few assertions that change the subject. In essence, it asserts that window has property customProp and then yields window.customProp to the next assertion, fooling you into false confidence.

The solution is to grab the window again and run the second assertion on it.

1
2
cy.window().should('have.property', 'customProp')
cy.window().should('not.have.property', 'customProp')

So how does this relate to detecting when the page reloads? If there is no other external effect after reloading the page: no url change, no DOM change, etc, then you should property assertions. Here is an example page that reloads in response to a click

1
2
3
4
5
6
7
8
9
10
11
12
<body>
<button id="button">Click to reload</button>
<script>
document.getElementById('button').addEventListener('click', () => {
console.log('clicked')
setTimeout(() => {
console.log('reloading page after delay')
location.reload()
}, 2000)
})
</script>
</body>

Here is my test - and this detects when the page reloads just fine.

1
2
3
4
5
6
7
8
9
10
it('reloads', () => {
cy.visit('index.html')
// mark our window object to "know" when it gets reloaded
cy.window().then(w => w.beforeReload = true)
// initially the new property is there
cy.window().should('have.prop', 'beforeReload', true)
cy.get('#button').click()
// after reload the property should be gone
cy.window().should('not.have.prop', 'beforeReload')
})

Using custom property to detect page reload

See also