Read more

Synchronize a Selenium-controlled browser with Capybara

Henning Koch
March 14, 2011Software engineer at makandra GmbH

When you click a link or a press a button on a Selenium-controlled browser, the call will return control to your test before the next page is loaded. This can lead to concurrency issues when a Cucumber step involves a Selenium action and a Ruby call which both change the same resources.

Illustration book lover

Growing Rails Applications in Practice

Check out our e-book. Learn to structure large Ruby on Rails codebases with the tools you already know and love.

  • Introduce design conventions for controllers and user-facing models
  • Create a system for growth
  • Build applications to last
Read more Show archive.org snapshot

Take the following step which signs in a user through the browser UI and then sets a flag on the user that was just signed in:

Given /^the user "([^"]*)" signed in (\d) days ago$/ do |name, days|
  visit new_session_path
  fill_in 'Username', :with => name
  fill_in 'Password', :with => 'secret'
  click_button 'Sign in'
  user = User.find_by_name(name)
  user.update_attribute :last_sign_in => days.days.ago
end

Note that you should strive to do everything through the browser UI in Cucumber features, but occasionally a step like this is too convenient to pass up.

The problem with the step above is that there is no guarantee the form submission has completed in Selenium before Ruby updates the record behind the scenes. In this case you might lose the update because the sign in process also sets the #last_sign_in attributes to the current time. This will make you feature fail occasionally.

The solution is to have Selenium resynchronize with the controlled browser before you do the update from Ruby. This will ensure that the controlled browser has finished its job. You can use the following step to perform resynchronization:

When /^the browser is ready$/ do
  Then 'I should see ""'
end

We can now rewrite the step from the example above to be free of concurrency issues:

Given /^the user "([^"]*)" signed in (\d) days ago$/ do |name, days|
  visit new_session_path
  fill_in 'Username', :with => name
  fill_in 'Password', :with => 'secret'
  click_button 'Sign in'
  When 'the browser is ready'
  user = User.find_by_name(name)
  user.update_attribute :last_sign_in => days.days.ago
end

Note that Capybara's Selenium driver performs resynchronization before every action that goes through the driver. So you don't need to care about resynchronization if you are a good boy and do everything through the browser UI.

See also

Waiting for page loads and AJAX requests to finish with Capybara

Posted by Henning Koch to makandra dev (2011-03-14 11:03)