3

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.

SamHeyman
  • 207
  • 3
  • 13
  • what actions does your task make you do with that element? – Rosen Mihaylov May 18 '21 at 09:44
  • I am filling in a form so I just need to select an item in the dropdown list – SamHeyman May 18 '21 at 09:45
  • Selects usualy require cy.select() https://docs.cypress.io/api/commands/select SO for your case it would be `cy.get('.ng-dropdown-panel-items').select(/test/i)` – Rosen Mihaylov May 18 '21 at 09:46
  • good point, and I tried it but I see that our developers have used Angular. This is the error I get when using select(): ```cy.select() can only be called on a – SamHeyman May 18 '21 at 10:12
  • I see. Could you add the HTML structure of the dropdown to the post, so we can see and think of something? If it is dropdown in div I suppose you can use `cy.get('.ng-dropdown-panel-items').focus().wait(1000).contains(/test/i).click()` – Rosen Mihaylov May 18 '21 at 11:01
  • the structure is: `
    Acta Marino
    – SamHeyman May 18 '21 at 12:30
  • @RosenMihaylov it seems that not all of the options in the dropdown list are initially populated, so not all options are in the DOM. The user needs to scroll to the bottom to dynamically populate all options. This means that my initial solution of scrolling to the bottom makes sense, and even if I select an item from the top (which is no longer visible), it still works. Thanks for your help and suggestion to use .select() (I don't know why someone down voted it, I think it was an useful answer) – SamHeyman May 18 '21 at 12:50

4 Answers4

3

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.

Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
2

For most cases you would need cy.get().select like for example:

cy.get('.ng-dropdown-panel-items').select(/test/i)

Rosen Mihaylov
  • 1,363
  • 2
  • 10
0

You can use an each() to loop through the drop down elements and when you find the desired text, make an click().

cy.get('span.ng-option-label.ng-star-inserted').each(($ele) => {
  if($ele.text() == 'desired text') {
    cy.wrap($ele).click({force: true})
  }
})
Alapan Das
  • 17,144
  • 3
  • 29
  • 52
0

Try something like below recursive function:

function scrollUntilElementFound(scrollIndex) {
scrollIndex = scrollIndex+10;
if(!cy.get('.ng-dropdown-panel-items').contains(/test/i)){
    cy.get('.ng-dropdown-panel-items').scrollTo(scrollIndex);
    scrollUntilElementFound(scrollIndex);
} else {
    //element found
    return;
}  
}
Fseee
  • 2,476
  • 9
  • 40
  • 63