select react-select dropdown list option using cypress [closed]
Asked Answered
W

23

40

Does anyone know how to select an option from a react-select dropdown list in a cypress test?

I have tried lots of stuff but to no avail.

Seems that react-select uses a hidden input. That cypress can not write into. And divs that cypress can also not write into either.

It also does not help that I do not know how to inspect the actual dropdown list in dev tools because it does not stay open.

I am using:

  • react-select v2.4.1
  • cypress v3.1.5

Edit 1:

@bkucera's answer works. The working Code I ended up was:

it('updates Person', () => {
    cy.get('[data-id=bearbeiter]')
      .find('.css-10nd86i')
      .click()
      .find('input')
      .eq(1)
      .focus()
    cy.contains('Test Tester').click({ force: true })
  })

I had to add an .eq(1) after find because there seem to be two inputs.

Edit 2:

Above solution ended up clicking on elements in the nav tree on my site that happened to contain the same text. So no cigar :-(

Edit 3:

I have also tried this solution:

Cypress.Commands.add('setSelectOption', ({ selector, option, value }) => {
  cy.get(selector)
    .find('.css-10nd86i input')
    .eq(1)
    .focus()
    .type(value, { force: true })
})

...but even though force: true is used I get this error:

The element typed into was:

  > <input name="aeId" type="hidden" value="862333db-31cf-444c-b8ea-021c640c7a44">

Cypress considers the 'body', 'textarea', any 'element' with a 'tabindex' or 'contenteditable' attribute, or any 'input' with a 'type' attribute of 'text', 'password', 'email', 'number', 'date', 'week', 'month', 'time', 'datetime', 'datetime-local', 'search', 'url', or 'tel' to be valid typeable elements.

Edit 4:

So far this has worked best:

Cypress.Commands.add('setSelectOption', ({ selector, option, value }) => {
  cy.get(selector)
    .find('.css-10nd86i input:text')
    .focus()
    .type(option, { force: true, delay: 600, timeout: 330000 })
    .type('{enter}', { force: true })
  cy.get(selector)
    .find('.css-10nd86i')
    .find('input')
    .eq(1)
    .should('have.value', value)
})

At least it works for short lists. Text is entered slowly. For our species list (7000 long) I added those delay and timeout options. Delay seems to help but I have not been able to understand exactly how these options influence behaviour. And sometimes cypress times out :-(

Edit 5:

Meanwhile (react-select v3.0.4, cypress v3.3.2) all tests fail because:

Expected to find element '.css-10nd86i' but never found it

I guess the class has changed. Not surprising with such a brittle solution :-(

Willette answered 7/3, 2019 at 14:59 Comment(2)
You should write what your cypress version isAbandoned
And what version of react-selectAbandoned
A
21

On Cypress 4.2.0 and react-select 3.0.8 pressing enter twice is working for me:

cy.get('#react-select-component-id').type('Something{enter}{enter}');
Aspergillosis answered 18/3, 2020 at 16:44 Comment(3)
one enter was sufficient in my caseBurrill
If you are filing out multiple react-select dropdowns in a row, it was helpful for me to add a slight delay with .type('Something{enter}{enter}', { delay: 100 })Gallardo
Very helpful! I ended up using {downArrow}{enter} in my type command to trigger the selection.Misadventure
P
15

in the latest version of react-select you can set classNamePrefix property.

Quote from react-select docs:

If you provide the classNamePrefix prop to react-select, all inner elements will be given a className based on the one you have provided.

For example, given classNamePrefix="react-select", the DOM would roughly look like this:

<div class="react-select">
  <div class="react-select__control">
    <div class="react-select__value-container">...</div>
    <div class="react-select__indicators">...</div>
  </div>
  <div class="react-select__menu">
    <div class="react-select__menu-list">
      <div class="react-select__option">...</div>
    </div>
  </div>
</div>

Using this trick you can easily find react-select components in Cypress using class selectors. For example, here is the code snippet to select first option:

cy.get('.react-select__control') // find react-select component     
   .click() // click to open dropdown
   .get('.react-select__menu') // find opened dropdown
   .find('.react-select__option') // find all options
   .first() 
   .click() // click on first option

Also you can explore cypress tests in the official github repository of react-select.

Plenty answered 11/9, 2020 at 10:6 Comment(2)
this actually worked by adding eq(1) after get('.react-select__control') .Theodora
My solution was not exactly this, but you have us the workflow to do it possible, thank you!Quality
A
8

Unfortunately, you are running into two Cypress bugs, which are fixed in pending releases

  • 1) an input that already has focus is being validated before .type, which is wrong

  • 2) when the browser is out of focus chrome does not fire blur/focus events, which react-select relies on. Due to this bug, you won't see the dropdown appear when the chrome window is not focused.
    The next version of Cypress will polyfill these events, fixing this problem.

    workarounds:

    for 1) you'll have to use {force:true} during the .type (see below)

    for 2) you can either make sure to have the window focused when running the test, or see the workaround in the code below

it('react-select', () => {
  cy.visit('https://react-select.com/creatable')
  cy.get('.css-10nd86i input').eq(1) // get the first react-select input
    .focus() // workaround for bug #2
    .type('Ocean', {force:true}) // workaround for bug #1
})

another example:

it('react-select', () => {
  cy.visit('https://react-select.com/creatable')
  cy.get('.css-10nd86i').eq(1)
    .click() // click on the react-select div
    .find('input').focus() // workaround for bug #2
  cy.contains('Ocean').click({force:true}) // workaround for bug #1
  // Other actions to take 👇
  // cy.focused().type('{downarrow}{enter}', {force:true}) 
  // cy.focused().type('{enter}', {force:true})
  // cy.focused().type('Ocean', {force:true})
})
Abandoned answered 7/3, 2019 at 17:31 Comment(21)
I had to add an .eq(1) after find because there seem to be two inputs. But that worked! Thanks a lotWillette
Uups. Does not work reliably: cy.contains('Ocean') findes Ocean anywhere on my page. In my case elements of the nav tree get clicked. Is there a way to ensure it only searches in the dropdown list?Willette
You can use a combination of .closest() and .find() to traverse the Dom from your react-scripts elementAbandoned
Yes. But what do I have to search for?Willette
There's a lot of answers to that, I would suggest inspecting the Dom, .closest() to the common ancestor element, then .contains('Ocean')Abandoned
This is where I fail because I can't inspect the dropdown list because it keeps disappearingWillette
Try .find('input').focus().debug()Abandoned
Make sure you have F12 devtools openAbandoned
I have spent too much of my and your time (thanks for that!) on this. Even if I get it to work correctly it would probably be a very brittle solution that can break at any update of react-select because it depends on undocumented features. I give up, hoping for better solutions in the future (maybe when those bugs are crushed).Willette
Why not try typing instead of click?Abandoned
I can't type because the inputs are hidden and cypress can not type on hidden inputsWillette
That's why you use {force:true} as the answer demonstratesAbandoned
force:true did not help. Please see Edit 3 in my questionWillette
Ah! You are using Cypress version < 3.0.3 aren't you? Prior to that, we use type attribute instead of type getter, so we don't know your input is type=text, which it is. Try either: .attr('type', 'text').type(), or upgrade CypressAbandoned
Well then you might be using a different version of react-select than the demo I postedAbandoned
react-select is 2.4.1Willette
Can you try: find('.css-10nd86i input:text')?Abandoned
That works for some fields: Only for those with few dropdown items. I have a field with 7000 items to choose from and here i get: Error: Cypress command timeout of '4300ms' exceeded.Willette
tried to adjust using the delay and timeout options of the type command. The command timeout seems to grow with the delay option but does not seem to depend on the timeout option. This has not worked wellWillette
Thanks! This worked within a complex environment where I didn't think there would be a clean solution.Berriman
I stuck an id on a parent then selected via "#my-id input". to check the value I used docs.cypress.io/faq/questions/…Evidently
M
7

I got this solution here:

"cypress": "^3.4.1"
"react-select": "^2.4.3"
cy
 .get('[class*="-control"]')
 .click(0, 0, { force: true })
 .get('[class*="-menu"]')
 .find('[class*="-option"]')
 .eq(2)
 .click(0, 0, { force: true })

Maryn answered 4/9, 2019 at 17:40 Comment(3)
I had to change .find('[class*="-option"]') to .find('[id*="-option"]') otherwise it worked for me.Staggers
Nice 😀 @StaggersMaryn
This solution is working for me, Thanks bt same as @Staggers I had to change class in to idBiddick
Z
3

I found a simple solution to same problem:

-> cy.get('#basic_category').click()    //getting id of dropdown by cy.get() and using click()
-> cy.contains('Test Category2').click()   //typing name of what you want to pick from dropdown in cy.contains() and using click()
Zootechnics answered 6/7, 2021 at 14:11 Comment(1)
I honestly thought it won't work, but it did! I like this solution. THank you!Joelynn
Z
2

You have to click first to open the react-select dropdown, and then click on the actual element you want to open. We use this syntax for that:

cy.get('.s3p-react-select__indicator').eq(1)
  .click()
cy.get('[id^="react-select-"]').contains('<value_dropdownbox>')
  .click()

And in does indeed use hidden input fields. To find the hidden input field open your developertools, choose elements and look for "input[type='hidden']".

At last an answer on your latest question:

It also does not help that I do not know how to inspect the actual dropdown list in dev tools because it does not stay open.

Try downloading the React select developer plugin for Chrome: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi

If you have that open your Chrome developer tools, hit the React tab. Now click right on your React element and choose inspect element. You see all options you can do for that element. But probably it wont select the correct element at once, so look for the element with checkbox "menuIsOpen", and check it. The dropdown will stay open until something changes on the page

Ziska answered 7/3, 2019 at 15:5 Comment(0)
G
2

Pass the ID to react-select, and find it like:

    cy.get('#country').within($el => {
    cy.wrap($el)
        .click()
        .find('div[tabindex*="-1"]')
        .first()
        .click();
    });

So, first query within the element, wrap it and fire the click event, then just use one of the data props that every element has, I went with tabindex -1 since every element has it, use eq(x) if you need specific element.

Garrik answered 30/9, 2019 at 12:37 Comment(0)
M
2

This worked for my State dropdown that has a data-testid used for React Testing Library:

cy.get("[data-testid='shippingAddress.state']").type("California{enter}");

Using:

"@testing-library/react": "11.2.7"
"react-select": "3.2.0"
"cypress": "6.9.1"
Motorize answered 11/8, 2021 at 18:8 Comment(0)
K
1

Here is my solution. It is a bit generalized, but seems to be working fine for me. Mine does not rely on classes because as soon as they re-render they will probably be different so they will fail.

My solution just looks for tag types which there should only be one input:text type anyway and you should be looking for a unique value in the dropdown that wouldn't be in the dropdown regardless.

// command.js
Cypress.Commands.add('setSelectOption', ({ selector, option, value }) => {
  cy.get(selector)
    .find('input:text')
    .focus()
    .type(option, { force: true, delay: 600, timeout: 330000 })
    .type('{enter}', { force: true })
  cy.get(selector)
    .contains(value)
})
    cy.setSelectOption({ 
      selector: '#funding-opportunity-select', 
      option: '00', 
      value: '00 Funding Opportunity Template'
    });

    cy.setSelectOption({ 
      selector: '#funding-cycle-select', 
      option: 'Test Funding Cycle', 
      value: "TPR Test Funding Cycle"
    });
Karriekarry answered 13/4, 2022 at 19:5 Comment(0)
R
1

None of the answers here worked for me. Also, most of them don't follow best practices according to Cypress and react-select documentation. The one I've come up with has the following advantages:

  • As recommended by Cypress, it selects elements by their unique data-cy instead of their class, ID, position, etc.
  • It assigns a unique data-cy to each option with the help of react-select's custom components
  • It browses through the options by emulating clicks rather than keyboard, as it's usually the most common way users will interact with the component (your mileage may vary)

Dependencies in package.json:

"react-select": "^5.5.6",
"cypress": "^7.5.0",

In the testable component/page:

import Select, { components } from "react-select";

// (...)

<Select
  data-cy="input-currency"
  options={[
    { label: "Australian Dollars", value: "aud" },
    { label: "Canadian Dollars", value: "cad" },
    { label: "US Dollars", value: "usd" },
  ]}

  components={{
    Input: props => <CustomInput {...props} />,
    Option: props => <CustomOption {...props} />,
    SelectContainer: props => <CustomSelectContainer {...props} />,
  }}
/>

// (...)

/** 
 * Wrapping the testable elements with [custom components](https://react-select.com/components#defining-components). 
 * This is the only way I found to assign them with a data-cy. 
 */
const CustomInput = (props) => <span data-cy={`${props.selectProps["data-cy"]}-value`}><components.Input{...props} /></span>;
const CustomOption = (props) => <span data-cy={`${props.selectProps["data-cy"]}-option-${props.data.id || props.data.value}`} key={props.innerProps.key}><components.Option {...props} /></span>;
const CustomSelectContainer = (props) => <span data-cy={`${props.selectProps["data-cy"]}`}><components.SelectContainer {...props} /></span>;

In the Cypress test file:

const dataCy = "input-currency";
cy.get(`[data-cy = "${dataCy}-value"]`).should("have.value", "");

const newValue = "usd";
cy.get(`[data-cy = "${id}"]`).click();
cy.get(`[data-cy = "${id}-option-${newValue}"]`).click();
cy.get(`[data-cy = "${dataCy}-value"]`).should("have.value", newValue);
Ratafia answered 27/10, 2022 at 3:16 Comment(0)
F
1

After upgrading to "react-select": "5.7.7" from 3.0.4 and using "cypress": "12.7.0" The input selection by placeholder changed from:

    // react-select @v3
    cy
        .contains('div', 'my::input::placeholder')
        .type('item_in_select{enter}', { delay: 50 });

to:

    // react-select @v5
    cy
        .contains('.react-select__value-container',  'my::input::placeholder')
        .find('input:text')
        .type('item_in_select{enter}', { delay: 50 });

This approach assumes that there no duplicate selects with the same placeholder.

Once you have your value item_in_select selected you can update selection like this:

    cy
        .contains('.react-select__value-container',  'item_in_select')
        .find('input:text')
        .type('another_item_in_select{enter}', { delay: 50 });
Forbiddance answered 8/11, 2023 at 10:0 Comment(0)
B
0

In my case this helped:

cy.get("[for=country]")
        .click() 
        .type("Poland{enter}");

But please remember I'm clicking on label, which nicely focus on react-select input.

Boredom answered 31/1, 2020 at 9:56 Comment(0)
C
0

I use Cypress 4.2.0, I am able to do the following chain commands:

Cypress.Commands.add('selectDropdownlist', (dropDownSelectionId) => {
cy.get('[data-id=bearbeiter]').click({force: true})
.get(`[id="${dropDownSelectionId}"]`).click() //the id or any selector of the value 'Test Tester'
})
Cruiserweight answered 28/3, 2020 at 12:58 Comment(0)
K
0

You can try my code template. In this test I click on input, select option in menu list 3 times. Notice: After each selection of option my menu list closes, so I need to click on input after this.

  it('test react-select', () => {
    cy.get('#react-select-2-input').click().focus()
    cy.get('#react-select-2-option-0').click()
    cy.get('#react-select-2-input').click().focus()
    cy.get('#react-select-2-option-1').click()
    cy.get('#react-select-2-input').click().focus()
    cy.get('#react-select-2-option-2').click()
  })
Kwh answered 18/9, 2020 at 19:2 Comment(0)
C
0

This worked for me:

cy.get('[id="react-select-component"] select > option ').eq(3);

In my case, id is for select component:

<select id="name_select" name="name_select">

<option value="someValue_1"> someOptionText_1 </option>
<option value="someValue_2"> someOptionText_2 </option>
<option value="someValue_3"> someOptionText_3 </option>

</select>

eq(3) => value="someValue_2"; eq(1) header

Colporteur answered 10/12, 2020 at 23:1 Comment(0)
D
0

There was a custom dropdown in a React-based application I was working on. So, I have written the following function to select a value from the dropdown.

// definition
function selectOptionFromDropDown(elDropdown, stOptionElement, stOptionToSelect){
  cy.get(elDropdown).eq(0).click()
  .find(stOptionElement).first().focus()
  cy.contains(stOptionToSelect).click({force:true})
} 
// calling
keywords.selectOptionFromDropDown('[id="dropdown-account-campaigns"]','input', 'Main (GBP)')
Dunagan answered 10/4, 2021 at 11:34 Comment(0)
F
0

If the object is read-only, all you need is to read the object and then select it.

cy.get('XXXXX').click(); cy.contains(YYYY).click({ force: true })

Fellowman answered 21/11, 2021 at 21:44 Comment(1)
There are thirteen existing answers to this question, including a couple of answers with community support. Are you sure your answer hasn't already been provided? If not, why might someone prefer your approach over the existing approaches proposed? Are you taking advantage of new capabilities? Are there scenarios where your approach is better suited?Bruni
M
0
cy.get("[id*='react-select']")
     .first()
     .focus()
     .type('some text in the list{enter}', { force: true });
Misspell answered 26/4, 2022 at 11:9 Comment(0)
B
0

in my company in our react app - menu items in drop down have the following structure: [id='react-select-1-option-2'] - where 1 is the id of the drop-down menu and 2 is the id of the menu option. So here is how I solved this challenge:

cy.get('[data-testid="DropDown"]').click()
cy.get("[id^='react-select-1-option-']").contains('OptionName').click() 
Brayer answered 22/7, 2022 at 22:20 Comment(0)
M
0
react-select version 3.2.0
cypress version 11.1.0

I ended up needing to the inputId on my react select component and then using the following cypress method

cy.get('#my-custom-inputId').type('SomeWords{enter}{enter}', { force: true, delay: 100 });

Mental answered 22/11, 2022 at 19:42 Comment(0)
W
0

I've created this custom command that takes a wrapping class and an option number from the resulting dropdown:

Cypress.Commands.add('reactSelect', (wrapperClass, selectValueNumber) => {
  cy.get(`.${wrapperClass} .select-input-__indicators`).as('select');
  cy.get('@select').click();
  cy.get(`[id^="react-select-"][id$="-option-${selectValueNumber}"]`).click();
})

Then called in my tests as:

cy.reactSelect('my-wrapper-class', valueNumber);

Note the class is on a custom component that wraps the react-select Select component.

Wigan answered 19/4, 2023 at 15:34 Comment(0)
T
0

I tried to find by label text and click on react-select's div and then typing text on body. And surprisingly this works

cy.get("label").contains("Select your country").next().click()
cy.get("body").type("Chad{Enter}")

select needs to be placed right after label. In comparison to other answers this answer is not using page code, instead it focuses only on what user sees.

Taxiway answered 5/7 at 8:25 Comment(0)
A
-1

cy.get(' [name=" Optionwithcheck[] "] ').select( ['Option 2','Option 3'] )

Absorber answered 10/12, 2021 at 4:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.