msmith1114
msmith1114

Reputation: 3229

Capybara Select2 Help w/Firefox

For some reason im running into problems with Select2 and Firefox w/Geckodriver.

Select2 fields I used to be able to just say page.select 'Text', from: 'Label' however that no longer works I just get a Element <option> could not be scrolled into view (Despite being scrolled into view). Right now im doing something similar to this:

  select2Fields = page.all('.select2-selection')
  select2Fields[0].click
  page.find('.select2-search__field').set('Text To Set')
  within('.select2-results') do
    page.find('li', text: 'Text To Click').click
  end

It's ugly and doesn't fit with my Page Object Model method, since I have to sorta know which select2 field it is. It doesn't seem to be when finding it with a label.

Any ideas? It's very frustrating since it worked with Chrome but the latest chromedriver has issues with the newest capybara versions.

Upvotes: 0

Views: 209

Answers (2)

Alejo
Alejo

Reputation: 9

I had tested the Thomas´ answer but it doesn´t work for me. When Capybara click in the desired option, the select2 box close itself and set the 0 option. Finnaly, I made a walkaround as I check the option I want as selected and trigger the change.select2 event. I know that I dont really test the select2 box.

    def self.select2 (page, datos)

        page.execute_script("$('##{datos[:from]}').select2('open')")

        if page.find(".select2-results li", text: datos[:texto]).click

            page.execute_script("$('##{datos[:from]} option[value=\"#{datos[:valor]}\"]').prop('selected', true)")
            page.execute_script("$('##{datos[:from]}').trigger('change.select2')")
        end

        page.find(:css, '#' + datos[:from]).value
    end

As I keep my module Helper without include it in tests, I needed to include self in the name of the method and take 'page' from the capybara´test as parameter. The variable 'datos' is a hash with the selector, the text of the option and its value.

As select2 box close when capybara click on it, I wrap the walkaround inside the if clause to be sure that some parts of the select2 box were working.

Finally, I returned the current value of the select to test it (really, it doesnt needed as I set the option with that value as 'selected')

I hope it would help anyone.

Upvotes: 0

Thomas Walpole
Thomas Walpole

Reputation: 49880

Not sure what you were using that you were able to ever use select with a select2 widget, it never should have worked, and the fact it did would have been a bug. The reason is the actual <select> element (which is what Capybaras select method works with) is non-visible on the page, and select2 replaces it with a JS driven widget. You need to do exactly what a user would do, which is click to make the widget show up then click on the <li> element which represents the correct entry. This can all be moved into a helper method and potentially some custom selectors which boils down to something like this

Capybara.add_selector(:select2) do
  xpath do |locator, **options|
    xpath = XPath.descendant(:select)
    xpath = locate_field(xpath, locator, options)
    xpath = xpath.next_sibling(:span)[XPath.attr(:class).contains_word('select2')][XPath.attr(:class).contains_word('select2-container')]
    xpath
  end
end

Capybara.add_selector(:select2_option) do
  xpath do |locator|
    # Use anywhere to escape from the current scope since select2 appends
    # the choices to the end of the document
    xpath = XPath.anywhere(:ul)[XPath.attr(:class).contains_word('select2-results__options')][XPath.attr(:id)]
    xpath = xpath.descendant(:li)[XPath.attr(:role) == 'treeitem']
    xpath = xpath[XPath.string.n.is(locator.to_s)] unless locator.nil?
    xpath
  end
end

def select_from_select2(value, from: nil, **options)
  select2 = if from
    find(:select2, from, options.merge(visible: false))
  else
    select = find(:option, value, options).ancestor(:css, 'select', visible: false)
    select.find(:xpath, XPath.next_sibling(:span)[XPath.attr(:class).contains_word('select2')][XPath.attr(:class).contains_word('select2-container')])
  end
  select2.click
  find(:select2_option, value).click
end

That should let you call select_from_select2 just like you would call select and it will find the select2 widget associated with the given <select> element (hidden by select2) and choose the correct entry from it.

Upvotes: 1

Related Questions