React-select: Using Capybara to interact with react-select?

Created on 14 Mar 2016  路  32Comments  路  Source: JedWatson/react-select

While I understand there are a number of ways to approach testing applications using React, I've decided to do integration testing using Capybara. Has anyone had any luck interacting with the react-select component using Capybara? If so, how did you go about doing this?

btw, this is an awesome, flexible replacement for drop downs in React. Thanks to the author/contributors!

categorquestion categortesting

Most helpful comment

I have success with Capybara by assigning the id to react component 'user_role', then execute the following code to perform manual clicks:

find('#user_role').click
within('div.Select-menu') do
  find('div.Select-option', text: 'admin').click
end

All 32 comments

+1

+1

Was just struggling to click the select in a test using react testutils for a while, and finally got it. The trick was:

Update this package to the latest version (1.0.0-beta12). We couldn't get anything to work on 1.0.0-beta9 which we were on before.
Focus on the input with selector: ".Select-control input"
Click on the div with select: ".Select-control"

Can't say if this will work for Capybara but it might.

I have success with Capybara by assigning the id to react component 'user_role', then execute the following code to perform manual clicks:

find('#user_role').click
within('div.Select-menu') do
  find('div.Select-option', text: 'admin').click
end

Does anyone have a solution in case there is a need to type at least one letter to see the available options?

This is the solution I came up with for Capybara + Poltergeist

I was trying to type values into the input with capybara. If I wrapped my select in a wrapper div with the id #wrapper_id, then this worked for me:

input = find('#wrapper_id').find('input')
input.set('My Value')
input.native.send_keys(:return)

Building on @wafendy's answer.

I passed the Select component a className value of input-customer.

Then in Capybara I interacted with it the following ways (using release version1.0.0-beta14).

Select an option

find(".Select.input-customer").click
within(".Select.input-customer") do
  find(".Select-option", text: "Customer Name").click
end

Verify existing value

within(".Select.input-customer") do
  expect(find(".Select-value-label").text).to eq("Custome Name")
end

Zero out a value

within(".Select.input-customer") do
  find(".Select-clear").click
end

@robwise Your link leads to localhost:3000, this seems to be the correct one https://robwise.github.io/blog/testing-react-select-with-capybara

@RKushnir oops, fixed, thank you!!

Thanks all for the help... My final method for making/selecting a Creatable tag. (You need to pass something to className to differentiate it).

  def fill_in_react_select(selector, with:)
    fix_overlap = %{
      var elements = [].slice.call(document.getElementsByClassName('Select-placeholder'))
      elements.forEach(function (element) {
        element.style.zIndex = -99999
      })
    }
    page.execute_script(fix_overlap)
    container = find(selector)
    input = container.find('.Select-input')
    input.send_keys with
    input.native.send_keys :return
  end
fill_in_react_select '.MyCapybaraReference', with: 'my fun test string'

Using the same solution as @wafendy @chrislaskey , but it seems to only work if the browser window is focused, so we fail on CI. :(

Just came across this- thought I'd help out with our code.

The following code works with Chrome and Poltergeist:

def react_select(selector, label)
  within selector do
    find('.Select-control').click
    expect(page).to have_css('.Select-menu-outer') # menu should be open now
    find('.Select-input').set label # set the value
    # This is a little funky because when the entered text forces the select to
    # wrap, it causes React to re-render.  We need to get it to re-render
    # (if needed) by hovering.
    expect(page).to have_css('.Select-option', text: label)
    find('.Select-option', text: label).hover
    expect(page).to have_css('.Select-option', text: label)
    find('.Select-option', text: label).click
    expect(page).to have_css('.Select-value-label', text: label)
  end
end

def react_deselect_multi(selector, label)
  within selector do
    node = find('.Select-value-label', text: label)
    node.parent.find('.Select-value-icon').click
    expect(page).to_not have_css('.Select-value-label', text: label)
  end
end

def react_deselect_single(selector)
  within selector do
    find('.Select-clear-zone').click
    expect(page).not_to have_css('.Select-clear-zone')
  end
end

These are battle-tested methods and have proved very reliable to us.

actually i using react-select-plus, but i think it's same with react-select.

try this:
find('.Select-input input').set('your_option_will_chosen').send_keys :tab

i just imagine like typing manually on field react-select then click 'tab' from my keyboard.
it's work for me (for capybara) with one option react-select.

For anyone else using Select.Async, find('.Select-input input').set(option_text).send_keys(:tab) won't work because it will send the key before the options are done loading. Also, send_keys(:tab) only works when you searchable set to true since without it, find('.Select-input input') won't exist. This solutions works for both non async and async dropdowns.

  def choose_from_react_select(selector, option_text)
    within(selector) do
      find('.Select-input input').set(option_text)

      # If the react select loads options asynchronously, this ensures
      # that the options are done loading before selecting the option.
      expect(page).not_to have_css('.Select-loading-zone')
      expect(page).to have_css(".Select-option", text: option_text)

      find('.Select-input input').send_keys(:tab)
    end
  end

@mcfiredrill Did you ever find a solution for the "only works if the browser window is focused" issue? I'm just working on some integration tests at the moment and, as you describe, they only work when the window has focus. /cc @wafendy @chrislaskey

I ran into a bit of an issue where the first "tag" (React-Select Creatable component frontend to represent tags from acts-as-taggable-on backend) would not get created using the #set and send_keys methods. I imagine it has something to do with the combination of React-Select + Selenium, but didn't have time to investigate. Here was my workaround.

  def creatable_tags_field
    find('.creatable-tags') # custom name given to the react component.
  end

  def fill_in_tags(*tags)

    # BUG: Workaround for Capybara/Selenium + React-Select issue.
    # First tag does not get created, but subsequent ones do.
    creatable_tags_field.find('input').send_keys(' ')
    creatable_tags_field.find('input').send_keys(:tab)

    tags.each do |tag|
      within creatable_tags_field do
        find('input').send_keys(tag)
        find('input').send_keys(:tab)
        find('.Select-value-label', text: tag)
      end
    end
  end

We are using react-select (version ^2.0.0-beta.2) with a class name prefix (see prop classNamePrefix).

Below a basic helper that works fine for us (based on @asselstine one):

def react_select(selector, class_prefix, selected_option)
  # Open the options menu
  r_select = find(selector)
  r_select.click
  expect(r_select).to have_css(".#{class_prefix}__menu")

  # Select the option
  r_select.find(".#{class_prefix}__option", text: selected_option).hover
  r_select.find(".#{class_prefix}__option", text: selected_option).click
end

and the select component looks like:

<Select
  className="selector className"
  classNamePrefix="prefix className"
  ...other props
/>

We are using react-select version 2.2.0 and having trouble selecting options via capybara. Is there any official documentation on what selectors and classes to target for interaction?

Update. I was able to get single selection working with this:

# NOTE: selection is the text value of the select option, typically the option label.
def select_from_react_select(selector, selection)
    react_select = find(selector)
    react_select.click

    within(selector) do 
      selected_option = find(:xpath, "//*[contains(@id, '-option-') and contains(string(), '#{selection}')]")
      selected_option.click
    end
  end

Having the same issue with react-select-2, does anyone know what is the id or class of the options that can be uses as a selector?

Isn't that what the comment above yours is demonstrating?

@robwise Hello, in @EOengineer's solution, it's not clear to me what the @id's value is. Also, I think he used two contains() to match the css, which doesn't necessarily mean that the format of the selector is "#{@id}-option-#{selection}". It would be nice if there is a general selector template that we can use in automated tests to interact with the select options.

@moyuanhuang the ids that gets generated for options within a V2 react-select look like:

react-select-#{react-select-instance-counter}-option-#{index-of-option}

react-select-instance-counter is an integer that seems to tally the number of selects on the page, oddly enough starting at 2.

For example, I have 3 react-select instances on my page for filtering purposes.

The first select's options have ids like:
react-select-2-option-0
react-select-2-option-1

The second select's options have ids like:
react-select-3-option-0
react-select-3-option-1.

If you have a single select instance on the page, you should be able to use:
find(:xpath, "//*[contains(@id, 'react-select-2-option-') and contains(string(), '#{selection}')]") for greater specificity.

I hope that helps.

@EOengineer That's great explanation, love it and thanks! 鉂わ笍

@EOengineer FWIW, I got to inspect the dropdown options locally, in my case, I was using the grouped option feature, and the resulting css turned out to be id="react-select-2-option-0-0", which I believe is in the format of react-select-2-option-#{gorup}-#{id}.

@davidhan527 's answer above for Async saved me. Only change was doing send_keys(:return) at the end vs :tab

Works for react-select 3.0.8:

def react_select_autocomplete(selector, item_text)
  input_selector = "#{selector}__input"
  menu_list_selector = "#{selector}__menu-list"

  find(input_selector).click

  within menu_list_selector do
    find('div', text: item_text).click
  end

  expect(page).not_to have_selector menu_list_selector
end

For anyone wondering how to inspect the html of the dropdown, pass:

        menuIsOpen  = {true}

You'll see the html is like:
image

My solution was this capybara helper:

  def select_react_select_option(containing_selector, option_text)
    find(containing_selector).click

    # react-select > 3
    find("#{containing_selector} div[class^=' css'][class$='option']", text: option_text).click
    # react-select <= 2.4
    find("#{containing_selector} div[class^='css'][class$='option']", text: option_text).click
  end

This takes advantage of the fact the classes for the options are of the form "css-xyzxyz-option" using class^ to match the beginning of the class and class$ to match the end.

Call like this:

select_react_select_option('#select_company_role', 'Agency')

Make sure to pass the id to Select

id={select_company_role}

Update: Dec 2020, upgraded to react-select 3.1.1 and needed to update my selector above to

div[class^=' css']

with a space before the css. Probably unstable for the future, but this is what we got.

After upgrading to Capybara from 2.18 to 3.30 I get the following error:

Selenium::WebDriver::Error::ElementClickInterceptedError: element click intercepted: Element <div class="react-select__input" style="display: inline-block;">...</div> is not clickable at point (108, 298). Other element would receive the click: <div class="css-dvua67-singleValue react-select__single-value">...</div>
  (Session info: headless chrome=76.0.3809.100)
  (Driver info: chromedriver=76.0.3809.126 (d80a294506b4c9d18015e755cee48f953ddc3f2f-refs/branch-heads/3809@{#1024}),platform=Linux 5.0.0-37-generic x86_64)

  0) Currency Balance Sheets Listing filtering allows to filter by client
     Failure/Error: find('.react-select__input', match: :first).set label # set the value

     Selenium::WebDriver::Error::ElementClickInterceptedError:
       element click intercepted: Element <div class="react-select__input" style="display: inline-block;">...</div> is not clickable at point (108, 298). Other element would receive the click: <div class="css-dvua67-singleValue react-select__single-value">...</div>
         (Session info: headless chrome=76.0.3809.100)
         (Driver info: chromedriver=76.0.3809.126 (d80a294506b4c9d18015e755cee48f953ddc3f2f-refs/branch-heads/3809@{#1024}),platform=Linux 5.0.0-37-generic x86_64)

# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/selenium/nodes/chrome_node.rb:46:in `rescue in click'
# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/selenium/nodes/chrome_node.rb:39:in `click'
# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/selenium/node.rb:337:in `set_content_editable'
# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/selenium/node.rb:87:in `set'
# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/node/element.rb:121:in `block in set'
# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/node/base.rb:83:in `synchronize'
# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/node/element.rb:121:in `set'
# ./spec/pages/react_select.rb:16:in `block in react_select'
# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/session.rb:345:in `within'
# /home/matias/.rvm/gems/ruby-2.5.7@foo/gems/capybara-3.30.0/lib/capybara/dsl.rb:51:in `block (2 levels) in <module:DSL>'
# ./spec/pages/react_select.rb:12:in `react_select'
 ```

Has anyone run into this yet? I am using the following code:

frozen_string_literal: true

Extracted from https://github.com/JedWatson/react-select/issues/832#issuecomment-276441836

module ReactSelect
REACT_SELECT_CONTROL_CLASS = '.react-select__control'
REACT_SELECT_MENU_CLASS = '.react-select__menu'
REACT_SELECT_OPTION_CLASS = '.react-select__option'
REACT_SELECT_SELECTED_VALUE_CLASS = '.react-select__single-value'

def react_select(selector, label)
within selector do
find(REACT_SELECT_CONTROL_CLASS).click
return unless has_selector?(REACT_SELECT_MENU_CLASS) # menu should be open now

  find('.react-select__input').set label # set the value
  # This is a little funky because when the entered text forces the select to
  # wrap, it causes React to re-render.  We need to get it to re-render
  # (if needed) by hovering.
  return unless has_selector?(REACT_SELECT_OPTION_CLASS, text: label)

  find(REACT_SELECT_OPTION_CLASS, text: label).hover
  return unless has_selector?(REACT_SELECT_OPTION_CLASS, text: label)

  find(REACT_SELECT_OPTION_CLASS, text: label).click
  return unless has_selector?(REACT_SELECT_SELECTED_VALUE_CLASS, text: label)
end

end
end
```

Captura de pantalla de 2020-01-20 12-08-44
Captura de pantalla de 2020-01-20 12-08-27

React-Select version: 2.4.4

I have added the data-testid to the component, which is used in react-testing-library too.
Now I do not have to add the id attribute

components={{
  Control: props => (
    <components.Control {...props} innerProps={{ ...props.innerProps, 'data-testid': testid }} />
  )
}}
find("[data-testid=\"#{testid}\"]").click

seen here: https://github.com/JedWatson/react-select/issues/3149#issuecomment-447425180

Hi all,

Thank you everyone who had a part in addressing this question.

In an effort to sustain the react-select project going forward, we're closing issues that appear to have been resolved via community comments.

However, if you feel this issue is still relevant and you'd like us to review it or have any suggestions - please leave a comment and we'll do our best to get back to you, and we'll re-open it if necessary!

Hi all, figured I'd show what worked for me with an async react-select.

find('.react-select__control').click
find('.react-select__control').native.send_keys(text[0])
expect(page).to have_content(text)
within('.react-select__menu') do
  find('.react-select__option', text: text).click
end

You'll need to add this prop to your select classNamePrefix="react-select".

react-select v3.1.0
cuprite v0.11

Was this page helpful?
0 / 5 - 0 ratings