Why can't my Capybara/Poltergeist test select from a jQuery autocomplete field?
Asked Answered
L

5

15

UPDATE: I have fixed this problem after lots of painstaking work on my own. I am happy to be a resource to anybody needing a hand with this. Here is a gist of my working setup.


I have tried every solution I could find Google and SO. Here are some different things I have tried:

page.execute_script %Q{$('#{selector}').val('#{value}').trigger('keydown')}

and

fill_in field, with: options[:with]
page.execute_script %Q{ $('##{field}').trigger('focus') }
page.execute_script %Q{ $('##{field}').trigger('keydown') }

This is what fails:

page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')

But it's definitely there when I look at it in Firebug and test it in the browser.

Here are all of the details, including a restatement of those above. Remember, the autocomplete field works fine in the browser.

listing_integration_spec.rb

require "spec_helper"

describe "Listing Integration" do

  let!(:user) { login_user }

  it "lets a user add information listing", js: true do
    listing = create(:listing, user: user)
    click_link('Additional Information')
    click_link('Create')
    fill_autocomplete('listings_search', with: listing.item_id)
  end

end

spec/support/feature_helper.rb

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[:with]}')}
  page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')
  page.execute_script %Q{ $("##{selector}").trigger('mouseenter').click() }
end

ERB from view template

<%= simple_fields_for :listings  do |f| %>
  <%= f.input :search, label: "Search by Listing", required: true %>
<% end %>

and the Coffeescript:

$("#listings_search").autocomplete
  source: (request, response) ->
    options = 
      term: request.term
    $.get "/search_listings", options, (data) ->
      if data.length == 0
        alert "No listings found."
      response data
  minLength: 2
  select: (event, ui) ->
    add_listing_hash = 
      type: "GET"
      url: "/add_listing"
      data: { id: ui.item.id }
      success: () ->
    $.ajax(add_listing_hash)
Laetitia answered 29/12, 2013 at 21:20 Comment(12)
Can you please post the markup of the page that you're testing, and the complete source code of the test that you're running?Acree
Did you try debugging the issue in the running poltergeist session?Jacynth
I did. It attempts to call the events, but the events are not triggering the autocomplete the way they are supposed to. I wasn't even able to invoke autocomplete directly.Laetitia
Have you tried running the spec using selenium? Obviously a longer spec but it's worth a try. Especially AJAX stuff IMHOTarrasa
Did you try adding some delay between the different steps ? That's dirty, I know, but that may be way to identify a possible race condition.Mesomorph
I added "sleep 5" between all of the steps with no improvement. Please note that I have discovered that the "autocomplete" event is indeed being triggered, and is returning one row of data, but that the text box that autocomplete generates with it's results can't be seen. When debugging this behavior in Poltergeist, I find that Poltergeist uses the full length of the timeout I have it configured with it, then fails to find the ul.ui-autocomplete li.ui-menu-item a selector. Something is making Poltergeist hang up after autocomplete is initiated.Laetitia
Have you tried taking a screenshot in poltergeist at each point to see what poltergeist can see? github.com/jonleighton/…Brune
Have you tried using an array rather than ajax to load the test data? From what I understand of Capybara it should handle async ok, but it would be worthwhile to try and simplify the problem.Brune
I'm definitely screenshotting the heck out of the test. The autocomplete "fires" but is struggling to allow clicks on the results box.Laetitia
@AKWF would you mind sharing your solution with us? I'm running into the same issue.Intelligence
I would also like to see the solution.Maud
Hi guys. Everything had to do with my setup. If your autocomplete is working in the browser, but not in RSpec, here is everything in my setup for you to look at and copy. Hopefully saves you some time. Let me know if anything is missing. gist.github.com/HuckyDucky/10219649Laetitia
C
9

JS drivers are generally meh, they're slow and not single one of them covers 100% of function, and they're often quirky and hard to debug, but I'm sure you've got that figured out by now.

I've got similar piece of code working on rails 3.2, minitest and poltergeist 1.3.0 (an ajaxed dropdown) but it kind of breaks periodically for no good reason (one might say it has a poltergeist? I have already resorted switching that test between selenium and poltergeist a couple times so far), not sure why autocompleter wouldn't work for you but it feels like a bug,

submit issue to https://github.com/jonleighton/poltergeist (you already have? https://github.com/jonleighton/poltergeist/issues/439), try changing to selenium or webkit, see if it works, you can use a different driver in this one test if it gets you out of the woods (it beats losing days of work over a widget that works).

Colpin answered 2/1, 2014 at 18:0 Comment(2)
There are so many moving parts (what events jQuery uses to trigger an autocomplete, PhantomJS, Capybara configuration) that the answer truly is meh. I got as far as having the results HTML generated but failing the mouseenter click.Laetitia
I traced the mouseenter click failure to bad stuff on my part. I also updated all involved gems to the bleeding edge, and I started to see results. The whole thing probably took 48 hours of work to solve. Fortunately it's for my own stuff so no one has to pay except me ;-) As frustrating as this was, I am better for having waded through all the moving parts.Laetitia
F
6

I've found several solutions online, none of which work with current versions of Poltergeist, Capybara, and Autocomplete. But I learned enough from them to make a working helper method, with no sleep calls.

def fill_autocomplete(css_id, page, options = {})
  find("#{css_id}").native.send_keys options[:with]
  page.execute_script %{ $('#{css_id}').trigger('focus') }
  page.execute_script %{ $('#{css_id}').trigger('keydown') }
  selector = %{ul.ui-autocomplete li.ui-menu-item:contains("#{options[:select]}")}
  expect(page).to have_selector('ul.ui-autocomplete li.ui-menu-item')
  page.execute_script %{ $('#{selector}').trigger('mouseenter').click() }
end

Example usage:

fill_autocomplete(
  '#contact_filter_company',
  listing_page,
  with:   'acm',
  select: 'Acme'
)

I have a page argument because I'm using SitePrism - if you're not, you can strip it out.

I'm using this with:

  • jQuery UI Autocomplete 1.11.2
  • poltergeist 1.5.1
  • capybara 2.4.4
  • rspec 3.1.0
Favela answered 24/11, 2014 at 14:43 Comment(1)
What exactly is listing page? The rails helper for the page?Manager
K
3

I was able to test my autocompleting text field with Poltergeist without much trouble. The main thing to know about is Poltergeist's .native.send_keys method.

Hacking together a summary out of the Cucumber steps where these lines of code actually live in my project:

find('#username').native.send_keys "the" # this field autocompletes usernames
wait_until { all('a', text: "the_username").any? }
find('a', text: "the_username").click

Then I submit the form and assert the expected results on the following page in the usual way.

wait_until (a reimplementation of a method which was removed from Capybara 2) takes a block which returns true when we should stop waiting. It's faster than waiting for 5 seconds or whatever every time.

def wait_until(delay = 1)
  seconds_waited = 0
  while ! yield && seconds_waited < Capybara.default_wait_time
    sleep delay
    seconds_waited += 1
  end
  raise "Waited for #{Capybara.default_wait_time} seconds but condition did not become true" unless yield
end
Kubis answered 30/4, 2014 at 1:24 Comment(0)
B
0

I think perhaps you need a mixture of triggering KEYDOWN, but also setting the keycode to DOWN.

e.g.

var keyEvent = $.Event("keydown");
keyEvent.keyCode = $.ui.keyCode.DOWN;  

$("#autocomplete").val("j");       

$("#autocomplete").trigger(keyEvent);

Here is a working jsfiddle example showing an item selected by the autocomplete: http://jsfiddle.net/alexkey/74BST/ I'm not sure why you need to trigger keydown twice, but that's a problem to solve separately (if a problem at all).

However I'm not familiar with the unit testing framework you are using, but I hope the above helps.

Credit goes to JQuery AutoComplete, manually select first searched item and bind click

I'm using the example code form: http://api.jqueryui.com/autocomplete/#entry-examples

The autocomplete unit tests that the jquery-ui team uses may come in useful for inspiration: https://github.com/jquery/jquery-ui/tree/master/tests/unit/autocomplete

Also a reference to the keycode: http://api.jqueryui.com/jQuery.ui.keyCode/

Brune answered 2/1, 2014 at 16:46 Comment(1)
I gave this a shot. The autocomplete is being triggered, but Poltergeist is hanging up on the generation of the results HTML.Laetitia
G
0

I had this problem and no proposed solution could solve it. My tests always failed when trying to find the ul.ui-autocomplete element. I finally noticed, that jQuery autocomplete appends the ul to the end of the html page and NOT to the input field in question. In my spec, I follow the practice of targeting my forms explicitly by within my_form do and doing all the fill_instuff inside this block:

within my_form do
  fill_autocomplete …
end

Of course this could never find the ul attached OUTSIDE this form element. My solution was simple: Use jQuery autocomplete's attribute appendTo: '#id_of_input_field' when initializing autocomplete. Now it can find my uland everything works fine.

Giotto answered 17/11, 2014 at 17:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.