Search code examples
javascriptcypressintegration-testingjavascript-function-declaration

Cypress Error: 'cy.within() can only be called on a single element'


I've received a Cypress error suggesting that multiple elements contain the searched value, so it cannot recognize the requested one.

Here is the Cypress script I've used for testing:

it('Check all promotions', () => {
            
        // Check Promotion 1
        let promotionCode = 'UK_ZYN_MULTI_BUY_20';
        let promotionName = '10 ZYN Cans multi-buy + 10 ZYN Cans multi-buy';
        let websiteEnv = 'Inactive';
        let promotionVersion = '2';

        searchPromotion(promotionCode, promotionName, websiteEnv, promotionVersion);
});
});

// searchPromotion function
function searchPromotion(promotionCode, promotionName, websiteEnv, promotionVersion) {
    cy.get('.z-bandbox-input')
        .eq(6)
        .should('exist')
        .clear()
        .type(promotionCode, { force: true })
        .wait(300);

    cy.get('.yw-textsearch-searchbutton.z-button')
        .eq(1)
        .click({ force: true })
        .wait(10000);

    const selector = "tr.yw-coll-browser-hyperlink:has(.yw-listview-cell-label.z-label:contains(\"" + promotionCode + "\")):has(.yw-listview-cell-label.z-label:contains(\"" + promotionName + "\")):has(.yw-listview-cell-label.z-label:contains(\"" + websiteEnv + "\")):has(.yw-listview-cell-label.z-label:contains(\"" + promotionVersion + "\"))";

    function checkPage() {
        cy.get('body').then($body => {
            if ($body.find(selector).length > 0) {
                cy.get(selector).within(() => {
                    cy.get('.yw-listview-cell-label.z-label')
                      .filter((_, el) => {
                          // Ensure the version is an exact match
                          const versionText = el.innerText.trim();
                          return versionText === promotionVersion; // Exact match check
                      })
                      .first() // Ensure only one element is selected
                      .click({ force: true });
                });
            } else {
                cy.get('button.z-paging-button.z-paging-next[title="Next Page"]').eq(2).then($button => {
                    if ($button.is(':visible') && !$button.attr('disabled')) {
                        cy.wrap($button).click({ force: true }).wait(5000);
                        checkPage(); // Recursively check the next page
                    } else {
                        cy.log('Promotion not found or next page button is not clickable');
                    }
                });
            }
        });
    }
    
    checkPage();
}

Here is the error received:
enter image description here

Cypress searched for the promotions and there is 3 of them. Basically those are 3 versions of the same promotion, so only their version number is different, everything else is the same, including the class name of each field.

In order to comprehend it better: When promotions are searched they are listed with their Code, Name, Status, Version etc. All these fields contains the same class name. When you search for the one promotion in particular by its code, you get a list of various versions of that promotion. It means that only the version value differs, and by that value Cypress can determine which promotion should it choose for further testing.

enter image description here

I've solved that by implementing this const:

const selector = "tr.yw-coll-browser-hyperlink:has(.yw-listview-cell-label.z-label:contains(\"" + promotionCode + "\")):has(.yw-listview-cell-label.z-label:contains(\"" + promotionName + "\")):has(.yw-listview-cell-label.z-label:contains(\"" + websiteEnv + "\")):has(.yw-listview-cell-label.z-label:contains(\"" + promotionVersion + "\"))";

Still, it seems that my functions didn't solve the problem of searching for exact values.

What I believe is the issue here is that Cypress is mistaking the "2" value set for the version field with the number "2" that is appearing in each promotion code: UK_ZYN_MULTI_BUY_20

I thought that the function I've set in my script could handle this, but it seems that it is missing something additional.


Solution

  • You are not using $body but if you change it to tbody ie. the body of the table, you can then .filter() rows for the correct cell contents.

    Adding :eq(cell-number) overcomes the issue of finding promotionVersion in the wrong column.

    Using string template for values makes the :contains() shorter.

    cy.get('tbody').then($tbody => {
    
      const target = $tbody.find(tr)
        .filter(`:has(td:eq(0):contains("${promotionCode}"))`)
        .filter(`:has(td:eq(1):contains("${promotionName}"))`)
        .filter(`:has(td:eq(2):contains("${websiteEnv}"))`)
        .filter(`:has(td:eq(3):contains("${promotionVersion}"))`)
    
      if (target.length) {
        cy.wrap($el).click({ force: true })
      } else {
        // next page logic
      }
    });
    

    Simple version of the table for checking:

    <table>
      <tbody>
        <tr>
          <td>a</td>
          <td>b</td>
          <td>c2</td>
          <td>0</td>
        </tr>
        <tr>
          <td>a</td>
          <td>b</td>
          <td>c2</td>
          <td>1</td>
        </tr>
        <tr>
          <td>a</td>
          <td>b</td>
          <td>c2</td>
          <td>2</td>
        </tr>
      </tbody>
    </table>
    

    Testing:

    const promotionCode = 'a'
    const promotionName = 'b'
    const websiteEnv = 'c2'
    const promotionVersion = '2'
    
    it('finds the above criteria', () => {
      
      cy.get('tbody').then($tbody => {
    
        const target = $tbody.find('tr')
          .filter(`:has(td:eq(0):contains("${promotionCode}"))`)
          .filter(`:has(td:eq(1):contains("${promotionName}"))`)
          .filter(`:has(td:eq(2):contains("${websiteEnv}"))`)
          .filter(`:has(td:eq(3):contains("${promotionVersion}"))`)
    
        if (target.length) {
          cy.log(`found at row ${target.index()}`)
        } else {
          cy.log('not found')
        }
      })
    });
    

    Passes:

    enter image description here