Today I Learned: Capybara better practices
Never re-invent the wheel
Capybara feature specs can be frustrating because it’s difficult to get them to pass 100% of the time. This post will teach you how to avoid a certain race condition, using only built-in Capybara methods.
Often times, it’s easy to wonder why your specs run so slow or why your spec is blinky. Most of the times it is because we’ve used something that just works( or maybe not). You could, of course, take a few minutes to read about it before using it, but since you’re probably as lazy as me, here’s a blog post explaining it.
For example, let’s imagine you are writing a spec that clicks on something, waits for a background task to be completed which updates a certain field, and then finally validates that field. (Oofff…lots of steps.) There are many ways to write this spec:
# spec/features/user_page_spec.rbdescribe UserPage do
it "validates text on the form"
click "validate"
expect(find('.sample_form_text').text).to eq('validated text')
end
end
The above spec works just fine, but it can be blinky. How?
Let's set some assumptions early before I go ahead:
Capybara.default_max_wait_time = 5.
I have the set the default_max_wait_time
of capybara to 5 seconds.
Now let’s go through each step. First, you click on validate
on the page and the background task gets triggered. Now it’s going to try to find the text on the class: sample_form_text
and validate it with validated text. In most cases, this works fine. But this call find('.sample_form_text').text
is almost instantaneous. So if the background task takes some time to fill the page with the text, the spec is going to fail. How can we fix this?
One method I like is from this post, https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara
# spec/support/wait_for_ajax.rbmodule WaitForAjax def wait_for_ajax Timeout.timeout(Capybara.default_max_wait_time) do loop until finished_all_ajax_requests? endend def finished_all_ajax_requests? page.evaluate_script(‘jQuery.active’).zero? endendRSpec.configure do |config| config.include WaitForAjax, type: :featureend
So you can use the background task as shown below:
click "validate"
wait_for_ajax
expect(find('.sample_form_text').text).to eq('validated text')
Another option is to use something capybara already provides. Shown below:
Before:click "validate"
expect(find('.sample_form_text').text).to eq('validated text')After:click "validate"
expect(find('.sample_form_text', text: expected_text)).to have_content(expected_text)
By default, the find with text
option waits for the text to be available and that fixes the blinkiness.
Hope this helped! We’ll be back soon with another edition of Today I Learned
References: