13

I have a Rails integration test that's failing, and I can't figure out why. I'm using Capybara with Selenium as the driver.

The test checks that page content has been removed after an AJAX call takes place. The relevant action is that a button is clicked, and that button click causes a section of the page to be removed via a jQuery remove() call. Here's an approximation of the integration testing code:

click_button("Remove stuff")
assert has_no_link?("This should be removed")

The assertion fails, implying that the link still exists.

I've been reading up on Capybara, and I know that you can extend the default wait time. I've extended it to a ridiculous value (20 seconds), and still the assertion fails.

When I follow the test process myself manually, the source of the page still shows the content, but the DOM does not (by viewing Firefox's DOM Inspector and looking for the element). Is this the issue? I've even tried inspecting the DOM while the tests are running in Firefox to check if the content was there, and it doesn't appear to be.

I have no idea how Capybara is still finding this link that no longer exists in the DOM. Is Capybara examining the source instead of the DOM and finding the link there? If so, I have no idea how to fix this test to make sure that the test passes. Refreshing the page would fix the issue, but that's not exactly what a user would do, so I hesitate to change the page just to make the test pass...

Would love any recommendations on how to approach this problem.

Thanks!

aardvarkk
  • 14,955
  • 7
  • 67
  • 96
  • 1
    Did you finally found a solution to your problem? I have a similar one and I am wondering whether you can be of any help to me. – p.matsinopoulos Oct 14 '12 at 17:14
  • 1
    Nope, unfortunately not. I spent way too much getting that single test to work, so I ended up just commenting it out instead of fixing it. Sorry I can't be of more assistance... I didn't have the energy to deep dive into after spending so much time already! Feel free to answer the question if you figure it out and I'll be happy to give you credit! – aardvarkk Oct 15 '12 at 00:37
  • Did you remember to include `js: true` in your rspec? – RubeOnRails Jan 03 '14 at 20:35

7 Answers7

3

Thoughtbot has a great blog post on waiting for AJAX, which you can read here, though it is based on Rspec, and it looks like you are using TestUnit.

It works great for situations when Capybara doesn't quite wait long enough, but doesn't add unnecessarily long timeouts. I work mostly in Rspec now, but I think you can modify it by doing this:

# Create this file in test/support/wait_for_ajax.rb
module WaitForAjax
  def wait_for_ajax
    Timeout.timeout(Capybara.default_max_wait_time) do
      loop until finished_all_ajax_requests?
    end
  end

  def finished_all_ajax_requests?
    page.evaluate_script('jQuery.active').zero?
  end
end

You can either include it when needed in the individual test file, or use one of the strategies provided in this SO post for automatically including it every time.

Then, whenever you have a test that is not properly waiting for AJAX to finish, just insert the line wait_for_ajax. Using your code as an example:

click_button("Remove stuff")
wait_for_ajax
assert has_no_link?("This should be removed")
Community
  • 1
  • 1
Sia
  • 8,894
  • 5
  • 31
  • 50
1

there was some method called wait_until, but it was deprecated recently and changed with synchronize method.

http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara

https://github.com/jnicklas/capybara/blob/master/lib/capybara/node/base.rb#L44

For now I don't know how to use it exactly, but I'm waiting for answer for my question from the author, so I hope to resolve this problem soonly

0

There's a neat way to check that ajax requests are done, which I learned from this article. Instead of wait with some specific time, you can use the ajax $.active function (which is not in the actual API but is exposed so you can use it). $.active tells you the number of active connections to a server, so when it drops to zero you know the ajax request is done:

wait_until do
  page.evaluate_script('$.active') == 0
end

If that doesn't work, then the issue is somewhere else (which judging from what you wrote seems likely). If the change is only happening in the DOM, then you have to make sure that javascript is enabled for your test/spec. In rspec, for example, you set :js => true to do that; in Cucumber you add a line above the scenario with @javascript. I don't use rails' default tests but there must be a setting to do the same.

Community
  • 1
  • 1
Chris Salzberg
  • 27,099
  • 4
  • 75
  • 82
  • Hmm... I'm using the Selenium driver under Capybara, which by default appears to support JavaScript testing (see https://github.com/jnicklas/capybara#drivers). I tried adding your wait code, but that didn't fix it. Good ideas though! Just not solved yet... – aardvarkk Sep 23 '12 at 23:13
  • Do you use capybara for any other tests that involve javascript? – Chris Salzberg Sep 23 '12 at 23:54
  • Yes, but this is the only one that's looking for *removed* information. All of the other stuff seems to work... For instance, checking that AJAX queries result in new buttons or changed content. But something about jQuery `remove()` doesn't seem to be sitting well. If I examine `page.html`, it still shows the content there, so there's definitely something going wrong here. – aardvarkk Sep 23 '12 at 23:57
  • Oh ok, that's a different problem then. – Chris Salzberg Sep 24 '12 at 00:35
  • This does not seem to work with the current version of capybara/poltergeist: Failure/Error: wait_until do NoMethodError: undefined method `wait_until' for # – justingordon Mar 09 '14 at 20:40
  • `wait_until` was removed: http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara you'll have to add it back to use it. – Chris Salzberg Mar 11 '14 at 09:33
0

Are you first testing something else with the link, and then testing that it is removed?

In other words, is your test something like:

has_no_link = $('#id_for_link')
//.... some test
click_button("Remove stuff")
assert has_no_link?("This should be removed")

If that's the case, then has_no_link will still point to the link. remove() will remove it from the DOM, but your variable still points to it in memory.

You should query for the link again in the DOM to see if you get a result.

Etai
  • 1,483
  • 1
  • 11
  • 15
0

I had to rewrite wait_until for a test that involved waiting for a callback triggered by streaming a youtube video to a certain point. Here's what I used:

# in spec_helper.rb
require "timeout"
def wait_until time=0.1
    Timeout.timeout(Capybara.default_wait_time) do
        sleep(time) until value = yield
        value
    end
end
animatedgif
  • 1,060
  • 12
  • 16
  • 1
    For more recent versions of Capybara, note that `default_wait_time` has been deprecated. Use `default_max_wait_time` now. – Sia Jun 09 '16 at 21:07
0

Couple of approaches I thought of:

1: Check your capybara version and look for any bugs with your version

2: Maybe try doing a find on the link after the button is clicked

click_button("Remove stuff") find(:xpath, '//a[text()='Link should be removed').should be_false

3: Use has_link? instead of has_no_link?

click_button("Remove stuff") page.has_link?("Link should be removed").should be_false

jamesy829
  • 428
  • 4
  • 7
0

You can always do this manually. Using @shioyama's idea:

  def wait_for_ajax
    timer_end = Time.now + 5.seconds 
    while page.evaluate_script('$.active') != 0    
      if Time.now > timer_end
        fail "Page took more than 5 seconds to load via ajax"
      end 
      sleep 0.1
    end
  end
montrealmike
  • 11,433
  • 10
  • 64
  • 86