When a Cypress test finishes, the web application stays in the browser's window. This could be confusing if the next test does not immediately start with cy.visit
. For example, the JavaScript callbacks from the application visited in the first test are still executing, and could "leak" into the second test. Imagine the application scheduling code to execute after a delay:
1 | console.log('rendering app') |
Imagine the test confirming the number of console logs calls. The application is printing a message on start up and when adding a todo. The first test successfully passes
1 | it('logs message on startup', () => { |
In the screenshot above notice the second log
call. It happens after the test has already finished and thus does not affect our assertion cy.get('@log').should('have.been.calledOnce')
.
📺 If you prefer watching the explanation to reading this blog post, I have recorded the video Visit The Blank Page Between The Tests.
Let's add a second test that confirms the console.log
is called when adding a new Todo item.
1 | it('logs message on startup', () => { |
The second test fails - there is an extra console.log
call that now is included in the second test.
In my case, if the application uses a delay of 30ms when calling the setTimeout
, the application because flaky - sometimes the tests pass and sometimes they fail.
1 | setTimeout(() => { |
I do not have to tell you, how frustrating flaky tests are.
📚 Read my other blog posts about flaky tests on Cypress blog.
A similar situation when the app does something unexpected due to the previous test can happen for other reasons. For example, a long-running network requests can finish and unexpectedly update the app. At best, the application's behavior can be hard to explain. At worst, you can get the dreaded this element is detached from the DOM error.
Solution: visiting a blank page
A good solution to clearly separate the tests and stop any application callbacks is to visit a "neutral" blank page. Unfortunately, using the cy.visit('about:blank')
would not work.
1 | afterEach(() => { |
Notice the URL in the browser - we did NOT visit the about:blank
page, instead we have visited our baseUrl
+ about:blank
!
We need another way of visiting the blank page. We can use the window.location = 'about:blank'
instead. Note: we also need to visit the page in every test.
1 | beforeEach(() => { |
The passing tests show the expected calls - and nothing else.
The callback from the application is truly canceled. You can confirm it is never executed by looking at the DevTools console. The log message "running app code" is never printed, because the JavaScript VM executing the application code for localhost:3000
is stopped.
Notice the application page is blank - because we visit the blank pages after each test. We could leave the application running and instead visit the blank page before each test. In that case make sure the about:page
callback is the very first callback executed for each test. A good idea is to place it into the support file, because that file is always loaded before the spec file loads.
1 | beforeEach(() => { |
1 | beforeEach(() => { |
Nice.