skip to Main Content

Hi i m new to Cypress and i try to avoid the wait() function on my tests..


I m aware, based on the official docs, that Cypress works async and wedont need to use the wait() function, especially the visit() command handles that because it loads the page and then it moves on.

On my test case i want to do 2 main things that cause an issue:

  1. open a dropdown menu (its on the Left nav menu, which there are 5 menus and i want the 2nd)
  2. click on an option to go to another page
it("clicks on the 'Front End' and navigates to the correct page", () => {
  visit(path, {
    timeout: 120000,
    pageLoadTimeout: 120000,
  });

  cy.get(selectors.CATEGORIES)
    .eq(2)
    // i use 'within', because i want to search **inside the selectors.CATEGORIES.eq(2) and not on the whole DOM**
    .within(() => {
      cy.get(dataCySelector("gridRow")).then(($optionsWrapper) => {
        const parentEl = $optionsWrapper.parent();
        const isMenuOpen = parentEl.css("display");

        // if i dont add the wait(), it selects the 1st 'menu options' instead of the **3rd**
        cy.wait(3000);

        if (isMenuOpen === "none") {
          console.log("*MENU IS CLOSE I OPEN*");
          cy.contains("category").click(); // OPEN THE MENU
          cy.contains("Front End").should("be.visible").click(); // click on the 'front end'
        } else {
          console.log("*MENU IS OPEN I DONT CLICK ON IT*");
          cy.contains("Front End").should("be.visible").click(); // JUST click on the 'front end'
        }

        cy.url().then(() => {
          cy.urlIncludes("/path/to/menu/option");
          cy.wait(3000);
          cy.contains(dataCySelector("AN_ELEMENT"));
        });
      });
    });
});

So the flow that i have is the below:


  1. visit the page that i want
  2. get the CATEGORIES selector (there are 5 menus on the left nav bar)
  3. get the 3rd
  4. Use within so to drill down in into its children (i replaced then because it searched on the whole DOM instead)!!
  5. i get the parent of the ‘gridRow’ and look if display=none
  6. ! if i dont add the wait(3000), the parentEl is the first menu wrapper!!
  7. After the comparison, click on the ‘option link’
  8. the user is redirected to the new page, but again i need the wait() so to check if the element selector exists.

Something must be wrong in here, can i get rid of the waits ?
thanks.

2

Answers


  1. For the second wait(), if your problem is linked to the fact that your page needs more time to load than the default timeout, you can still override it.

      cy.url().then(() => {
        cy.url().should("contain", "/path/to/menu/option");
        cy.contains(dataCySelector("AN_ELEMENT"), {timeout: 30_000});
      });
    

    For the first wait, the If/Else is pretty weird. It should be known when it’s opened or not, so I would avoid it (by creating for example 2 functions, for both cases) and then, for each case, I would add a {timeout: 30_000} where it’s necessary.

    Login or Signup to reply.
  2. In general, changing .then() to .should() will give you retry and remove the need to wait.

    Retry is the smart way of waiting since it only waits as long as the condition is not met.

    You must use expect() or assert() to trigger the retry. Cypress patches those chai methods and uses the error thrown to trigger each retry attempt.

    Checking the URL

    cy.url().should((url) => {
      expect(url).to.include("/path/to/menu/option")  // retries if not true
    })
    // or 
    cy.url().should('include', "/path/to/menu/option")  
    
    // this command can be separate, 
    // Cypress has a queue and this line is only run after the above succeeds
    cy.contains(dataCySelector("AN_ELEMENT"))  
    

    Checking the visibility of gridRow parent

    You have a clean page load at the top of the test, so your menu will not be open initially (the page should load predictably).

    The test could simply be:

    cy.get(selectors.CATEGORIES)
      .eq(2)
      .within(() => {
        cy.contains("category").click()
        cy.contains("Front End").should("be.visible").click()
        cy.url().should('include', '/path/to/menu/option')
        cy.contains(dataCySelector("AN_ELEMENT"))  
      })
    

    But for the purpose of illustration, this should open the menu if not already open.

    cy.get(dataCySelector("gridRow"))
      .parent()
      .then($menu => {
        const menuIsClosed = $menu.css("display") === 'none';
        if (menuIsClosed) {
          $menu.click()            // jQuery click()
        }
      })
    

    The note it selects the 1st ‘menu options’ instead of the 3rd seems to indicate the menu items are lazy-loaded.

    You can overcome that by adding a .should() assertion for the number of menu options (length property).

    cy.get(dataCySelector("gridRow"))
      .should('have.length', 3)
    cy.contains("Front End").should("be.visible").click()
    

    The whole test

    You want to "decouple" steps so you can check them individually.

    It’s hard to be exact about the code since you have abstracted the basic Cypress commands into custom commands, and not given details, but here’s the approach I would take:

    cy.get(selectors.CATEGORIES)
      .eq(2)
      .within(() => {
    
        // open the parent
        cy.get(dataCySelector("gridRow"))
          .parent()
          .then($menu => {
            if ($menu.css("display") === 'none') {
              $menu.click()            // jQuery click()
            }
          })
          .should('not.have.css', 'display', 'none')   // add retry in case of lag
    
        // ensure all options loaded
        cy.get(dataCySelector("gridRow"))
          .should('have.length', 3)                  
    
        // click required option
        cy.contains("Front End").should("be.visible").click()
        cy.url().should('include', '/path/to/menu/option')
    
        // verify page
        cy.contains(dataCySelector("AN_ELEMENT"))  
      })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search