Re: [Capybara] Is #wait_until Really Deprecated In 2.0

9827 views
Skip to first unread message

Jonas Nicklas

unread,
Aug 23, 2012, 4:19:30 PM8/23/12
to ruby-c...@googlegroups.com
This will be ridiculously long, but well, you asked…

It isn't deprecated, it's been removed. Deprecated implies that the
functionality still exist, but use is no longer recommended because it
will be removed at some point in the future. This is not the case, as
it stands wait_until will be completely gone without replacement in
Capybara 2.0.

wait_until was removed for several reasons:

1) It simply isn't necessary for most applications and test suites
2) It's existence confuses people into thinking that it is necessary,
when in fact it isn't
3) It was added at a point where Capybara's auto-waiting was much more
primitive than it is now
3) 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. As you pointed out `find("#foo")` 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 review what wait_until actually did. In Capybara 1.1.2,
wait_until delegated to Capybara.timeout, check out the implementation
here: https://github.com/jnicklas/capybara/blob/1.1.2/lib/capybara/util/timeout.rb.

Essentially this is similar to the timeout method in the Ruby standard
library, except it avoids the use of Threads, for some reason I can't
quite remember. Line 20 was some weird thing that capybara-envjs
needed, but since that project is defunct, I don't think it really
does anything. So essentially we have a generic implementation of
timeout. When the block yields a truthy result, it is returned,
otherwise it retries until it times out eventually.

We added this method to begin with because at some point we were using
it internally. However, we changed the internal implementation to use
an exception based workflow instead. This was done to compensate for
certain timing issues in tests. Essentially every time an element
which has disappeared from the page is accessed in any way, Selenium
raises an exception. Since Capybara 1.1, we rescue that exception, we
run the query that fetched the element again, and we try redoing the
action. So if you have something like:

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.

In your modal example you had this:

page.find(modal_wrapper_id).text.must_match %r{login failed}i

If you'd change this to:

page.find(modal_wrapper_id, :visible => true).should
have_content("login failed")

or even:

page.should have_selector(modal_wrapper_id, :visible => true, :text =>
"login failed")

It would have worked out of the box, no need for
`assert_modal_visible` at all. That's the thing, in almost every case
where someone has used `wait_until` it wouldn't have been necessary if
they'd simply understood how the waiting behaviour works.

Now I say almost, because Jo actually proposed a use case where
something like wait_until is useful: testing the persistence in an
application with an asynchronous UI by checking against the model
directly. This is a bit of an edge case obviously. And even for those
who run into something like this, wait_until is both trivial to
implement and actually provided by the Ruby standard library.

In Capybara 1.1.X we actually had *two* methods called wait_until,
with completely different behaviours and implementations. As if that
wasn't confusing enough, the one that was vastly more useful was
actually private and undocumented. This method is now public and
called `synchronize` in Capybara 2.0. But before you run off and wrap
every line of your code in calls to `synchronize`, please be aware
that you should only *ever* need to call this if you're messing around
with the internals of the driver by accessing `#native` and you're
seeing failures due to timing issues.

I hope that answers your question sufficiently. I'm of course open to
a discussion about the removal, but I feel fairly confident that it
was a good decision, and that it will steer people towards writing
better tests.

/Jonas

MetaSkills

unread,
Aug 23, 2012, 5:17:37 PM8/23/12
to ruby-c...@googlegroups.com
First, thanks for taking the time to explain.

My modal examples were somewhat contrived. Most of the places I have used #wait_until were and still seem to be for valid reasons. Stuff like waiting for #evaluate_script to let me know AJAX queues were empty and/or all jqXHR objects finished. In the cases where I do leverage it for the DOM, it is almost always a concert of elements and classes that account for animations and transitions to come to some truthy end, not just visibility. Like a modal that is sliding down may be on the DOM, but not proper to act upon till it is fully animated into view.

These use cases seem to cry out for some standard wait interface. Perhaps it could be #synchronize, but that does not appear to meet the the same needs. In lieu of one, people will have to write their own and as developers move from project to project it could become messy. I still think having a Capybara #wait_until as a common interface, even if others misuse it, is a good idea and could be documented to speak to the valid reasons, unless I am still wrong in using it.

Jonas Nicklas

unread,
Aug 23, 2012, 5:50:39 PM8/23/12
to ruby-c...@googlegroups.com
> Stuff like waiting for #evaluate_script to let me know AJAX queues were empty and/or all jqXHR

Why do you need to wait for Ajax requests? Surely is no point in
waiting for them just because. Your Ajax requests are probably going
to have some kind of impact on the DOM, and Capybara can wait for that
to finish just fine.

> In the cases where I do leverage it for the DOM, it is almost always a concert of elements and classes that account for animations and transitions to come to some truthy end, not just visibility. Like a modal that is sliding down may be on the DOM, but not proper to act upon till it is fully animated into view.

Not really sure why you couldn't interact with the stuff in the modal
while it is sliding around. I fail to see the problem here. If your
intended interaction fails due to some kind of error, Capybara will
try to run it again, if it doesn't, then it's all good. I wrote a
rather complex interaction with a modal dialog just today, in Capybara
1.X no less, and I didn't have to do any manual waiting anywhere.

/Jonas

Ken Collins

unread,
Aug 23, 2012, 7:02:56 PM8/23/12
to ruby-c...@googlegroups.com
Why do you need to wait for Ajax requests? Surely is no point in
waiting for them just because. Your Ajax requests are probably going
to have some kind of impact on the DOM

Sure, some do. Some do not. Sometimes I want to wait for all the page request to finish, reload a model, and QA the functional controller action did indeed touch the database.

Not really sure why you couldn't interact with the stuff in the modal
while it is sliding around. I fail to see the problem here. 

Technically yes. But waiting for it means I can take a screen shot if something goes wrong and actual see it too. 

What driver do you use? I myself have been using Poltergeist almost exclusively and I have seen a few cases where things happen so damn quickly that it causes errors. For instance, I click a button, wait for ajax requests, then a DOM element to be rendered and faded in via jQuery. Even after waiting for the ajax requests, the following call to find('#said-animated-element').click would fail. I found in that particular case the element was technically there, but was not clickable till it was animated in a few milliseconds later. In fact, Poltergeist was so fast, that if I rendered the page to a png right after the error, I would not see the element at all. Perhaps these edge cases are more logical when using a really fast JavaScript driver?

Another question. I made this #save_and_open_page helper when using Poltergeist. I used wait_until there because of the async nature of the driver. I actually had to wait till the file showed up.

def save_and_open_page
  dir = "#{Rails.root}/tmp/cache/capybara"
  file = "#{dir}/#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.png"
  FileUtils.mkdir_p dir
  page.driver.render file
  wait_until { File.exists?(file) }
  system "open #{file}"
end

Here is another example, how would you tell default Capybara to wail until all my spinners have completed. I use a method like this. 

def wait_for_spinners
  wait_until { page.evaluate_script("jQuery('.spinner:visible').length === 0") }
rescue Capybara::TimeoutError
  flunk 'Ran out of time waiting for all .spinner objects to be removed from the page.'
end

So taking your point, I can see where #has_css? delegates to #assert_selector which uses the #synchronize block which will raise Capybara::ExpectationNotMet. So my helper above could be rewritten like this?

def wait_for_spinners
  page.has_selector? '.spinner', :visible => false
rescue Capybara::ExpectationNotMet
  flunk 'Ran out of time waiting for all .spinner objects to be removed from the page.'
end

I dont want to keep this thread constantly going back and forth. I see your point and mine both. Maybe there are still a few of mine that are moot, but I still have a gut feeling that an acceptance testing framework without a solid wait interface to test whatever the user deems necessary in their async JavaScript applications is a small hole. I am willing to be wrong on that and if I have not convinced you by now, then I will learn :)

 - Cheers,
    Ken





Jonas Nicklas

unread,
Aug 23, 2012, 7:40:57 PM8/23/12
to ruby-c...@googlegroups.com
Not sure about Poltergeist, I haven't used it yet. Though there are
tests in the Capybara test suite which test this behaviour, so if
Poltergeist passes the suite, and I would be surprised if it didn't,
then it should work just as well.

The screenshot thing is interesting. Capybara 2.0 has a
save_screenshot method, so I suspect Poltergeist will implement that
eventually, and will have to make it synchronous. Aside from that,
you're messing with the internals of the drivers, so really in this
case you're using Poltergeist and not Capybara.

Just a small correction on your last example, has_selector? returns a
boolean, and shouldn't raise any exceptions, so you probably want
something more like this:

def wait_for_spinners
page.has_selector?('.spinner', :visible => false) or flunk("message")
end

/Jonas

Matt Wynne

unread,
Aug 28, 2012, 5:15:08 PM8/28/12
to ruby-c...@googlegroups.com
One of you guys should write this up as a blog post.

dwt

unread,
Nov 14, 2012, 9:26:36 AM11/14/12
to ruby-c...@googlegroups.com
Maybe I'm missing something, but if I want to see if some js redirects the page to another url / #anchor then the capybara api seems not to to give me an opportunity to use the integrated autowait.

Do I now have to write and maintain my own implementation of with_timeout or is there a helper in Capybara that can deal with this?

Regards,
Martin

Adrian Longley

unread,
Nov 14, 2012, 9:36:35 AM11/14/12
to ruby-c...@googlegroups.com

On 14 Nov 2012, at 14:26, dwt <mhae...@gmail.com> wrote:

Maybe I'm missing something, but if I want to see if some js redirects the page to another url / #anchor then the capybara api seems not to to give me an opportunity to use the integrated autowait.

Do I now have to write and maintain my own implementation of with_timeout or is there a helper in Capybara that can deal with this?

Could you look for evidence of the behaviour you are expecting in the page? You could always check the URL immediately afterwards if it's important.

It's probably also good not to depend on urls where possible so that if the implementation (Ajax vs post-back or #! vs history API for example) changes then your tests are tolerant of this. Having said that, if you redirect off site that may not be applicable.

Regards,
Martin

Jonas Nicklas

unread,
Dec 13, 2012, 6:38:38 PM12/13/12
to ruby-c...@googlegroups.com
I explained my reasoning behind the removal of wait_until further
here: http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara

/Jonas

On Thu, Dec 13, 2012 at 9:07 PM, <ca...@change.org> wrote:
>
>
> On Thursday, August 23, 2012 2:50:39 PM UTC-7, jnicklas wrote:
>>
>> > Stuff like waiting for #evaluate_script to let me know AJAX queues were
>> > empty and/or all jqXHR
>>
>> Why do you need to wait for Ajax requests? Surely is no point in
>> waiting for them just because. Your Ajax requests are probably going
>> to have some kind of impact on the DOM, and Capybara can wait for that
>> to finish just fine.
>>
>
> For our test suite, we often have AJAX requests which lead to page
> transitions. Capybara will wait for the AJAX request, and then perform the
> find() test on the resulting page, waiting until the page transition
> completes if necessary. Most of the time, this works with no problems
> whatsoever.
>
> There are several cases, however, where the page transition is
> non-deterministic, because we have "splits" which guide the user down a
> randomly selected path, and wait_until allows us to determine when we can
> use find() or has_content to determine which path to test for. Furthermore,
> some of those post-AJAX transitions lead to the same URI (sometimes with an
> #anchor), and find() will grab the previous page's elements when we really
> want the elements on the post-transition page.
>
> If we lose wait_until, we'll basically have to re-write it ourselves, or
> just grab it from a previous Capybara release.
>
> -c.

ca...@change.org

unread,
Dec 13, 2012, 7:03:44 PM12/13/12
to ruby-c...@googlegroups.com
I read that post.  I still disagree with point 1, and by extension point 2.

It may not be necessary for "most" test suites, but it's certainly necessary for ours.  It seems like you're removing a oft-used method simply because you think it's used too often.

 -c.
Message has been deleted
Message has been deleted

Martin Bilski

unread,
Mar 5, 2013, 4:13:07 AM3/5/13
to ruby-c...@googlegroups.com
Nice, Kevin, great job!

Martin

On Tue, Mar 5, 2013 at 4:33 AM, Kevin Triplett <ke...@mopacmedia.com> wrote:
Anyone reading this thread for an alternative to re-writing your tests, please see my gist.
It is working well in my integration tests:

https://gist.github.com/KevinTriplett/5087744

-- Kevin


On Saturday, February 23, 2013 12:25:26 PM UTC-6, Anton Zhuravsky wrote:

Why do you need to wait for Ajax requests? Surely is no point in
waiting for them just because. Your Ajax requests are probably going
to have some kind of impact on the DOM, and Capybara can wait for that
to finish just fine.


Not neccessarily at all. Any background interaction that is not visible by the user, but used by javascript code doesn't fall into this category. I have even simpleir example: testing pieces of your JS (i.e. remote data store) where you only would like to call JS functions and see how they hit the server. 

wait_until is generally a bad practice no doubt, but it was at least useful to wait until some background work completes (like AJAX) as there are no built-in means for this (afaik). Or there are? 

--
You received this message because you are subscribed to the Google Groups "Capybara" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-capybar...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Miguel Madero

unread,
Sep 23, 2013, 5:38:32 PM9/23/13
to ruby-c...@googlegroups.com, mar...@xtreeme.com
I recently found another use case. It could be another edge case, but... 

We have a page test that changes the selected value in a dropdown and as a result, it does two things. It re-calculates a price clientside based on the new selection, and then it posts the update to the server. Our tests is verifying that we update the price clientside, which is fine for our purposes. However, there's still a pending request. This pending request can conflicts with other running tests or the DatabaseCleaner. 

We had to manually implement something similar to wait_until. Not a big issue and I understand the rationaly behind removing it to avoid people using it when it's often not needed, but it would be nice to have something equivalent for those cases where it's used. 
Reply all
Reply to author
Forward
0 new messages