5
votes

I'm having weird issues with some of my feature tests using Capybara with poltergeist driver. The test should perform a simple checkout in my online shop. They all pass fine on my local MacBook as well as on an Ubuntu vagrant box. However on CI services like Codeship, Wercker or Semaphore they fail with the very same error.

My spec:

require 'rails_helper'

    describe 'Checkout' do

      let!(:product) { FactoryGirl.create(:product) }

      it 'checks out via CreditCard', js: true do
        visit products_path
        expect(page.body).to have_link('Test Product 1')
        click_link('Test Product 1')
        #rest of spec ommitted
      end
    end

The error I get on CI is:

2) Checkout checks out via CreditCard
Failure/Error: click_link('Test Product 1')
Capybara::ElementNotFound:
  Unable to find link "Test Product 1"

To me this is super weird, as the first expectation 'expect(page.body).to have_link('Test Product 1')' seems to pass but then it fails on the next step where it should actually click the link it just assured to be present on the page?

I then reconfigured poltergeist driver as follows to gather more debug information.

Snippet of rails_helper.rb:

Capybara.register_driver :poltergeist do |app|
  Capybara::Poltergeist::Driver.new(app, {js_errors: false,
                                          #inspector: true,
                                          phantomjs_logger: Rails.logger,
                                          logger: nil,
                                          phantomjs_options: ['--debug=no', '--load-images=no', '--ignore-ssl-errors=yes', '--ssl-protocol=TLSv1'],
                                          debug: true
                                       })
end
Capybara.server_port = 3003
Capybara.app_host = 'http://application-test.lvh.me:3003' # lvh.me always resolves to 127.0.0.1
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = :poltergeist
Capybara.default_wait_time = 5

Now I can see on CI console that the test successfully visits my products_path and the expected html page (including my the link it should click) is being returned. I removed the rest of the HTML response to make it more readable:

{"name"=>"visit", "args"=>["http://application-test.lvh.me:3003/products"]}
{"response"=>{"status"=>"success"}}
{"name"=>"body", "args"=>[]}
{"response"=>"--- snip --- <div class=\"info\">\n<a class=\"name color-pomegranate\" href=\"/en/products/6\">\nTest Product 1\n</a>\n850,00 \n</div> --- snap ---"}
{"name"=>"find", "args"=>[:xpath, ".//a[./@href][(((./@id = 'Test Product 1' or normalize-space(string(.)) = 'Test Product 1') or ./@title = 'Test Product 1') or .//img[./@alt = 'Test Product 1'])]"]}
{"response"=>{"page_id"=>4, "ids"=>[0]}}
{"name"=>"visible", "args"=>[4, 0]}
{"response"=>false}
{"name"=>"find", "args"=>[:xpath, ".//a[./@href][(((./@id = 'Test Product 1' or contains(normalize-space(string(.)), 'Test Product 1')) or contains(./@title, 'Test Product 1')) or .//img[contains(./@alt, 'Test Product 1')])]"]}
{"response"=>{"page_id"=>4, "ids"=>[1]}}
{"name"=>"visible", "args"=>[4, 1]}
{"response"=>false}

The last two find actions repeat until Capybara reaches its timeout, then the test fails. I double checked the xpath Capybara uses via some online xpath validators, but as expected it matches the HTML link. I also used capybara-screenshot gem to dump the HTML body on failure and the link in question is also present.

So why is the test still failing? Is there any race condition that I'm not aware of? Why is it passing locally but on none of the CI services?

Here are my gem version:

  • capybara (2.4.4)
  • capybara-screenshot (1.0.3)
  • database_cleaner (1.3.0)
  • factory_girl (4.5.0)
  • factory_girl_rails (4.5.0)
  • poltergeist (1.5.1)
  • rails (4.1.8)
  • rspec (3.1.0)
  • rspec-rails (3.1.0)
  • and phantomjs 1.9.7
1

1 Answers

0
votes

While I can't reproduce this, I remember having this problem before. I believe your line:

expect(page.body).to have_link('Test Product 1')

is passing because the link is literally on the body of the html page, even though it may be hidden due to CSS or JS behavior. However, the line:

click_link('Test Product 1')

definitely checks for visibility before clicking the link. You should check your spec_helper.rb configurations to make sure:

Capybara.ignore_hidden_elements = true

is present, so that the first line wouldn't pass. I think I also had to change the first line I mentioned to:

# Change page.body to page, to look at the rendered page, not the literal one
expect(page).to have_link('Test Product 1')

Once you do this, the first line blocks the thread and waits until the link becomes visible. Then the rest of the test will pass.

Hope this solves it.