Skip to content

Instantly share code, notes, and snippets.

@thijsc
Created November 24, 2011 11:08
Show Gist options
  • Save thijsc/1391107 to your computer and use it in GitHub Desktop.
Save thijsc/1391107 to your computer and use it in GitHub Desktop.
Select item from chosen js select with Capybara and Selenium
def select_from_chosen(item_text, options)
field = find_field(options[:from])
option_value = page.evaluate_script("$(\"##{field[:id]} option:contains('#{item_text}')\").val()")
page.execute_script("$('##{field[:id]}').val('#{option_value}')")
end
@michael-harrison
Copy link

Great work. I've taken a different route to this. Rather than change the hidden select then do the update I've chosen to simulate user interaction. I've tested it with Webkit and Selenium and it works happily. You can check it out here: https://gist.github.com/4102026

@kelso
Copy link

kelso commented Jan 17, 2013

Thank you for this snippet! Awesome.

@bitterloa
Copy link

Yeeees!! :-)

@egjiri
Copy link

egjiri commented Feb 14, 2013

You can also overwrite Capybara's own select method and incorporate the above changes.
This way you would be able to target both regular select elements as well as chosen ones.

def select(value, options={})
    if options.has_key?(:from)
        element = find(:select, options[:from]).find(:option, value)
        if element.visible?
            element.select_option
        else
            field = find_field(options[:from])
            option_value = page.evaluate_script("$(\"##{field[:id]} option:contains('#{value}')\").val()")
            page.execute_script("$('##{field[:id]}').val('#{option_value}')")
            page.execute_script("$('##{field[:id]}').trigger('liszt:updated').trigger('change')")
        end
    else
        find(:option, value).select_option
    end
end

@bbuchalter
Copy link

@egjiri, I'm not sure if this is from some other code, but I'm finding that

element = find(:select, options[:from]).find(:option, value)

fails because the select is disabled and won't be returned by the Capybara finder. Did you not encounter this?

@bbuchalter
Copy link

Calling this before using your select helped me:

  def enable_element(id)
    element = find(:xpath, "//*[@id='#{id}']")
    if element["disabled"]
      page.execute_script("$('##{id}').removeAttr('disabled')")
    end
  end

@deepj
Copy link

deepj commented Mar 7, 2013

Anyone didn't work with Poltergeist (Phantom.js) :(

@Dagnan
Copy link

Dagnan commented Apr 1, 2013

@deepj it works for me (poltergeist-1.1.0)

@Dagnan
Copy link

Dagnan commented Apr 1, 2013

Actually I had to update the select list through the command given by @rheaton.

page.execute_script("$('##{field[:id]}').trigger('liszt:updated').trigger('change')")

@curreta
Copy link

curreta commented Apr 11, 2013

Thanks so much for this. I used @rheaton 's snippet, and it ran beautifully.

@jgarber
Copy link

jgarber commented Apr 16, 2013

If using @rheaton's snippet with Capybara 2.1, you will need to pass visible: false to find_field since Chosen hides the field.

@simonmorley
Copy link

+1 to @jgarber for this point. Just wasted days of my life looking for a fix:

field = find_field(options[:from], visible: false)

Fixes the failing tests.

@bbuchalter
Copy link

@tjmcewan

I know it's been a long time, but it seems these two statements you added for supporting from a multiple-select,

page.execute_script("value = ['#{option_value}']\; if ($('##{field[:id]}').val()) {$.merge(value, $('##{field[:id]}').val())}")
option_value = page.evaluate_script("value")

seem to break functionality for a single select if there is a default value selected. Thought I would mention it here since this is still an active thread.

@pduey
Copy link

pduey commented Aug 16, 2013

For capybara 2, visible: true is the default, so this doesn't work as is. Changed the first line to:

field = find_field(options[:from], visible: false)

@tinderfields
Copy link

+1 for pduey's comment I also had to add visible: false

@michaelbrawn
Copy link

Here is an updated version of @Macrow 's chosen_select.rb

# Support for multiple selects (just call select_from_chosen as many times as required):
module ChosenSelect
  def select_from_chosen(item_text, options)
    field = find_field(options[:from], visible: false)
    option_value = page.evaluate_script("$(\"##{field[:id]} option:contains('#{item_text}')\").val()")
    page.execute_script("value = ['#{option_value}']\; if ($('##{field[:id]}').val()) {$.merge(value, $('##{field[:id]}').val())}")
    option_value = page.evaluate_script("value")
    page.execute_script("$('##{field[:id]}').val(#{option_value})")
    page.execute_script("$('##{field[:id]}').trigger('chosen:updated')")
  end  
end

RSpec.configure do |config|
  config.include ChosenSelect
end

@arojoal
Copy link

arojoal commented Aug 24, 2014

Thanks @michaelbrawn!!!!

I'm using cucumber-1.3.16 and rspec-3.0.3 and I've got a problem with

RSpec.configure do |config|
  config.include ChosenSelect
end 

When I run cucumber I get a "undefined method `configure' for RSpec:Module (NoMethodError)".

I've replaced it with "World(ChosenSelect)" and it works!!!! this is how the complete module code looks like:

# Support for multiple selects (just call select_from_chosen as many times as required):
module ChosenSelect
  def select_from_chosen(item_text, options)
    field = find_field(options[:from], visible: false)
    option_value = page.evaluate_script("$(\"##{field[:id]} option:contains('#{item_text}')\").val()")
    page.execute_script("value = ['#{option_value}']\; if ($('##{field[:id]}').val()) {$.merge(value, $('##{field[:id]}').val())}")
    option_value = page.evaluate_script("value")
    page.execute_script("$('##{field[:id]}').val(#{option_value})")
    page.execute_script("$('##{field[:id]}').trigger('chosen:updated')")
  end  
end

World(ChosenSelect)

@nTraum
Copy link

nTraum commented Sep 1, 2014

Can confirm this works like a charm. Thanks a lot! 👯

(RSpec 3, Capybara 2.4.1)

@ralphos
Copy link

ralphos commented Oct 14, 2014

Thanking you!!!

@flexybiz
Copy link

When to use last one with Ember.js got a problem: it doens't update binded values. So just add .change() at the end and it will work as expected.

# Support for multiple selects (just call select_from_chosen as many times as required):
module ChosenSelect
  def select_from_chosen(item_text, options)
    field = find_field(options[:from], visible: false)
    option_value = page.evaluate_script("$(\"##{field[:id]} option:contains('#{item_text}')\").val()")
    page.execute_script("value = ['#{option_value}']\; if ($('##{field[:id]}').val()) {$.merge(value, $('##{field[:id]}').val())}")
    option_value = page.evaluate_script("value")
    page.execute_script("$('##{field[:id]}').val(#{option_value})")
    page.execute_script("$('##{field[:id]}').trigger('chosen:updated')")
    page.execute_script("$('##{field[:id]}').change()")
  end  
end

World(ChosenSelect)

@corporealfunk
Copy link

Without needing to call evaluate_script, this also works with Capybara 2.4.4, Capybara-webkit 1.4.1 and Rspec 3.2.2, using Chosen 1.4.1:

find("#field_id_chosen").trigger("mousedown")
find("#field_id_chosen ul.chosen-results li", :text => "Item Text").trigger("mouseup")

@jonahwh
Copy link

jonahwh commented Mar 31, 2015

For those of you using rails who need to escape javascript when setting option_value the first time.

include ActionView::Helpers::JavaScriptHelper

in the Module/Class where this method lives, and change

option_value = page.evaluate_script("$(\"##{field[:id]} option:contains('#{item_text}')\").val()")

to

option_value = page.evaluate_script("$('##{field[:id]} option:contains(\"#{j item_text}\")').val()")

As an aside, if your select box is prepopulated, and you want to CHANGE the value, you must clear the value of the select box before calling select_from_chosen, or it will attempt to select both values.

@ChunAllen
Copy link

It only works for a single selection of chosen dropdown but not on grouped options chosen select

@osiro
Copy link

osiro commented Aug 24, 2015

Hey guys,

The implementation of @flexybiz above seems to work fine, however if, for whatever reason, I run select_from_chosen two or more times on the same field, this will select multiple options even though my chosen is configured to be a standard select, not a multiple one.

My workaround was creating deselect_from_chosen.

So in case you guys need to deselect fields in your tests, here's how I did it:

  def deselect_from_chosen(field)
    field = find_field(field, visible: false)
    page.execute_script("$('##{field[:id]}').val('').trigger('change').trigger('chosen:updated')")
  end

@thijsc
Copy link
Author

thijsc commented Sep 7, 2015

Funny to find this gist again 4 years later with so much discussion :-). I just needed this again, this one based on the snippet by @corporealfunk works well in Selenium:

def select_from_chosen(item_text, options)
  field = find_field(options[:from], :visible => false)
  find("##{field[:id]}_chosen").click
  find("##{field[:id]}_chosen ul.chosen-results li", :text => item_text).click
end

@idabmat
Copy link

idabmat commented Feb 12, 2016

For deselecting a specific value :

def deselect_from_chosen(item_text, options)
  field = find_field(options[:from], visible: false)
  page.execute_script("$(\"##{field[:id]} option:contains('#{item_text}')\").removeAttr('selected')")
  page.execute_script("$('##{field[:id]}').trigger('chosen:updated')")
end

@brunzino
Copy link

Here's another deselect function that's more in-line with @thejsc's approach. I like this approach because it triggers the change hook.

def deselect_from_chosen(item_text, options)
  field = find_field(options[:from], visible: false)
  find("##{field[:id]}_chosen ul.chosen-choices li.search-choice", :text => item_text).find("a.search-choice-close").click
end

@westonganger
Copy link

westonganger commented Aug 29, 2016

For anyone whos interested in just selecting whichever option is first without having to know the text

def select_first_from_chosen(from)
  field = find_field(from, visible: false)
  find("##{field[:id]}_chosen").click
  first("##{field[:id]}_chosen ul.chosen-results li").click
end

Use like this: select_first_from_chosen('post[name]')

@JeremiahChurch
Copy link

To guarantee an exact match rather than a partial match aka - find 'OR' and not match 'MORE'
replace
the line

find("##{field[:id]}_chosen ul.chosen-results li", text: item_text).click

with

find("##{field[:id]}_chosen ul.chosen-results li", text: /\A#{Regexp.quote(item_text)}\z/).click

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment