Why wait_until was removed from Capybara
The release of Capybara 2.0.0 removed the wait_until
method from the API. This seems to have frustrated a few people, so let me explain why this decision was reached and what your options are for the future.
wait_until
was removed for several reasons:
- It simply isn’t necessary for most applications and test suites.
- Its existence confuses people into thinking that it is necessary, when in fact it isn’t.
- It was added at a point where Capybara’s auto-waiting was much more primitive than it is now.
- It used to be used internally, it no longer is.
Capybara has a had a very important feature since pretty much the beginning which is that it automatically waits for elements to appear or disappear on the page. If you do something like find("#foo")
, this will block until an element with id "foo" appears on the page, as will has_content?("bar")
, click_link("baz")
and most other things you can do with Capybara.
Let’s look at a more complicated case:
find("#foo").click_link("baz")
Even if #foo
is originally on the page and then removed and replaced with a #foo
which contains baz after a short wait, Capybara will still figure this out. Let’s make that really clear, Capybara is ridiculously good at waiting for content.
For the most part, this behaviour is completely transparent, and you don’t even really have to think about it, because Capybara just does it for you. What trips a lot of people up is that they try to do something like this:
page.find("foo").text.should contain("login failed")
And now they have introduced potential timing issues. text
, being just a regular method, which returns a regular string, isn’t going to sit around and wait for anything. It will simply return the text as it appears when Capybara gets to this line and call it a day. Now after a long debugging session, our developer has found the timing issue. They now realize that there is a wait_until
method in the API, and immediately think that, "hey, this sounds like what I need!"
wait_until do
page.find("#foo").text.should contain("login failed")
end
Fantastic, it now works! The thing is though, Capybara could have easily figured out how to wait for this content, without you muddying up your specs with tons of explicit calls to wait_until
. Our developer could simply have done this:
page.find("#foo").should have_content("login failed")
Or even:
page.should have_selector("#foo", :text => "login failed")
And the problem would have solved itself.
As long as you stick to the Capybara API, and have a basic grasp of how its waiting behaviour works, you should never have to use wait_until
explicitly.
Synchronize
Capybara 2 introduces a new method called synchronize
. While this method is part of the public API, don’t run off and use it just yet. It has a very distinct use case, which is completely different from what you’re probably using wait_until
for. You will most likely only ever have to call this if you access the low level driver directly through #native
. In this case, you might receive errors like Selenium’s StaleElementReferenceError
. It’s these kinds of errors that synchronize
prevents.
Assserting on model objects
I am firmly convinced that asserting on the state of the interface is in every way superior to asserting on the state of your model objects in a full-stack test. In some cases, especially if your interface is asynchronous, you might still want to do it though.
This is the only legitimate use case for wait_until
I’ve heard of in Capybara.
Imagine that in the following scenario, "Liked" is shown immediately, through JS, regardless of if the change was actually persisted to the server or not:
click_link("Like")
page.should have_content("Liked")
post.reload.should be_liked
This would cause timing issues, if the AJAX request is slower than the reload. We could have written this as:
click_link("Like")
page.should have_content("Liked")
wait_until { post.reload.liked? }
Where wait_until
could be implemented like this: https://gist.github.com/d8da686061f0a59ffdf7
Though I personally would have preferred something like:
click_link("Like")
page.should have_content("Liked")
expect { post.reload.liked? }.to become_true
Which could be implemented like this: https://gist.github.com/4129937
You could have also asserted this through the UI:
click_link("Like")
page.should have_content("Liked")
visit current_path
page.should have_content("Liked")
But it is kind of verbose, and it’s also a lot slower, so I can understand why the model test might be preferred.
But then why not just bundle it
If and when you need some kind of behaviour that waits for things, wait_until
is a giant big sledgehammer. There are more fine grained, sophisticated tools built into Capybara, and I want you to learn about them, because those are some of the best features of the library. And when the built in tools aren’t enough, there are more sophisticated tools available than that clunky hammer. So hopefully the removal of wait_until
encourages you to write better tests.
Comments