21
votes

I have a Select2 element on a page that loads results via ajax. Would like to test this with capybara/rspec (using the poltergeist driver), but since the Select2 element actually starts out as a hidden field, and then populates a <ul> once results are processed, none of the normal select, fill_in or choose helpers will work.

What I have now is something like

  it "should have a search field for events" do
    click_link "Select an Event"       # this works as expected
    within('.select2-container') do    # works
      find('.select2-search input').set(event.description[0,5])  # won't work!
      find(:xpath, "li[text()='#{event.description}']").click 
    end
    click_button "Search"
    page.should have_content("Displaying all 2 photos")
  end

line 4 above, apparently capybara can find the element but the value isn't changed and Select2 never makes its ajax call. (can't use normal fill_in there because the search field element has no label, id, or name attributes)

18
An utterly frustrating problemguero64

18 Answers

9
votes

Stop scratching your head, do this and it will work

select "option_name_here", :from => "Id Of Your select field"

I tried every module and every damn thing I could and finally just did this without any helper and such things by a simple line of code.

8
votes

I created a helper that you can use with Cucumber, it doesn't have a sleep so it's faster than the methods above. This also works if you use the createSearchChoice provided by Select2.

Put it in features/support/select2_helper.rb

module Select2Helper
  def select2(value, attrs)
    first("#s2id_#{attrs[:from]}").click
    find(".select2-input").set(value)
    within ".select2-result" do
      find("span", text: value).click
    end
  end
end

World(Select2Helper)

Use it like this:

Then(/^I select2 "([^"]*)" from "([^"]*)"$/) do |value, select_name|
  select2 value, from: select_name
end

Tested with Rails 4.1.1 and Cucumber 1.3.15.

6
votes

You can use capybara-select-2 gem which will do all the work for you our of the box. Just pass search: true to the helper options to make an Ajax call:

select2 'Marriage', from: 'Event', search: true
4
votes

I was able to solve this by using page.execute_script to manually hack the needed value and keyup event into the search field.

  it "should have a search field for events" do
    click_link "Select an Event"
    page.execute_script("i = $('.select2-container input').first();
                         i.val('#{event.description[0,4]}').trigger('keyup');");
    sleep 2
    find('.select2-results li:first-child').click
    click_button "Search"
    page.should have_content("Displaying all 2 photos")
  end
4
votes

For multiple select2 components on your page, I've created the following helper methods that work for me:

def select2(id, value)
  page.execute_script %Q{
    i = $('#s2id_#{id} .select2-input');
    i.trigger('keydown').val('#{value}').trigger('keyup');
  }
  sleep 2
  find('div.select2-result-label').click
end

def remove_all_from_select2(id)
  page.execute_script %Q{
    $('#s2id_#{id} .select2-choices a').click();
  }
end

def remove_from_select2(id, value)
  page.execute_script %Q{
    $('#s2id_#{id} .select2-choices div:contains("#{value}")').closest('li').find('a').click();
  }
end
2
votes

For those struggling with Select2 multiple:

Strangely I found that select_tag with multiple:true seems keen to generate html for each entry like:

<div class="select2-result-label">
  <span class="select2-match"></span>
  value
</div>

rather than

<div class="select2-result-label">
  <span class="select2-match">value</span>
</div>

Unhelpfully putting the contents outside of the span.

In order to test it with capybara I have two methods:

# for most select2s

def select2_select(value, id)
  # This methods requires @javascript in the Scenario
  first("#s2id_#{id}").click
  find(".select2-input").set(value)
  within ".select2-result" do
    if !find("span").text.empty?
      find("span", text: value).click
    elsif !find("div").text.empty?
      find("div", text: value).click
    else
      fail 'neither span nor div within this select2, did you get the id of the select2 right?'
    end
  end
end


# for select_tag select2s which are multiple:true

def select2_select_multiple(select_these, id)
  # This methods requires @javascript in the Scenario
  [select_these].flatten.each do | value |
    first("#s2id_#{id}").click
    found = false
    within("#select2-drop") do
      all('li.select2-result').each do | result |
        unless found
          if result.text == value
            result.click
            found = true
          end
        end
      end
    end
  end
end

The latter being too hacky for my liking, so I use the first where possible and hope to replace the latter eventually.

HAML used:

= select_tag "some_id", options_for_select(['hello','world']), multiple: true, include_blank: true

js:

$("#some_id").select2();
2
votes

Inspired from this answer and one at Capybara select2 helper I came up with what seems robust for multiple select2 fields of both single-select and multi-select on the same page:

def select2(value, attrs)
  s2c = first("#s2id_#{attrs[:from]}")
  (s2c.first(".select2-choice") || s2c.find(".select2-choices")).click

  find(:xpath, "//body").all("input.select2-input")[-1].set(value)
  page.execute_script(%|$("input.select2-input:visible").keyup();|)
  drop_container = ".select2-results"
  find(:xpath, "//body").all("#{drop_container} li", text: value)[-1].click
end
2
votes

This works for me on a dropdown style select2 with a remote data source:

  def select2(value, from:)
    execute_script("$('##{from}').select2('open')")
    find(".select2-search__field").set(value)
    find(".select2-results li", text: /#{value}/).click
  end

from must be the ID of the regular select tag that the select2 is built from.

1
votes

I have two remote select2s. I search for a string in first one and according to the result found, second one gets filled. After trying capybara-select2 gem and all solutions listed here and there I had to come up with my own solution cause none of them worked for searching. In my case what I select is not important so did not try to select a result label with a specific value. Hope this helps someone in my situation.

Given(/^I have selected  "(.*?)" category$/) do |arg1|  
  page.find(:css, ".select2-choice.select2-default").click 
  page.find(:css, "#s2id_autogen1_search").set "Dip Boya"
  sleep 2
  page.all(:css, '.select2-result-label')[1].click

end

Given(/^I have selected "(.*?)" variation$/) do |arg1|  
  page.find(:css, ".select2-choice.select2-default").click 
  page.all(:css, '.select2-result-label')[1].click
end
1
votes

https://github.com/goodwill/capybara-select2 with js: true specified worked fine for me. Cheers

1
votes

Here is what works for me, even with select2 AJAX autocomplete:

find('#user_id').sibling('span.select2').click
find('.select2-search__field').set('ABCDEF')
find('.select2-results li', text: 'ABCDEF').click
0
votes

Simple answer:

page.find('.select2-drop-active .select2-input').set('foo')
page.find('.select2-drop-active .select2-input').native.send_keys(:return)
0
votes

Hey I am not sure that anybody still cares but I just had this issue as well and I found an easy way to deal with it.

First off, I do not add the class to each drop-down select but rather I do it globally like so:

    class CollectionSelectInput < SimpleForm::Inputs::CollectionSelectInput
      def input_html_classes
        super.push('chosen-select')
      end
    end

So when I started to have issues with capybara not being able to use the select2 drop-down my fix was to only use the global push if not in test environment:

    class CollectionSelectInput < SimpleForm::Inputs::CollectionSelectInput
      unless Rails.env.test?
        def input_html_classes
          super.push('chosen-select')
        end
      end
    end

I used to use the capybara-select2 gem but it seems as though it is no longer being maintained :(

0
votes
def select2(id, value)
  find("##{id} ~ span.select2").click
  within ".select2-results" do
    find("li", text: value).click
  end
end
0
votes

Here's a solution for a more recent version of select2 - specifically the select2-rails (4.0.0) gem:

Place it in features/support/feature_helpers.rb, and then include it in your spec_helper.rb file with config.include FeatureHelpers in your RSpec.configure do |config| block.

module FeatureHelpers
  def fill_in_select2(container_selector, with: '')
    page.execute_script %Q{
      i = $('#{container_selector} .select2-search__field');
      i.trigger('keydown').val('#{with}').trigger('keyup');
    }
    sleep 2
    find('.select2-results__option--highlighted').click
  end
end

Then use fill_in_select2 just as you would use fill_in in Capybara, but include the class or id of a container element with the appropriate prefix symbol. (e.g. fill_in_select2 '.actions_list', with: '[email protected]').

Note: this selects the first element in the dropdown after inputting text.

Tested in Rails 4.2.5.2, Capybara 2.7.1, and Capybara-Webkit 1.11.1

0
votes

After spending longer on this than I'd like, I finally got it working by building on Brandon McInnis' answer using a xpath selector.

module Select2Helper
  def fill_in_select2(container_selector, with: '')
    page.execute_script %Q{
      i = $('#{container_selector} .select2-search__field');
      i.trigger('keydown').val('#{with}').trigger('keyup');
    }
    sleep 2
    find(:xpath, "//body").all("#{container_selector} li", text: with)[-1].click
  end
end
0
votes

For select2 4.0 in combination with capybara-webkit, I tried the execute_script-based solutions but they didn't work because capybara couldn't find the resulting 'li' element. I finally settled on this simple solution based on send_keys. Because I use a remote data source, I added a small sleep to allow the results to appear:

module Select2Helpers
  def fill_in_select2(field_name, with: '')
    field = find("##{field_name}")
    parent = field.find(:xpath, './/..')
    ui = parent.find('.select2-search__field')

    ui.send_keys(with)
    sleep 0.5
    ui.send_keys(:enter)
    sleep 0.1
  end
end

Note that you may need to update to the latest capybara-webkit (1.14.0) to make this work.

0
votes

This helper worked for me (using a placeholder):

  # Add this helper
  def select2(placeholder, option)
    find("select[data-placeholder='#{placeholder}'] + .select2").click
    within '.select2-results' do
      find('span', text: option).click
    end
  end

And call it like so:

select2 'Placeholder', 'some option'