Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rails3-jquery-autocomplete: how to test with RSpec and Capybara?

I'm using Rails 3.2.8 with

  • gem 'jquery-rails', '2.1.2'
  • gem 'rails3-jquery-autocomplete', '1.0.9'
  • gem 'rspec-rails', '2.11.4'
  • gem 'capybara', '1.1.3'
  • gem 'capybara-webkit', '0.12.1'

I've recently converted a select drop-down to an autocomplete (using rails3-jquery-autocomplete) and am trying to update my request specs to fill in the autocomplete.

My approach has been to create a helper as shown in this gist:

def fill_autocomplete(field, options = {})
  fill_in field, :with => options[:with]

  selector = "ul.ui-autocomplete a:contains('#{options[:select]}')"

  page.should have_selector selector
  page.execute_script "$(\"#{selector}\").mouseenter().click()"
end

If I test that with Webkit, the test fails with this message:

expected css "ul.ui-autocomplete a:contains('Firstname Lastname')" to return something.

If I test with Selenium, as I watch the test proceed in Firefox, I do not see the drop-down pick list appearing. Adding a sleep 3 to wait for the drop-down doesn't help.

I see here that there is a related but somewhat old issue with capybara-webkit, something about when a field is filled in, it is immediately blurred, which would of course prevent the autocomplete pick list from appearing. But that doesn't seem to jibe with others who have gotten this to work.

Is it currently possible to test a form that includes a rails3-jquery-autocomplete field with RSpec and Capybara? How? If not, is there a way to manually fill in the corresponding, hidden id field so I can still test that the form creates the correct data?

like image 399
Mark Berry Avatar asked Nov 02 '12 00:11

Mark Berry


4 Answers

Finally got autocomplete tests working at least with the Selenium driver.

The solution is to put focus on the field and issue a keydown event.

I confirmed this first with manual testing in a browser. If I use the mouse (not Ctrl-V) to paste a search term into an autocomplete field, nothing happens--no drop-down pick list is displayed. This is apparently the equivalent of what happens when Capybara sends a fill_in command. However, after the term is in the field, while focus is still on the field, if I touch any key, e.g. the Shift key, the pick list appears. So apparently the pick list only appears when a key is pressed.

Option 1

One solution is to extend the function from the original question as follows:

def fill_autocomplete(field, options = {})
  fill_in field, :with => options[:with]

  page.execute_script %Q{ $('##{field}').trigger("focus") }
  page.execute_script %Q{ $('##{field}').trigger("keydown") }
  selector = "ul.ui-autocomplete a:contains('#{options[:select]}')"

  page.should have_selector selector
  page.execute_script "$(\"#{selector}\").mouseenter().click()"
end

and then call it like this:

fill_autocomplete "to_contact_name", with: "Jone", select: "Bob Jones"

Option 2

A similar approach, adapted from the Steak test in the main rails3-jquery-autocomplete gem, uses the standard fill_in, followed by this choose_autocomplete_result:

def choose_autocomplete_result(item_text, input_selector="input[data-autocomplete]")
  page.execute_script %Q{ $('#{input_selector}').trigger("focus") }
  page.execute_script %Q{ $('#{input_selector}').trigger("keydown") }
  # Set up a selector, wait for it to appear on the page, then use it.
  item_selector = "ul.ui-autocomplete li.ui-menu-item a:contains('#{item_text}')"
  page.should have_selector item_selector
  page.execute_script %Q{ $("#{item_selector}").trigger("mouseenter").trigger("click"); }
end

which is called like this:

fill_in "to_contact_name", :with => "Jone"
choose_autocomplete_result "Bob Jones", "#to_contact_name"

I've adopted the second approach for my tests. It seems pretty reliable with Selenium but doesn't work with webkit, which is too bad, since the Selenium tests are quite slow by comparison. A solution that works under webkit would be welcome!

like image 167
Mark Berry Avatar answered Nov 17 '22 10:11

Mark Berry


I myself bumped into the same pain spot too. After spending few hours on this, I have one good helper that works both with selenium and polstergeist plus no usage of sleep(). The following code has been tested with Capybara 2.1.0:

  def fill_autocomplete(field, options = {})
    fill_in field, with: options[:with]

    page.execute_script %Q{ $('##{field}').trigger('focus') }
    page.execute_script %Q{ $('##{field}').trigger('keydown') }
    selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains("#{options[:select]}")}

    page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')
    page.execute_script %Q{ $('#{selector}').trigger('mouseenter').click() }
  end

Basically, I tell Capybara to fill in the input field then use JS to trigger the keydown event to activate autocomplete. However instead of sleep(), I take advantage of page.should have_selector('ul.ui-autocomplete li.ui-menu-item a') that wait till the dropdown list appeared. Then I use JS to trigger the mouseenter event then click. I wish that there are better way than doing things with JS eval, but this is the most reliable solution that I could come up.

like image 43
Trung Lê Avatar answered Nov 17 '22 10:11

Trung Lê


If anyone needs some code for inspiration (took me a while even after reading this thread), this works:

      context "with 5 items results" do
        before do
          5.times { FactoryGirl.create(:item, title: "chinese " + random_string(5) ) }
          page.execute_script("$('input#search_param').val('chinese')")
          page.execute_script("$('input#search_param').trigger('focus')")
          page.execute_script("$('input#search_param').trigger('keydown')")
        end
        specify { page.evaluate_script("$('input#search_param').val()").should == 'chinese' }
        specify { page.should have_css "#autocomplete-menu li", count: 5 }
      end

Thanks Mark Berry for the inspiration.

like image 27
Adam Waite Avatar answered Nov 17 '22 09:11

Adam Waite


Another option is the capybara send_keys method:

find('#my_object_id').send_keys 'bob'

I prefer this option over injecting javascript to fire keydown/focus/mouseenter events.

like image 1
Jay Prall Avatar answered Nov 17 '22 09:11

Jay Prall