select react-select dropdown list option using cypress

Alex picture Alex · Mar 7, 2019 · Viewed 11k times · Source

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 :-(

Answer

bkucera picture bkucera · Mar 7, 2019

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