Cypress: Ability to select by index within `.select()` command

Created on 18 Oct 2017  路  20Comments  路  Source: cypress-io/cypress

4239

  • Operating System: GNU/Linux Ubuntu 17.04
  • Cypress Version: "_from": "cypress@^1.0.1", "_id": "[email protected]",
  • Browser Version: Google Chrome Version 61.0.3163.100 (Official Build) (64-bit)

    Current behavior:

screenshot from 2017-10-18 10-02-19

Desired behavior:

I can select this option, via an index inside the argument option.

How to reproduce:

<select id="country">
   <option label="United States" value="object:44" selected="selected">United States</option>
   <option label="United Kingdom" value="object:45">United Kingdom</option>
   <option label="Switzerland" value="object:46">Switzerland</option>
   <option label="France" value="object:47">France</option>
   <option label="Germany" value="object:48">Germany</option>
   <option label="Canada" value="object:49">Canada</option>
   <option disabled="" label="------------------" value="object:50">------------------</option>
   <option label="Estonia" value="object:117">Estonia</option>
   <option label="Ethiopia" value="object:118">Ethiopia</option>
   <option label="Falkland Islands (Malvinas)" value="object:119">Falkland Islands (Malvinas)</option>
   <option label="Faroe Islands" value="object:120">Faroe Islands</option>
   <option label="Fiji" value="object:121">Fiji</option>
   <option label="Finland" value="object:122">Finland</option>
   <option label="France" value="object:123">France</option>
   <option label="French Guiana" value="object:124">French Guiana</option>
   <option label="French Polynesia" value="object:125">French Polynesia</option>
   <option label="French Southern Territories" value="object:126">French Southern Territories</option>
</select>

Test code:

cy.get('#country').select('France');

Additional Info (images, stack traces, etc)

I tried to create the patch myself (_it seems the error is here -> https://github.com/cypress-io/cypress/blob/0e0f75cfe90d24d9b0ed52b3c6a5a986baefcf8a/packages/driver/src/cy/commands/actions/select.coffee#L50_) but I cannot find the file inside node_modules :/

PS: Cypress is awesome :heart:

first-timers-only pkdriver ready for work feature

Most helpful comment

Even though this isssue is closed. I still think a select by index is required. Double list entries (both in value as label) is quite common as for long lists people tend to add the most used options as preferred option on top as well (same as in the example).

All 20 comments

Your options may have duplicate labels, but they have different values so you can use the value, rather than the label text in select() and you should get your expected option.

You could select the first instance of "France" with cy.get('#country').select('object:47') or the second with cy.get('#country').select('object:123').

Using the value here to be more specific about which match you want makes your test deterministic.

Hope this helps!

Of course but the list is generated via Angular. The value might change.

That does present a bit of a problem. Here is a workaround you can use for now:

cy.get('#country')
    .find('option[label="France"]') // this will return both elements
    .then($els => $els.get(1).setAttribute('selected', "selected")) // set the selected attribute on the desired option
    .parent() // change the subject back to the select
    .trigger('change') // trigger change event so event handlers will pick it up

I did a quick test where the drop down selection lead to another UI change with a handler for the select's change event and this did the trick. You may also wish to remove the selected attribute beforehand just so your DOM represents a more realistic scenario before the change even it triggered (though it made no difference in my sample code).

I did that with:

cy.get('#country')
    .find('option[selected="selected"]')
    .then($el => $el.get(0).removeAttribute('selected'))

So everything together looks like:

// remove current selection
cy.get('#country')
    .find('option[selected="selected"]')
    .then($el => $el.get(0).removeAttribute('selected'))

// Manually set desired selection
cy.get('#country')
    .find('option[label="France"]')
    .then($els => $els.get(1).setAttribute('selected', "selected"))
    .parent()
    .trigger('change')

Haven't tried this but you could probably clean this up by doing...

// Manually set desired selection
cy.get('#country').then(($select) => {
  const opt = $select.find('option[label="France"]')
  $select.val(opt.attr('value'))
  return $select
}).trigger('change')

Hey @dhoko, did the suggestions above solve your issue?

I didn't debug my tests yet, I will try monday. I think it should solve it yes. I can create a custom command from the snippet.

I tried this one -> https://github.com/cypress-io/cypress/issues/757#issuecomment-337676399 it worked. Thx.

Even though this isssue is closed. I still think a select by index is required. Double list entries (both in value as label) is quite common as for long lists people tend to add the most used options as preferred option on top as well (same as in the example).

@brian-mann your answer is an absolute lifesaver. I'm using Vue with Cypress and it's been nearly impossible to figure out how to use my selects - it's due to my selects having objects as their values in the code. Works great for everything but testing - until your answer.

Sorta wish Cypress just let us tell it to act like a normal user and "click here" and then "click here". It doesn't let you do that on selects, and it makes things pretty tough.

Thanks for your answer.

Will reopen as feature to add 'select by index' to .select() command.

:+1: for visibility

That does present a bit of a problem. Here is a workaround you can use for now:

cy.get('#country')
    .find('option[label="France"]') // this will return both elements
    .then($els => $els.get(1).setAttribute('selected', "selected")) // set the selected attribute on the desired option
    .parent() // change the subject back to the select
    .trigger('change') // trigger change event so event handlers will pick it up

I did a quick test where the drop down selection lead to another UI change with a handler for the select's change event and this did the trick. You may also wish to remove the selected attribute beforehand just so your DOM represents a more realistic scenario before the change even it triggered (though it made no difference in my sample code).

I did that with:

cy.get('#country')
    .find('option[selected="selected"]')
    .then($el => $el.get(0).removeAttribute('selected'))

So everything together looks like:

// remove current selection
cy.get('#country')
    .find('option[selected="selected"]')
    .then($el => $el.get(0).removeAttribute('selected'))

// Manually set desired selection
cy.get('#country')
    .find('option[label="France"]')
    .then($els => $els.get(1).setAttribute('selected', "selected"))
    .parent()
    .trigger('change')

That workaround helped me, thanks!

This definitely feels like it shouldn't need a workaround. There is nothing wrong with having options with duplicate values and labels.

    cy.get('#country')
      .find('option[label="France"]').then(elements => {
        const france = elements[0].getAttribute('value');
        cy.get('#country')
          .select(france);
      });    

Hey @jennifer-shehane, the lack of select by index has been annoying me so I'd like to take this issue. Would we want to add an option for index, and then default to 0? It looks like the check at line 109 could be changed to handle this pretty easily.

@jennifer-shehane Some select tags fields on closed platforms are auto generated and out of developer control. The platform I am testing list the same values twice in select fields causing an error below. Whats the status on index based selection? I believe @tylerlinquata is willing to fix. @tylerlinquata have you opened a pull request yet?

Screen Shot 2020-03-16 at 11 26 06 AM

+1 to the select by index feature. I'm testing a select that is populated with data from an API that changes every call, so I can't select by value.

I think this would basically accept a new argument index, so that the arguments accepted would be:

Syntax

.select(value)
.select(values)
.select(index)
.select(value, options)
.select(values, options)
.select(index, options)

Argument

index (Number)

A number indicating the index to find the <option> at within the <select>.


We would be open to a PR (that is fully tested). The code can be found here: https://github.com/cypress-io/cypress/blob/develop/packages/driver/src/cy/commands/actions/select.coffee#L12

Check out our contributing doc.

I share implemented solution

selectDropDownList(cy.get('#selectedRoomNo'),4)  
function selectDropDownList(dropDownList, value) {
    dropDownList.then(($select) => {
      debugger
      $select.find('option[value="' + $select.val() + '"]')[0].removeAttribute('selected')
      const opt = $select.find('option[value="' + value + '"]')
      $select.val(opt[0].value)
      opt[0].setAttribute('selected', '')
      return $select
    }).trigger('change')
  }
Was this page helpful?
0 / 5 - 0 ratings