Search code examples
cypressangular-ngselect

Cypress: How to scroll a dropdown to find item


I need to click a dropdown list and scroll to find an item by text.

enter image description here

At the moment I know that the item is at the bottom of the list so I can do:

cy.get('.ng-dropdown-panel-items').scrollTo("bottom").contains(/test/i).click()

and this works, but if the item moves and is no longer at the bottom, this will break.

I tried scrollIntoView but with no luck:

cy.get('.ng-dropdown-panel-items').contains(/test/i).scrollIntoView().click()

and

cy.get('.ng-dropdown-panel-items').scrollIntoView().contains(/test/i).click()

Does anyone know how I can do this?

Update: the list of options is dynamically generated (not all options are in the DOM initially) so scrolling to the bottom is required to get all options. Once all options are available .contains() can be used to find the element.


Solution

  • The Angular ng-select in virtual mode is quite tricky to handle.

    It's list is virtual, which means it only has some of the items in the DOM at one time, so you can't select them all and iterate over them.

    You can recursively scan the options list and use .type({downarrow}) to move new options into the DOM (which is one way a user would interact with it).

    it('selects an option in a virtual-scroll ng-select', () => {
    
      cy.visit('https://ng-select.github.io/ng-select#/virtual-scroll')
    
      cy.get('ng-select').click();                    // open the dropdown panel
    
      cy.get('.ng-option')
        .should('have.length.gt', 1);                 // wait for the option list to populate
    
      function searchForOption(searchFor, level = 0) {
    
        if (level > 100) {                                         // max options to scan
          throw 'Exceeded recursion level'                         // only useful for 100's
        }                                                          // not 1000's of options 
    
        return cy.get('ng-select input')
          .then($input => {
            const activeOptionId = $input.attr('aria-activedescendant') // highlighted option
            const text = Cypress.$(`#${activeOptionId}`).text()         // get it's text
            if (!text.includes(searchFor)) {                            // not the one?
              cy.wrap($input).type('{downarrow}')                       // move the list
              return searchForOption(searchFor, level + 1)              // try the next
            }
            return cy.wrap(Cypress.$(`#${activeOptionId}`))
          })
      }
    
      searchForOption('ad et natus qui').click();             // select the matching option
    
      cy.get('.ng-value')
        .should('contain', 'ad et natus qui');                // confirm the value 
    
    })
    

    Note that recursion can be hard on memory usage, and this code could be optimized a bit.