My React web application, reports errors to users via the Snackbar component. By default, Snackbars don't autohide for accessibility what if we do want to hide Snackbars automatically using the autoHideDuration
parameter? In my case, I'm using 6000 milliseconds (i.e. 6 seconds).
How can I use Cypress to verify that no error message appeared on the screen?
I tried to detect that no Snackbar appeared with the following logic:
function errorSnackbarDoesNotExist(errorMessagePrefix) {
cy.get(".MuiSnackbar-root").should("not.exist");
cy.get("[id=idErrorSnackbar]").should("not.exist");
cy.get("[id=idErrorAlert]").should("not.exist");
cy.contains(errorMessagePrefix).should("not.exist");
}
However, when I forced an error to ensure that this function would detect an actual error, it did not work: none of the assertions in errorSnackbarDoesNotExist()
failed as I wanted them to.
I could not find a Cypress recipe for testing a Snackbar/Toast which is asynchronous.
I did try adding a { timeout: 10000 }
to the cy.get()
statements, but that didn't work. I thought this was supposed to wait for 10 seconds (which is longer than the 6 seconds of my autoHideDuration
). It seems like the timeout was not working, as reported also as a Cypress issue Timeout option not respected for .should('not.exist') #7957.
Someone asked a similar question but they wanted to know how to manipulate the system internally (e.g. by a stub) to cause an error. In my case, I'm not asking about how to cause the error, I'm asking about how to detect that NO error was reported to the end user.
CodePudding user response:
I got it to work by adding a short timeout instead of a long one, as follows:
function errorSnackbarDoesNotExist(errorMessagePrefix) {
cy.get(".MuiSnackbar-root", { timeout: 1 }).should("not.exist");
cy.get("[id=idErrorSnackbar]", { timeout: 1 }).should("not.exist");
cy.get("[id=idErrorAlert]", { timeout: 1 }).should("not.exist");
cy.contains(errorMessagePrefix, { timeout: 1 }).should("not.exist");
}
An article about the Cypress "should" assertion helped me understand that a short timeout, not a long timeout was what's needed. With the long timeout, Cypress may have detected the Snackbar but since it waited long enough for the Snackbar to disappear, maybe it only paid attention to the final state of the screen at the end of the timeout period.
I'll provide a deeper analysis as to why cy.get(".MuiSnackbar-root", { timeout: 10000 }).should("not.exist");
may not have been doing what I intended. Here's what I think happened, second by second:
Second | Activity |
---|---|
0 | Application threw an error and displayed the Snackbar. Snackbar detected so should("not.exist") was false but the timeout was 10 seconds so it tried again. |
1 | Snackbar still detected but there's 9 seconds left to wait for cy.get(".MuiSnackbar-root", { timeout: 10000 }).should("not.exist") to become true |
2 | Snackbar still detected but there's 8 seconds left to wait for cy.get(".MuiSnackbar-root", { timeout: 10000 }).should("not.exist") to become true |
... | ... |
6 | Snackbar hidden and since cy.get(".MuiSnackbar-root", { timeout: 10000 }).should("not.exist") is now true, it stops waiting for the timeout and Cypress considers the validation as passed |
So I think the solution is to change the Cypress timeout to a duration shorter than the autoHideDuration
instead of longer. I found that a timeout of 1 millisecond was enough to make Cypress detect the unwanted Snackbar. I'm not sure if that is long enough. Maybe it needs to be something squarely in the middle of the 6 seconds.
CodePudding user response:
Just a note about { timeout: 10000 }
- its not a wait, it's the period of time that the command will retry if it fails.
On that basis, it's possible if the snackbar is hidden, then you trigger the error the code runs before the snackbar is displayed.
I'm assuming that even with the auto-hide feature, the snackbar will always appear on errormessage and then disappear after 6 seconds.
In which case you need an on-off type test
function errorSnackbarDoesNotExist(errorMessagePrefix) {
cy.get(".MuiSnackbar-root").should("be.visible"); // existence is implied
cy.get(".MuiSnackbar-root", { timeout: 6100 }).should("not.exist");
...
}
Or if you don't like your test waiting the 6 seconds, try adding cy.clock()
function errorSnackbarDoesNotExist(errorMessagePrefix) {
cy.get(".MuiSnackbar-root").should("be.visible"); // existence is implied
cy.clock()
cy.tick(6000) // move app timers 6s
cy.get(".MuiSnackbar-root").should("not.exist");
...
cy.clock().then(clock => clock.restore()) // unfreeze timers
}