18

I am using Cypress cy.get to grab elements, but if there are none, my test is failing. I do not want it to fail. I want it to continue. The test is simply to list the items that are there, if any.

const listItemTitle = '[data-cy-component=list-item-title]';
cy.get(listItemTitle).each(($el, index, $list) => {
  cy.wrap($el).then(($span) => {
    const spanText = $span.text();
    cy.log(`index: ` + index + ' ' + spanText);
  });
});

I would have thought, if there are no elements there - that this code would still be ok, but not so. When I run it, I get this error: CypressError: Timed out retrying: Expected to find element: '[data-cy-component=list-item-title]', but never found it.

It works fine where elements are present. If no elements are found, I want to go on & do a different test.

Here is an experiment I tried:

let count: number = Cypress.$(listItemTitle).length;
cy.log('before:' + count);
cy.get(actionsBarActionsAdd).click();
cy.get(singlePickerSearch).type('Assets' + '{enter}');
cy.get(listItemCheckboxTitle)
  .first()
  .click();
cy.toast({
  type: 'Success',
});
count = Cypress.$(listItemTitle).length;
cy.log('after:' + count);
cy.get(listItemTitle).each(($li, index, $lis) => {
  cy.log('interface no. ' + (index + 1) + ' of ' + $lis.length);
  cy.wrap($li).click();
});

This was the outcome:

18 LOG        before:0
19 GET       [data-cy-component-key="actions-add"] input
20 CLICK
21 GET       [data-cy-component=single-picker-search] input
22 TYPE      Assets{enter}
23 GET       [data-cy-component="list-item-checkbox-title"]
24 FIRST
25 CLICK
26 GET       .iziToast    toast2
27 ASSERT    expected [ <div.iziToast.iziToast-opening.fadeInUp.iziToast-theme- 
alloy.iziToast-color-green.iziToast-animateInside>, 1 more... ] to have class iziToast-color-green
28 LOG       after:0
29 GET       [data-cy-component=list-item-title]
30 LOG       interface no. 1 of 1

Conclusively shows that Cypress.$(listItemTitle).length does not count number of elements with selector: listItemTitle.

Update:

By putting a cy.wait(1000); after the Add had been executed (in my experiment) - giving the DOM time to update - the new element was found. With more specific selectors, the wait is not required

Steve Staple
  • 2,983
  • 9
  • 38
  • 73

6 Answers6

17

You can use jquery via Cypress.$ to check if any exist.

const listItemTitle = '[data-cy-component=list-item-title]';
if (Cypress.$(listItemTitle).length > 0) {
  cy.get(listItemTitle).each(($el, index, $list) => {
    cy.wrap($el).then(($span) => {
    const spanText = $span.text();
    cy.log(`index: ` + index + ' ' + spanText);
    });
  });
}
Brendan
  • 4,327
  • 1
  • 23
  • 33
  • 1
    Cypress.$(listItemTitle).length does not give the answer you think it does. It does not give a count of listItemTitle elements. – Steve Staple Jan 18 '19 at 15:28
  • @SteveStaple what do you think it gives? I am using this in my tests in multiple places. If the element exists it executes the code in the if block, if the element does not exist it does not. – Brendan Jan 18 '19 at 15:48
  • I can see no relation between the number of elements & the result of .length. I have run tests where I count before & after adding an element, and the answer in both cases is still 0. – Steve Staple Jan 18 '19 at 15:53
  • 2
    Are you sure your selector is correct? If it didn't match the element as you expect that would explain 0 elements ever being found. – Brendan Jan 18 '19 at 18:18
  • @SteveStaple thanks for engaging on this answer, it looks like an interesting problem. – Richard Matsen Jan 18 '19 at 19:27
  • 5
    One potential difference (in your experiment) is that `cy.get()` has a built-in retry of 5 seconds, whereas `Cypress.$(listItemTitle)` is synchronous. What happens if you `cy.wait(5000)` before counting again, or count after the last `cy.get(listItemTitle)`? – Richard Matsen Jan 18 '19 at 19:32
  • @Brendan Upon further investigation, it turns out to be as you said. Have had to re-tune to make the selectors more specific. Now it works as expected. By putting a cy.wait(1000); after the Add had been executed (in my experiment) - giving the DOM time to update - the new element was found. With more specific selectors, the wait is not required. I think this issue can be safely closed. – Steve Staple Jan 18 '19 at 19:35
  • Great to hear that it's working for you! Can you accept the answer? – Brendan Jan 18 '19 at 19:54
  • @Brendan if i want to use 'spanText' outside the function , how can i achieve it as i tried no. of times but getting NULL every time . is there any way of doing this in cypress? – Nitish Kumar May 08 '20 at 13:29
  • 2
    Not as-is because you're mixing sync and async code. It is possible, this might help you understand a bit better: https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Mixing-Async-and-Sync-code – Brendan May 08 '20 at 18:34
  • I have a similar problem, but this answer wouldn't work for me because the `if` is evaluated immediately, before anything else is run. – mzedeler Mar 01 '21 at 20:18
6

Update for Cypress 12

The ideal way to do this is with Gleb Bahmutov's cypress-if, but it's blocked at the moment due to changes in version 12 of Cypress.

The issue is being worked on, but in the meantime here is a hack on .should() that performs the check with retry, but does not fail when no elements turn up - based on @M.Justin's method.

let start = Date.now()

const commandRetry = ($subject, {assert, timeout}) => {
  const elapsed = Date.now() - start
  console.log(elapsed, assert($subject))
  if (elapsed < timeout && !assert($subject)) {
    const err = new Error('Retry')
    err.isDefaultAssertionErr = true
    throw err
  }
}

cy.get('li')
  .should($subject => commandRetry($subject, { 
    assert: ($subject) => $subject.length > 0, 
    timeout: 3000 
  }))
  .should("have.length.gte", 0)
  .then($subject => cy.log(`Found ${$subject.length} items`))

Below is the web page I used to test it. It initially has no <li> elements, and after two seconds adds one.

You can see the test retrying the assertion in the console, and passing after 2 seconds.

If you comment out the script on the page, so no <li> are added, you see the test run for 4 seconds but does not fail.

If both cases the correct element count is logged.

<ul></ul>
<script>
  setTimeout(() => {
    const ul = document.querySelector('ul')
    const li = document.createElement('li')
    li.innerText = 'A list item'
    ul.appendChild(li)
  }, 2000)
</script>
Fody
  • 23,754
  • 3
  • 20
  • 37
1

You can use cy.get(".field").should("have.length", 0)

m4n0
  • 29,823
  • 27
  • 76
  • 89
hedge
  • 21
  • 5
  • This fails the original question, since there may be elements, but there may be none. Changing "have.length" to "have.length.gte" covers both cases: `.should("have.length.gte", 0)`. – M. Justin Feb 01 '23 at 18:37
1

The following retrieves the items, even if there are none:

cy.get(listItemTitle).should("have.length.gte", 0)

Note that this won't wait for the items to exist if the page is still loading them, and therefore might return before the elements are present. It should be safe to use when you know those items will have already been loaded.

This pattern can easily lead to problematic tests, so be careful about when and how it's used.

M. Justin
  • 14,487
  • 7
  • 91
  • 130
1

I have a similar case: I want the test to succeed if there is no element found, but if there are elements, I need that elements to be further tested with should. I came across this question, and based on the answer of M. Justin, I came up with the following:

        cy.get('td:eq(0) i').should("have.length.gte", 0).then (($hits) => {
            cy.log ('number of hits: ' + $hits.length)
            if ($hits.length == 0) {
                cy.log ('no hits OK');
                cy.wrap (true);
            }
            else {
                cy.log ('hits. test hits....');
                // this is only an example
                cy.wrap($hits).should('have.css',
                            'color').and('equal', 'rgb(170, 181, 0)');
            
            }
        });
ruud
  • 743
  • 13
  • 22
0

We can use this solution for finding optional elements in the DOM.

I added this as a constant function.

`export const optionalFindingElement = (element: string) => {
  return Cypress.$(element).length;
};
`

and then use it like this:

`Given('There are not assets generated by cypress in the kanban', () => {
  const assetNumber = optionalFindingElement('[data-fx-name="assetName"]');
  if (assetNumber > 0) {
   // Do something if the element was found
  } else {
 // Do something if the element was not found
}
});

`

Cheers!

Félix Pujols
  • 107
  • 1
  • 7