Why does adding "sleep 1" in the sequence force me to pass the Rspec / Capybara test?

I am using rails 4.0.5, rspec 2.14.1, capybara 2.2.1, capybara-webkit 1.1.0 and database_cleaner 1.2.0. I see some weird behavior with the following function test (which mimics a user viewing a comment on a post, hangs over an icon to display a menu, and clicking a menu item to delete a comment):

let(:user){create(:user)} let(:post){create(:post, author: user)} let!(:comment){create(:comment, post: post, author: user)} ... it "can delete a comment" do assert(page.has_css? "#comment-#{comment.id}") find("#comment-#{comment.id}-controls").trigger(:mouseover) find("#comment-#{comment.id} .comment-delete a").click assert(page.has_no_css? "#comment-#{comment.id}") end 

This test fails for about 80% of the time, always because some record is retrieved from the database as nil - I get NoMethodError: undefined method X for nil:NilClass for different X values. Sometimes nil is a comment that is deleted , sometimes this is a post to which a comment is attached, sometimes it is the author of a comment / post.

If I add sleep 1 to the end of the test, it will pass:

 it "can delete its own comment" do assert(page.has_css? "#comment-#{comment.id}") find("#comment-#{comment.id}-controls").trigger(:mouseover) find("#comment-#{comment.id} .comment-delete a").click assert(page.has_no_css? "#comment-#{comment.id}") sleep 1 end 

It is also passed if I put sleep 1 in the after block.

Any idea why I get these NoMethodErrors and / or why the test passes if I make it sleep for a second after completing all the work?

+7
ruby ruby-on-rails rspec capybara capybara-webkit
source share
2 answers

I suspect that in your application for comment it may disappear from the page (this is the last thing you say) before it removes it from the database. This means that the test can be cleaned before removal. If so, you can fix this by waiting for the actual deletion to occur at the end of the test. I have this method (reimplementing a method that has been removed from Capybara 2 but is still sometimes necessary )

 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 

so i can do

 wait_until { Comment.count == 0 } 

in tests.

Another approach is to add Rack middleware, which blocks requests made after the test completes. This approach is described in detail here: http://www.salsify.com/blog/tearing-capybara-ajax-tests I saw that it copes very well with the problem of data leakage in the RSpec set of good-sized function specifications.

+5
source share

https://github.com/jnicklas/capybara#asynchronous-javascript-ajax-and-friends

When working with asynchronous JavaScript, you may encounter situations when you try to interact with an element that is not yet present on the page. Capybara automatically does this while waiting for items to appear on the page.

-one
source share

All Articles