Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to select by index within .select() command #757

Closed
dhoko opened this issue Oct 18, 2017 · 27 comments · Fixed by #18201
Closed

Ability to select by index within .select() command #757

dhoko opened this issue Oct 18, 2017 · 27 comments · Fixed by #18201
Assignees
Labels
good first issue Good for newcomers pkg/driver This is due to an issue in the packages/driver directory type: feature New feature that does not currently exist

Comments

@dhoko
Copy link

dhoko commented Oct 18, 2017

#4239

  • Operating System: GNU/Linux Ubuntu 17.04
  • Cypress Version: "_from": "cypress@^1.0.1", "_id": "cypress@1.0.2",
  • 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 ->

if (num = options.$el.length) and num > 1
) but I cannot find the file inside node_modules :/

PS: Cypress is awesome ❤️

@avanslaars
Copy link
Contributor

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!

@dhoko
Copy link
Author

dhoko commented Oct 18, 2017

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

@avanslaars
Copy link
Contributor

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')

@brian-mann
Copy link
Member

brian-mann commented Oct 18, 2017

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')

@jennifer-shehane jennifer-shehane added the stage: needs information Not enough info to reproduce the issue label Oct 19, 2017
@jennifer-shehane
Copy link
Member

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

@dhoko
Copy link
Author

dhoko commented Oct 20, 2017

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.

@dhoko
Copy link
Author

dhoko commented Oct 23, 2017

I tried this one -> #757 (comment) it worked. Thx.

@benvds
Copy link

benvds commented Jan 19, 2018

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).

@jennifer-shehane jennifer-shehane removed the stage: needs information Not enough info to reproduce the issue label Jan 19, 2018
@johndatserakis
Copy link

@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.

@jennifer-shehane
Copy link
Member

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

@jennifer-shehane jennifer-shehane added type: feature New feature that does not currently exist pkg/driver This is due to an issue in the packages/driver directory stage: ready for work The issue is reproducible and in scope labels Jul 13, 2018
@jennifer-shehane jennifer-shehane changed the title Selector, Select doesn't work if we have the same option twice Selector, Select by index Jul 13, 2018
@jennifer-shehane
Copy link
Member

@jennifer-shehane jennifer-shehane added the good first issue Good for newcomers label Jul 13, 2018
@mattvb91

This comment has been minimized.

@grazielegs
Copy link

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!

@Undistraction
Copy link

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

@jennifer-shehane jennifer-shehane changed the title Selector, Select by index Ability to select by index within .select() command Jun 21, 2019
@azaeng04
Copy link

azaeng04 commented Aug 19, 2019

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

@tylerlinquata
Copy link

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.

@jbool24
Copy link

jbool24 commented Mar 16, 2020

@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

@RamiroPinol
Copy link

+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.

@jennifer-shehane
Copy link
Member

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.

@davidcordoba4
Copy link

davidcordoba4 commented May 19, 2020

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')
  }

@dillingham
Copy link

dillingham commented Jun 4, 2021

This is especially useful for my current usecase:

a component has select options with the same value
but when selected it uses the option's data-name attribute

@bahmutov
Copy link
Contributor

bahmutov commented Jun 4, 2021

Take a look at https://glebbahmutov.com/cypress-examples/7.4.0/recipes/selected-value.html#selecting-option-by-index where I use the following to select by index

// every child of <select> is an <option> element
cy.get('select')
  .children()
  .eq(1)
  .then(($option) => {
    const value = $option.attr('value')
    // if we want to select the oranges,
    // let's use the value we got
    cy.get('select').select(value)
  })

@dillingham
Copy link

This worked for me

cy.get('select').find('option[data-name="sort"]').then($el =>
    $el.get(0).setAttribute("selected", "selected")
).parent().trigger("change")

@sbaechler
Copy link

What is the reason that cy.select() does not support duplicate options? If text and value are the same, then Cypress should just select the first instance.

As mentioned before, duplicating entries is a common pattern. E.g. in a countries list where the most used countries appear on top and in the full list below.

Selecting by index would then be the workaround but it would be flaky because the order or number of options might be different.

The workaround with the jQuery selectors did not work for me in a React project. Maybe because React uses its own event system.

@Loren-Johnson
Copy link

I agree with sbaechler. I think the solution is not to add index selection, but rather allow select() to specify if it is expecting duplicates and which duplicate should be chosen.

@mjustin
Copy link

mjustin commented Aug 27, 2021

I'm writing against a select box where the selected option is shown as the default selected element, but is disabled. The same value is shown selectable lower down in the list. In order to select the option, I need to somehow specify that I want the non-disabled one, otherwise I get an error.

cy.select() failed because this <option> you are trying to select is currently disabled

@cypress-bot
Copy link
Contributor

cypress-bot bot commented Sep 27, 2021

Released in 8.5.0.

This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v8.5.0, please open a new issue.

@cypress-bot cypress-bot bot removed the stage: needs review The PR code is done & tested, needs review label Sep 27, 2021
@cypress-bot cypress-bot bot locked as resolved and limited conversation to collaborators Sep 27, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
good first issue Good for newcomers pkg/driver This is due to an issue in the packages/driver directory type: feature New feature that does not currently exist
Projects
None yet
Development

Successfully merging a pull request may close this issue.