Pass The Reference

How to write the correct instanceof assertions in Cypress tests.

Imagine your code is using a lot of instanceof checks. When running Cypress tests, you might want to also check if a given object is an instance of a given constructor function. The problem is, your specs and the application run in different iframes. Thus a constructor might have the same name (let's say Alert), but an instance of Alert from the spec's iframe window is different from an instance of the application's iframe window. Watch my video below for the introduction to this problem and one possible solution

📝 For more, read the the recipe "Be very careful with instanceof assertion" from my Cypress examples site.

In this blog post, I will show a general way of how to avoid this problem if you are making objects yourself from the spec. Let's say we have the following statements in the application and in the Cypress spec files:

1
2
3
4
5
6
7
8
9
10
11
12
13
// application code in "src"
import Alert from './Alert'
function isAlert(a) {
// uses the imported "Alert" function
return a instanceof Alert
}
...
// cypress/integration/spec.js
import Alert from '../../src/Alert'
function isAlert(a) {
// uses its own imported "Alert" function
return a instanceof Alert
}

If you are importing "Alert" from the spec, that this Alert function is different from the Alert imported in the application. If the spec calls isAlert and passes its own objects, the check will fail and return false

1
2
3
4
5
6
7
// cypress/integration/spec.js
import Alert from '../../src/Alert'
// if get the instance of alert from the application
cy.get(...).then(... => {
// NOPE, will fail!
expect(isAlert(appAlert)).to.be.true
})

So how do we ensure the spec has the same Alert reference as the application? By avoiding importing it and instead getting it from the application. Following the approach outlined in the blog post Send Data From The Application To The Cypress Test we do the following in the application:

1
2
3
4
5
6
7
8
// application code in "src"
import Alert from './Alert'
if (window.Cypress) {
window.Alert = Alert
}
function isAlert(a) {
return a instanceof Alert
}

So when the application loads, it "saves" its Alert reference on the window object. From the spec we can get to that reference using the cy.window and cy.its commands. Even if it takes a little for the application to load and set the window.Alert no problem, the cy.its command retries automatically until that property is found.

1
2
3
4
5
6
7
8
// cypress/integration/spec.js
// do not import Alert, instead
cy.window().its('Alert').then(Alert => {
// ✅ The Alert instance now comes from the application code
cy.get(...).then(... => {
expect(isAlert(appAlert)).to.be.true
})
})

Beautiful.