Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wait until cy.get("selector") stops failing #75

Closed
dialex opened this issue Jan 9, 2020 · 16 comments
Closed

Wait until cy.get("selector") stops failing #75

dialex opened this issue Jan 9, 2020 · 16 comments

Comments

@dialex
Copy link

dialex commented Jan 9, 2020

From the examples, I understood that waitUntil will wait until a condition becomes true:

cy.waitUntil(() => cy.getCookie('token').then(cookie => cookie == "some value")));

But what if my condition is Cypress assertion? Using the same example:

// what if you need to wait until the cookie 'token' even exists?
cy.waitUntil(() => cy.getCookie('token')));

My context is, I have a page that will display data that is being updated by a cron job. The page does not update automatically, so the user must manually refresh the page. Sometimes the cron job takes a little longer, so I want to waitUntil a Cypress command or assertion stops failing. The problem is, as soon as the command fails, the test ends with a failure.

How can I make this code work?

waitUntilPaymentIsProcessed() {
  cy.log("Waiting... Payment must be processed by cron job")
  cy.waitUntil(
    () => {
      cy.reload()
      cy.get(activePackageSubtitle).should("exist") // wait until this passes
    },
    {
      interval: 3000,
      timeout: 10000,
      errorMsg: "Timed out waiting for cron to process payment"
    }
  )
  cy.log("Waiting... Done")
}

P.S: Thank you so much for writing this lib. It's so useful and so critical. I don't know why it doesn't come out-of-the-box with Cypress. Well done 😄

@NoriSte
Copy link
Owner

NoriSte commented Jan 9, 2020

Hi @dialex, you are welcome! 😊
Well:

  • from the very first example you cited: cy.getCookie('token') is not an assertion, it is a Cypress command. cy.waitUntil(() => cy.getCookie('token'))); waits until the token has a truthy value. I know that it is not your point, go on 😉

  • this plugin cannot retrieve Cypress assertions at all, in Cypress, a failing assertion forces the whole test to fail and you cannot restore it

  • you need to transform your cy.get(activePackageSubtitle).should("exist") assertion in something that does not fail, hence you need to manually control that the element is in the page.
    How could you do that? I am going to show you a potential example that does not work but I am going to explain why later.

    Instead of writing

cy.waitUntil(
    () => {
      cy.reload()
      cy.get(activePackageSubtitle).should("exist")
    }
  )

you should write

// ATTENTION: this is just an example, it will not workl
cy.waitUntil(
    () => {
      cy.reload()
      return cy.get(activePackageSubtitle).then($el => $el.length)
    }
  )
cy.get(activePackageSubtitle).should("exist")

so this should not fail because you manually check for the element existence before asserting about that with Cypress

  • why the example above should work but it does not? Because cy.get has a built-in assertion, you can find it in the docs

cy.get() will automatically retry until the element(s) exist in the DOM.
cy.get() will automatically retry until assertions you've chained all pass.

So, cy.get() is itself an assertion! And, again, a failing assertion makes the whole test fail, with or without this plugin

  • ... And so? Do not worry, the solution is a mix of both approaches! Since we can not use cy.get to query for an element and checking the length of the jQuery element itself... We could, instead, leveraging jQuery without cy.get!

    My solution is
cy.waitUntil(
    () => {
      cy.reload()
      return Cypress.$(activePackageSubtitle).length;
    }
  )
cy.get(activePackageSubtitle).should("exist")

Simply, we consume directly the Cypress' exposed jQuery.

Or, more condensed

cy.waitUntil(() => cy.reload().then(() => Cypress.$(activePackageSubtitle).length))
cy.get(activePackageSubtitle).should("exist")

Let me know if it works as you expect 😉

@dialex
Copy link
Author

dialex commented Jan 10, 2020

Thank you so much for the fast reply and in-depth explanation on how to solve this problem 🙇

YES, it worked. I spend the whole yesterday trying a solution but I could workaround the "cy.get() is itself an assertion" obstacle. I will add this tip to my "Cypress recipes" notebook!

@dialex dialex closed this as completed Jan 10, 2020
@NoriSte
Copy link
Owner

NoriSte commented Jan 10, 2020

You're welcome 😊

@NoriSte
Copy link
Owner

NoriSte commented Jan 10, 2020

Last but not least: if you have looked for a solution on Google/stackoverflow and you haven't found anything useful... Please help the community doing so

  • raise a new question on stackoverflow
  • explain well your problem and link to all the resources that seemed helpful but they weren't (and why)
  • I'll reply with the solution and you accept it

Doing so:

  • you don't need to add it the your recipes list, the next time you'll find it directly searching on Google
  • you'll help other developers with the same problem
  • other useful and alternative solutions could come and we both learn something new

What do you think? 😊

@dialex
Copy link
Author

dialex commented Jan 13, 2020

Since this questions was specific to this plugin (and not Cypress) itself, I think I prefer to leave it here. Google will catch it anyway, so we don't lose anything. But it was a good idea nonetheless 😉

P.S: I just like having this personal notebook with my most used Cypress code snippets -- it's faster than searching online or going through bookmarks. It's also shared privately with teammates, my goal is to make it public after I finish my current project :P

@kalpeshchilka
Copy link

Thank you so much for the fast reply and in-depth explanation on how to solve this problem 🙇

YES, it worked. I spend the whole yesterday trying a solution but I could workaround the "cy.get() is itself an assertion" obstacle. I will add this tip to my "Cypress recipes" notebook!

Hello @dialex,

I am not sure if it's a lot to ask, but can you please share your "Cypress recipes" notebook. It will be of great help since it's created with all your experience of cypress and plugins.

@dialex
Copy link
Author

dialex commented Jul 23, 2021

Of course @kalpeshchilka, glad to help 😉

@goska-malaga
Copy link

after hours spent trying to use get (as I found it on stackoverflow) and it is hard to notice a small comment in readme since example of using cy.get is more visible: cy.waitUntil(() => cy.get("input[type=hidden]#recaptchatoken").then($el => $el.val()))
so, after some hours I got here to this thread and it took me some another half day trying until I found another comment about use of brackets #355
all the time it took me for sure is because of low coding skills and like you wrote it is all about the brackets

@dimitur2204
Copy link

dimitur2204 commented Jun 17, 2022

Simply, we consume directly the Cypress' exposed jQuery.

Or, more condensed

cy.waitUntil(() => cy.reload().then(() => Cypress.$(activePackageSubtitle).length))
cy.get(activePackageSubtitle).should("exist")

Let me know if it works as you expect 😉

@NoriSte Isn't using something like

    cy.waitUntil(function () {
      return Cypress.$('[data-cy="progress-bar-4"]').length;
    });

the same as using cy.wait() or cy.get('#',{ timeout: }). Is it as much of an antipattern as those or is using waitUntil better in some way?

@NoriSte
Copy link
Owner

NoriSte commented Jun 21, 2022

@NoriSte Isn't using something like

    cy.waitUntil(function () {
      return Cypress.$('[data-cy="progress-bar-4"]').length;
    });

the same as using cy.wait() or cy.get('#',{ timeout: }).

Yes, it is! And in this case, using waitUntil would be an anti-pattern.

The above example is a bit different

cy.waitUntil(() => cy.reload().then(() => Cypress.$(activePackageSubtitle).length))
cy.get(activePackageSubtitle).should("exist")

because it include two operations, reloading the page and then waiting for the element. If you do not use waitUntil you end up with a tests that

  1. reloads tha page
  2. looks for the element
  3. fails if the element is not there

Instead, by using waitUntil, the test does:

  1. reloads tha page
  2. looks for the element
  3. if the element is not there... repeat from 1
  4. consumes the element

Let me know if you need more help 😊

@madhuriaarth
Copy link

I am using the above wait function using to download the files but its not working form me every time it reloads the page.

@NoriSte
Copy link
Owner

NoriSte commented Nov 29, 2022

I am using the above wait function using to download the files but its not working form me every time it reloads the page.

Can you share a reduced-to-the-bone project that I can play with?

@madhuriaarth
Copy link

Cypress.Commands.add("waitUntilSpinnerFinished", () => {
cy.waitUntil(() => Cypress.$(".bp4-spinner").length === 0, { timeout: 20000 });
})
This is the code I used for some times spinner takes more time to load the page. I got error.

@NoriSte
Copy link
Owner

NoriSte commented Nov 30, 2022

This is not a reduced-to-the-bone project that I can play with. What is the error you are receiving, for instance?

To play with your code (play, not read), I must

  1. guess your use case
  2. create a small frontend app to reproduce the case I guessed
  3. Imagine your test
  4. Writing the test I imagined
  5. Play with it

I guess that the chance I have to match your use case is 0.01%... And I can spend hours there...

Instead:

  1. If you create a repo with a frontend app that reproduces your case (not your app, just a small app with a spinner)
  2. If you include also a basic Cypress test that reproduces your case (not your test, just a small one-line test that checks for the spinner)
  3. ... 99% of the cases you find the problem by yourself, trust me. Reducing the problem to the bare minimum is usually enough to solve the problem itself
  4. In the case of the remaining 1%: I'm here to help, but I can clone a repo, install the dependencies, and check the problem. I do not know if I take 1 second or 1 hour to solve it, but at least 100% of that time is dedicated to helping you, not to guessing your use case 😊

@doelgonzo
Copy link

@NoriSte thank you so much for this thread. It works for me but I need to handle a slightly different scenario.

In these examples, it seems that we reload regardless on if the first render has the element on the page.

In my case, the element may update (driven by a socket push that makes an element render) or not (socket push timing not lined up and socket message came through while component still loading, so I would need to refresh to see it (it would come down on the reload's page fetch). I would like to avoid the initial reload if the element is on the page the first time, and reload if it's not. How can I achieve this? I've tried a bunch of stuff, including wrapping this like in the examples below

Works but sometimes with an unnecessary reload:

const elemId = '#myElem'
cy.waitUntil(() => cy.reload().then(() => Cypress.$(elemId).length), {
    interval: 3000,
    timeout: 5000,
    errorMsg: `Unable to find element using selector "${elemId}"`,
  })

Doesn't work:

const elemId = '#myElem'
if (Cypress.$(elemId).length > 0) return cy.get(elemId)
  return cy.waitUntil(() => cy.reload().then(() => Cypress.$(elemId).length), {
    interval: 3000,
    timeout: 5000,
    errorMsg: `Unable to find element using selector "${elemId}"`,
  })

but it doesn't seem to work, as it says it cannot find #myElem

@NoriSte
Copy link
Owner

NoriSte commented Dec 12, 2022

Uhm, something simpler like this could work?

cy.waitUntil(
  () => {
    if (Cypress.$(`#${elemId}`).length) {
      return true // the wait is over, a truthy stops cy.waitUntil retrying
    }

    return cy.reload().then(
      () => false // After reloading, a falsy value continues cy.waitUntil trying
    )
  }, {
    interval: 3000,
    timeout: 5000,
    errorMsg: `Unable to find element using selector "${elemId}"`,
  }
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants