Search code examples
ruby-on-railsrspeccapybarapoltergeistcapybara-webkit

capybara not wait ajax request


I get the following error in rspec + capybara + poltergeist:

given!(:user_owner) { create(:user) }
given!(:second_user) { create(:user) }
given!(:question) { create(:question, user: user_owner) }

describe 'as not question owner' do
  before do
    login_as(second_user, scope: :user, run_callbacks: false)
    visit question_path(question)
  end

  scenario 'can upvote just one time', js: true do
    first('a#question-upvote').click
    expect(first('div#question-score').text).to eq '1'
    first('a#question-upvote').click
    expect(first('div#question-score').text).to eq '1'
  end

Failure/Error: expect(page.first('div#question-score').text).to eq '-1'

   expected: "-1"
        got: "0"

When I insert sleep 1:

scenario 'can upvote just one time', js: true do
   first('a#question-upvote').click
   sleep 1
   expect(first('div#question-score').text).to eq '1'
   first('a#question-upvote').click
   sleep 1
   expect(first('div#question-score').text).to eq '1'
end

Test pass.

I understood that page not waited asynchronous request. How can I rewrite test to start it to work well without sleeping?

P.S. Sorry for English.


Solution

  • You're killing any waiting behavior by using the eq matcher. This is because once you call .text on a found element you have a String and there is no way to reload/re-query that string when used with the eq matcher. If you want waiting/retrying behavior you need to use the matchers provided by Capybara with Capybara elements.

    So instead of expect(first('div#question-score').text).to eq '1' you should be doing

    expect(first('div#question-score')).to have_text('1', exact: true) # you could also use a Regexp instead of specifying exact: true
    

    Another thing to note is that all/first disable reloading of elements, so if the entire page is changing (or the element you are waiting for text on is being completely replaced) and the initial page had an element that would match the selector but you actually want the element from the second page (or replaced element) to be checked you shouldn't be using first/all - In that case you would want to use find with a query using the css :first-child/:first-of-type, etc type things (or equivalent XPath) to uniquely identify your element instead of returning multiples and picking one of them. If it's just the value of the element being replaced asynchronously on the page then you did not to worry about it.