autocomplete method refuses to fire in RSpec test
Asked Answered
B

3

6

I am on Day 4 of trying to get an autocomplete field to fire in an RSpec test. Works super in the browser, it is just incredibly resistant to running in my request specs.


UPDATE: It looks like my RSpec/Capy scripts are running against the dev db instead of the test db. I'm using Pow, so I don't know what to set default_url_options[:host] or Capybara.app_host and Capybara.server_port to. I have a feeling if I fix this, it may work.


The stack is:

  • Rails 3.2.16
  • Capybara
  • RSpec
  • Poltergeist/PhantomJS
  • Pow
  • Zeus
  • Factory Girl

Click links, click buttons, fill_in fields all work great. But when it comes time to get this autocomplete to work, it absolutely refuses to work.

I am using this method:

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

which I found here. The screenshot line is my own. But the line above:

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

returns false. It works like a charm in the browser. I just can't for the life of me figure out why it won't work. I have tried everything I know how. How can I debug this?

The screenshot just shows the page I am expecting, with the field filled in appropriately. I even tested this with a "hello" alert that I inserted into the autocomplete call. Works flawlessly in the browser, but no result at all in the test.

In short, it looks like the following two lines are having no effect:

  page.execute_script %Q{ $("##{field}").trigger('focus') }
  page.execute_script %Q{ $("##{field}").trigger('keydown') }
Bluma answered 29/12, 2013 at 3:45 Comment(7)
Are you running your spec with the js: true flag?Thigmotropism
Yes. Any other Javascript runs fine.Bluma
When I have problems with javascript and headless browsers in my testing env, I temporarly switch to Selenium with firefox or chrome to SEE what happens. When everything works fine, and only phantomjs produces this error, check the generated source code after clicking on the selectors (I think it's something with puts page.html, but I'm not sure)Minimal
This might help: nofail.de/2013/10/debugging-rails-applications-in-developmentThigmotropism
The tests are running against the development db? what? or do you mean that capybara is accessing your running development instance?Thigmotropism
How is your autocomplete implemented? Perhaps it is activated by an event other than focus or keydown.Weevily
It's jQuery's autocomplete. It is indeed activated by focus and keydown.Bluma
A
3

I had a similar problem and even though Capybara's documentation says that have_selector will wait for the AJAX call to complete, it did not work for me.

The following worked in my case:

def fill_in_autocomplete(field_label, field_class, options = {})
  field_id = "##{page.evaluate_script("$('#{field_class}').attr('id')")}"

  selector = "ul.ui-autocomplete li.ui-menu-item a"

  fill_in(field_label, with: options[:with])
  page.execute_script("$('#{field_id}').trigger('focus')")
  page.execute_script("$('#{field_id}').trigger('keydown')")

  sleep(2) # Hack! not sure why the line below isn't working...
  #expect(page).to have_selector(selector, text: options[:with])

  page.execute_script("$('#{selector}').click()")
end

You can call the method above like this:

fill_in_autocomplete('Some label', '.js-some-field', with: 'Some value'

I didn't want to pass the field ID and opted for a class instead, which is why the first line in the helper gets the ID based on the element's class.

Audraaudras answered 4/2, 2014 at 2:24 Comment(0)
M
0

This kind of asynchronous call is really tricky to troubleshoot. As Xaid suggested, sometimes just putting in an arbitrary sleep suffices to get the job done.

I've found in my own coding that what typically happening is that though Capybara tries to intelligently wait for the AJAX to do its magic, something is causing Capybara to believe the conditions are met for it to continue when you're not actually ready for it to do so.

Digging into the code for Capybara, you can find that the delays around asynchronous behavior are accomplished with the synchronize wrapper function that's part of the Node::Base class. (See here, if you're curious how it actually works: https://github.com/jnicklas/capybara/blob/master/lib/capybara/node/base.rb#L73)

Essentially, it's doing a series of wait commands for you (0.05 second each time) and trapping any "I can't find that element" exceptions until the timeout (defaulted at 2 seconds) has passed. If for any reason, the element you're waiting on DOES exist before you expect it to, it will stop waiting and move on.

Dumping out the HTML of the page as it exists in the headless browser can be a great troubleshooting trick. Try inserting a puts page.body into your code before your screenshot code. That should output the HTML for the page. Does the code include the ul.ui-autocomplete li.ui-menu-item a where you expect it to be? How about after that delay Xiad suggested. Does it exist then?

For really sticky problems, I'll add the pry gem to my project and insert a binding.pry into the code. That lets me play with the browser from within the test to see what it's seeing. Granted, with timing issues, I typically can't act fast enough to see what it's seeing, but even that's a clue.

Hopefully, these troubleshooting tips are helpful.

Maneater answered 4/2, 2014 at 2:56 Comment(0)
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.

Gene answered 17/11, 2014 at 17:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.