Understanding Asynchronous JavaScript (Ajax and friends)
Let’s understand Asynchronous JavaScript (Ajax and friends). When working with asynchronous JavaScript, you might come across situations where you are attempting to interact with an element which is not yet present on the page. Capybara has an internal mechanism for handling asynchronous loading of parts of the page. A good example is clicking on some part of the page causing JavaScript to be executed that creates a new element on the page. Capybara automatically deals with this by waiting for elements to appear on the page.
When issuing instructions to the DSL such as:
click_link(‘foo’)
click_link(‘bar’)
expect(page).to have_content(‘baz’)
If clicking on the foo link triggers an asynchronous process, such as an Ajax request, which, when complete will add the bar link to the page, clicking on the bar link would be expected to fail, since that link doesn’t exist yet. However, Capybara is smart enough to retry finding the link for a brief period of time before giving up and throwing an error. The default time is 2 seconds and can change with property
Capybara.default_wait_time
There is no silver bullet for handling these cases. On one hand, it is a good practice to hide the complexity of the wait mechanism from the creator of the test script so the syntax is cleaner and easier to understand. On the other hand, sometimes it is better to have more control over the wait mechanism and handle it yourself.
The same is true of the next line, which looks for the content baz on the page; it will retry looking for that content for a brief time. You can adjust how long this period is (the default is 2 seconds):
Capybara.default_max_wait_time = 5
Be aware that because of this behaviour, the following two statements are not equivalent, and you should always use the latter!
!page.has_xpath?(‘a’)
page.has_no_xpath?(‘a’)
The former would immediately fail because the content has not yet been removed. Only the latter would wait for the asynchronous process to remove the content from the page.
In any case, this wait issue is often the cause of unexpected failures in the script. This is likely a good indicator that the implementation has either performance or other issues.
Capybara also supports execution of JavaScript via:
page.execute_script()
where JavaScript code can pass like this:
page.execute_script(“$(‘#area button.primary’).click()”)
Capybara’s Rspec matchers, however, are smart enough to handle either form. The two following statements are functionally equivalent:
expect(page).not_to have_xpath(‘a’)
expect(page).to have_no_xpath(‘a’)
Capybara’s waiting behaviour is quite advance, and can deal with situations such as the following line of code:
expect(find(‘#sidebar’).find(‘h1’)).to have_content(‘Something’)
Even if JavaScript causes #sidebar to disappear off the page, Capybara will automatically reload it and any elements it contains. So if an AJAX request causes the contents of #sidebar to change, which would update the text of the h1 to “Something”, and this happened, this test would pass. If you do not want this behaviour, you can set Capybara.automatic_reload to false.
Make your resume stand out and become a Certified Capybara Testing Professional. Try free practice tests here!
A great career is just a certification away. So, practice and validate your skills to become Certified Capybara Testing Professional