Skip to content

Instantly share code, notes, and snippets.

@timdiggins
Last active September 18, 2021 16:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timdiggins/a6fe929d2abcbfe72d75c7395c7146c5 to your computer and use it in GitHub Desktop.
Save timdiggins/a6fe929d2abcbfe72d75c7395c7146c5 to your computer and use it in GitHub Desktop.
Succeed or sleep and try again (for Capybara js)
Succeed or sleep and try again (for Capybara js)

Succeed or sleep and try again (for Capybara js)

Using Capybara and Selenium in a rails system / feature spec, I get random errors sometimes on CI (rarely locally) which come from either a StaleElementReferenceError or also sometimes just a Capybara ElementNotFound error (often because I have failed to explicitly look for some change).

Sometimes I can work around this with enough expect...to have or expect...not_to have, but sometimes I just can't. It feels awful (and actually slows down everything) to put in random sleep amounts.

But, inspired by https://accidentaltechnologist.com/ruby-on-rails/fixing-staleelementreferenceerror-when-using-capybara/, how about only sleeping when it fails?

Here's my response to this:

Sample usage:

describe "something that is temperamental", js: true do
  visit "some/path"
  click_on "some button"
  succeed_or_sleep do
    expect(page).to have_content("some new content")
  end
end

Attached are the support file, and a spec for testing the support file

# frozen_string_literal: true
RSpec.configure do |config|
RSpec.shared_context "SucceedOrSleepSupport" do
def succeed_or_sleep(time_to_sleep_if_it_fails = 1)
yield
rescue Selenium::WebDriver::Error::StaleElementReferenceError, Capybara::CapybaraError
if @succeed_or_sleep_already_run
raise
else
@succeed_or_sleep_already_run = true
sleep time_to_sleep_if_it_fails
retry
end
end
end
config.include_context "SucceedOrSleepSupport", js: true
end
# frozen_string_literal: true
require "rails_helper"
describe "SucceedOrSleepSupport", type: :feature, js: true do
it "rescues and retries" do
already_run = false
succeed_or_sleep(0) do
next if already_run
already_run = true
raise Selenium::WebDriver::Error::StaleElementReferenceError
end
end
# rubocop:disable RSpec/InstanceVariable
it "rescues and retries only once" do
@iteration = 0
succeed_or_sleep(0) do
@iteration += 1
case @iteration
when 1
raise Selenium::WebDriver::Error::StaleElementReferenceError
when 2
raise Selenium::WebDriver::Error::StaleElementReferenceError
else
flunk "expected it to only retry once, but it has run #{iteration - 1} times"
end
end
rescue Selenium::WebDriver::Error::StaleElementReferenceError
expect(@iteration).to eq(2)
# in which case this is an expected error
end
# rubocop:enable RSpec/InstanceVariable
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment