Home > Back-end >  exiting while loop inside promise or cypress conditionals
exiting while loop inside promise or cypress conditionals

Time:10-05

I can't get the results from Cypress that I'm looking for. I need to either use conditions or exit a function from a promise or depending on the results of a promise. As I say below, I've read the docs on conditions and still have questions.

Context: We auto populate a dummy data db with patients, providers and appointments, among other things.

We create the appointments in a script but only for a percentage of the patients, and it is somewhat random. In our cypress spec for testing the "modify appointment" feature, we were hardcoding a patient name (anti-pattern), and so it would work some of the time and fail some of the time in GitHub Actions. This is clearly bad. It's amazing nobody caught it.

In coming up with a good enough short term fix, which is not part of my current ticket, I am creating a function: getFirstPatientHavingAppointment(), which became more than just a getter.

Assuming I don't want to change the population script or require a hard coded name, but just want to modify the e2e tests to make things work, what should I do? I keep hitting road blocks with my approach:

This is what I've been trying - including small permutations:

  function getFirstPatientHavingAppointment() {
    let i = 0
    var completed = false // useless 
    while (i < 15) { // big safe number to a naive solution
      cy.log("Finding First Patient Having Appointment")
      cy.get('i[data-memberdetails]').eq(i).click()
      cy.wait(1000);
      cy.get('#appointments-tab').click();
      cy.wait(1000)
      cy.get('body')
        .then($body => {
          if ($body.find('button[data-test-id="open_appt_modal_for_provider"]').length) {
            // appointments exist, exit
            cy.log("Appointment found. Will modify. ")
            cy.get('button[data-test-id="open_appt_modal_for_provider"]').first().click();
            fillOutAppointmentModifyModal();
            completed = true // this doesn't work of course nor does 'return'
          } else {
            cy.log("No appointments found. Will try next patient")
            cy.get("#patient_details_close_button").click();
          }
        })
      cy.log(completed) // this always logs false
      if (completed) {
        cy.log("Completed-- patient updated")
        return
      }
      i  
    }

I've got the main part of the algorithm to work in that it keeps clicking and searching until it finds a patient with an appointment, and does all it needs to, but then it keeps going: i=1, i=2, i=3

I tried to find a way to exit the loop with a return statement but apparently you can't return from a parent function inside a .then(). I understand there's a sync and async nature to all of this that has to be honored, but it seems like there should be some way to break from a loop inside of a then(), some backdoor. I tried adding a flag variable 'completed' but that is not being mutated synchronously.

I read the docs on conditional testing with cypress, why they don't enable it and why it's an anti-pattern and that is why I have to use the cy.get('body').then() approach. I understand the gist of what they're saying and agree that not using conditions as they say is probably best in most cases, but I disagree with Cypress's reasoning why they prohibit conditional testing and error catching on cy.get entirely. I'm sure they're right for 90 percent of cases and there's wisdom in what they say that should be heeded, but I'm not sure they should proscribe it altogether, as though there's only one right way, especially as they acknowledge, these idioms exist in most other languages. In anyone case, if people want to be bad programmers, let them. Don't take away their tools. We know that developers can always come up with creative innovative ways to do things if and when they have the tools. They did give us the tool I am using, which works for the first part of my algorithm, but now I need to exit the loop inside the context of the promise (or find some other way to do things).

I think my app is deterministic enough to handle conditions. Maybe the tests as written will be a little flaky but they will be less flaky than before and not have hardcoded names. Someone else wrote all of this and if we decide to make it better and more robust, it should probably be its own ticket. I'm trying to fix it and unblock myself as well as others on their user stories in a good enough mvp way.

What does the internet think?

CodePudding user response:

SOLVED: This turned out to be really simple.

Forget about using a while loop in Cypress, especially with then(). The two together won't work together asynchronously, and new items will keep getting put on the Cypress task queue. The Cypress then() is not even the same as the Javascript promises then, which makes for confusion during your learning phase see here and while loops are in Cypress are generally an anti-pattern I believe.

I realized the solution watching this nice video

I just need Simple Recursion.

Drop the while loop. A function can call itself. That being so, it calls it when it's ready, without having to pass state outside itself, and you don't have to worry about new function calls to the command queue synchronously. There is the if-power you need in Cypress.

Here is the code and it works smoothly and even looks nicer.

  function getFirstPatientHavingAppointment(i=0) {
    cy.log("Finding First Patient Having Appointment")
    cy.get('i[data-memberdetails]').eq(i).click()
    cy.get('#appointments-tab').click();
    cy.get('body')
      .then($body => {
        if ($body.find('button[data-test-id="open_appt_modal_for_provider"]').length) {
          // appointments exist, exit
          cy.log("Appointment found. Will modify. ")
          cy.get('button[data-test-id="open_appt_modal_for_provider"]').first().click();
          fillOutAppointmentModifyModal();
          cy.log("Completed appt modification")
          return
        } else {
          cy.log("No appointments found. Will try next patient")
          cy.get("#patient_details_close_button").click();
          cy.log(i 1)
          getFirstPatientHavingAppointment(i 1)
        }
      })
  }

Simple recursion, no top level loops.

  • Related