cypress-io / eslint-plugin-cypress

An ESLint plugin for projects that use Cypress

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Suggestion: Loosen or Reconsider the `no-unnecessary-waiting` Rule

ITenthusiasm opened this issue Β· comments

So I just recently started trying out Cypress; and with the help of Cypress Testing Library, life has been pretty simple. Unfortunately, after writing my first few tests, I came to learn that ESLint complains when passing a number to cy.wait (when using cypress/recommended).

I read the documentation, and it most definitely makes sense! πŸ˜„ But I think it also makes the dangerous assumption that these are the only reasons people could conceivable use cy.wait. Actually, all of the warnings in the documentation are for people who misunderstand how Cypress [resolution] works altogether. People who already know how Cypress resolves would never run into these problems. So it seems less like a cy.wait issue and more like an issue with understanding Cypress.

My problem is that the rule (and its documentation) gives no solution for the following use case: After the user performs an action, a toast message appears. This message disappears after the allotted time (if not interacted with). With Cypress Testing Library, this might look like:

const toastMessage = "My toast message";
const toastTime = 5000;

/* Imagine user actions are here */

cy.findByText(toastMessage).should("exist");
cy.wait(toastTime);
cy.findByText(toastMessage).should("not.exist");

I think there are 2 potentially better options:

1a) Let the Rule Pass When a Variable Is Used

When a person provides a specific variable denoting the allotted wait time, it helps clarify intent (as in the toast example I showed). This (theoretically) suggests that the wait time is not merely "arbitrary".

1b) Let the Rule Pass When a Variable Is Used, but Only As a Rule Option

Same as the previous one. However, instead of making it the default case, it enables people to opt-in for allowing variables via a rule option.

2) Remove the Rule Altogether

This is a very peculiar option. But the reason I consider this an option is because the documentation doesn't provide a strong case for not using cy.wait(Number). Most of the time, lint rules provide strong arguments by discussing code readability, minimization of code lines, minimization of confusion, improved consistency, etc. Here, the only argument is that people need to know how Cypress resolution works; but they should be doing that anyway.

Whether or not people understand Cypress resolution is a separate matter from how people should or shouldn't be using cy.wait. Under the cy.wait documentation, the description would do much better to display the valid use cases for the function instead of only saying what should never be done. This enables developers to understand the intent of cy.wait, and it will likely decrease the number of people who create janky workarounds for what they consider their valid use cases that lack in-documentation examples.

@ITenthusiasm I do not think what you are showing is a good use case for cy.wait(ms).

Instead of hardcoded duration

const toastTime = 5000;

/* Imagine user actions are here */

cy.findByText(toastMessage).should("exist");
cy.wait(toastTime);
cy.findByText(toastMessage).should("not.exist");

I would write the test like this:

const toastTime = 5000;

/* Imagine user actions are here */

cy.findByText(toastMessage).should("exist");
cy.findByText(toastMessage, {timeout: toastTime}).should("not.exist");

Which means "make sure the toast message goes away in less than 5 seconds". For more on this particular test case, see the blog post https://glebbahmutov.com/blog/negative-assertions/

I think these rules should be disabled case-by-case basis. I have added documentation to the README on how to do this in #81 and I hope this would be enough for most users.

@bahmutov I think there are some counterpoints that could be made here... For one thing, though you have provided an alternate example, it's still practically the same as my example. I don't think it's necessarily a sufficient reason to ban cy.wait(Number).

There are more points that could be made. But regardless, I think the larger questions remain: 1) What is the intent of cy.wait(Number)? 2) Will users be told how to use cy.wait(Number) instead of only being told how not to use it?

The example I have provided is not working the same way as your example. Refer to our guide on https://on.cypress.io/retry-ability for more examples, but in general, using

cy.findByText(toastMessage, {timeout: toastTime}).should("not.exist");

means Cypress waits up to the toastTime milliseconds for the toast message to go away. If it goes away after 1 second, great - the test takes one second. If the toast message goes away after 4.5 seconds - good, the test lasts 4.5 seconds.

In your example, the test always waits 5 seconds then checks if the toast message goes away. Because each Cypress command by default waits up to 4 seconds, the test can potentially last up to 9 seconds, but 5 seconds is hardcoded.

We advise against hardcoding waits for this reason - you do not want to slow down your test by wait(N) commands, since there is a built-in retry mechanism specifically designed to make the test wait only as long as needed for the assertion to pass.

  1. the only thing that sometimes requires the cy.wait(N) is when the application does not provide any observable behavior for the test to wait for. Like the initial scaffolding period when the application does not respond yet, as described in https://www.cypress.io/blog/2018/02/05/when-can-the-test-start/ In that case if I could not change the application, I would just use cy.wait(N) and move on

  2. because the hard-coded wait is such an anti-pattern in our opinion, and is a red flag showing the application is less than fully testable, we do not intend to suggest when to use it. I haven't seen a good argument for using the cy.wait(N) aside from the edge cases above.

I'm not 100% sure if #81 truly closes this issue.

However, the point you made about waiting for 5 seconds vs 9 seconds was something I didn't consider. Thanks for the clarification!

πŸŽ‰ This issue has been resolved in version 2.11.3 πŸŽ‰

The release is available on:

Your semantic-release bot πŸ“¦πŸš€