Both .should() and .then() commands in Cypress could be used in similar ways, both can be passed a callback function that executes in the previously yielded subject in the whole chain. But there’re differences that need to be taken into account when using these two commands. I’ll share a thing or two about the differences and show some examples.

Let’s take the following example:

describe.only('Test the difference between commands .should() and .then()', function() {
	it('Test .then() command', function() {
		cy.wrap([1, 3, 2]).then((array) => {
			expect(array).to.have.length(5)			
		})
	})

	it('Test .should() command', function() {
		cy.wrap([1, 3, 2]).should((array) => {
			expect(array).to.have.length(5)			
		})
	})
})

The only difference is the .should()/.then() commands, the rest is the same. Both tests will fail since the array doesn’t have 5 elements.

Execution Time

Ok, let’s make some experiments. First, I’ll comment out the first part and invoke the test in Cypress Test Runner.

describe.only('Test the difference between commands .should() and .then()', function() {
	/*it('Test .then() command', function() {
		cy.wrap([1, 3, 2]).then((array) => {
			expect(array).to.have.length(5)			
		})
	})*/

	it('Test .should() command', function() {
		cy.wrap([1, 3, 2]).should((array) => {
			expect(array).to.have.length(5)			
		})
	})
})

The run, I get the following result:

image

When I comment out the .should() test and uncomment the .then() test, this is the outcome:

describe.only('Test the difference between commands .should() and .then()', function() {
	it('Test .then() command', function() {
		cy.wrap([1, 3, 2]).then((array) => {
			expect(array).to.have.length(5)			
		})
	})

	/*it('Test .should() command', function() {
		cy.wrap([1, 3, 2]).should((array) => {
			expect(array).to.have.length(5)			
		})
	})*/
})

image

What happened? Well, notice the execution time, it’s 4.01 seconds and 0.03 seconds. Quite a difference considering the rest is completely the same.

It turns out there’s a big difference in how these two commands get executed by Cypress. .should() command is automatically retried, whereas .then() either passes or not.

By this point, it seems really useful to learn a bit about timeouts, I will, however, just mention what happened in the previous example. If you need to find out more about timeout, you should read this Cypress documentation and have a look how it works with the .should() command.

So, since I didn’t change the default value of defaultCommandTimeout, nor I passed down the chain any other timeout, the default value is 4 seconds. Yes, Cypress will keep executing the callback function in .should() for 4 seconds. It either passed during this time, or not and then the whole test fails. That’s why it took 4.01 seconds to finish the first example.

If I ever decide that 4 seconds are not enough (might not be when getting an element from the DOM), then I can pass down a different timeout on a per chain basis:

describe.only('Test the difference between commands .should() and .then()', function() {
	/*it('Test .then() command', function() {
		cy.wrap([1, 3, 2]).then((array) => {
			expect(array).to.have.length(5)			
		})
	})*/

	it('Test .should() command', function() {
		cy.wrap([1, 3, 2], { timeout: 10000 } ).should((array) => {
			expect(array).to.have.length(5)			
		})
	})
})

This will keep executing for 10 seconds:

image

Or if I need to change the timeout globally, it’s much cleaner to change it in configuration, so my cypress.json would look sth like this:

{	
	"defaultCommandTimeout": 15000
}

This makes Cypress keep trying for 15 seconds:

image

So, to make some sort of conclusion here, from the point of view of execution, .should() command should be used whenever you need Cypress to keep trying more times. Typically, you are writing some e2e tests and you need to “wait” for some element to appear in the DOM.

Subject

Moving on to another difference. I’ll change the example a bit:

describe.only('Test the difference between commands .should() and .then()', function() {
	it('Test .then() command', function() {
		cy.wrap([1, 3, 2]).then((array) => {
			expect(array).to.have.length(3)
			return true			
		}).then((value) => {
			expect(value).to.be.true
		})
	})

	it('Test .should() command', function() {
		cy.wrap([1, 3, 2]).should((array) => {
			expect(array).to.have.length(3)
			return true			
		}).then((value) => {
			expect(value).to.be.true
		})
	})
})

Again, the only difference is the .then() and .should() commands after the initial .wrap() command. Let’s execute it in the runner again:

image

Didn’t you expect both tests to pass? I once did, but then I read up on the difference a bit. It turns out that .should() ignores a return value from a callback function and simply passes down the previous subject. .then() on the other hand does’t do that, so true became a new subject that pass passed down the chain to the next .then() command. An example from Cypress documentation could be found here.

This is actually useful, because I can still use the original subject later on:

it('Test .should() command', function() {
	cy.wrap([1, 3, 2]).should((array) => {
		expect(array).to.be.an('array')			
	})
	.and('have.length', 3) // the subject is still the initial array
	.and('include', 1) // still the array
})

I just pass down the initial array. .and() is just an alias of .should(). However, be aware that some chainers change the subjects yielded. There is even a filled in issue on this topic, so who know how this will be resolved in future versions of Cypress.

All in all, .should() and .then() could be a bit confusing, so it’s better to pour over the documentation a few times.