teamcapybara / capybara

Acceptance test framework for web applications
http://teamcapybara.github.io/capybara/
MIT License
10.02k stars 1.45k forks source link

expect(page).to_not have_content super slow #2344

Closed rgaufman closed 4 years ago

rgaufman commented 4 years ago

Meta

Capybara Version: 3.31.0 Driver Information (and browser if relevant): selenium-webdriver (3.142.7) with Chrome 81.0.4044.122

Expected Behavior

It should work as quickly as expect(page).to have_no_content.

Actual Behavior

Using expect(page).to have_no_content:

$ for i in 1 2 3 4 5; do rspec 2>&1 | grep Finished; done
Finished in 11.2 seconds (files took 4.38 seconds to load)
Finished in 11.37 seconds (files took 5.01 seconds to load)
Finished in 11.65 seconds (files took 4.61 seconds to load)
Finished in 11.29 seconds (files took 4.86 seconds to load)
Finished in 11.15 seconds (files took 4.76 seconds to load)

Using expect(page).to_not have_content

$ for i in 1 2 3 4 5; do rspec 2>&1 | grep Finished; done
Finished in 21.08 seconds (files took 4.37 seconds to load)
Finished in 21.66 seconds (files took 4.34 seconds to load)
Finished in 21.7 seconds (files took 4.46 seconds to load)
Finished in 20.77 seconds (files took 4.33 seconds to load)
Finished in 21.66 seconds (files took 4.83 seconds to load)

Steps to reproduce

Change expect(page).to_not have_content to expect(page).to have_no_content and the spec will run significantly quicker.

twalpole commented 4 years ago

As long as you have required the Capybara rspec matchers as described in the README these calls are identical - so I'm assuming you haven't actually required the Capybara rspec matchers.

rgaufman commented 4 years ago

Ah, maybe, but I did this, can you point me to what I missed please?


require 'rails/mongoid'

ENV['RAILS_ENV'] ||= 'test'
require File.join(__dir__, '../config/environment')

require 'rspec/rails'
require 'capybara/rspec'
require 'capybara-screenshot/rspec'
require 'selenium-webdriver'
require 'rack_session_access/capybara'
require 'rspec/retry'

require 'sidekiq/testing'
require 'simplecov'

...

Capybara.register_driver :chrome do |app|
  options = Selenium::WebDriver::Chrome::Options.new(
    args: chrome_args
  )
  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.default_max_wait_time = 5
Capybara.javascript_driver = :chrome

RSpec.configure do |config|
  ...
  config.include Capybara::DSL
  config.include JavascriptHelpers
  ...
end

Here is the full config https://pastebin.com/yGiap1KT

twalpole commented 4 years ago

@rgaufman requiring capybara/rspec will include the rspec matchers into feature and view specs, but rspec-rails also includes them into helper, mailer, controller, and system specs I believe. Check that the type of spec you're running actually has Capybara::RSpecMatchers included into that type of spec. If you don't have the matchers included correctly in your test scopes then when the matcher fails you'll get an error something like "expected has_content? to return true" (which is the error from RSpecs generic have matcher) rather than the Capybara error message of something like "expected to find text '....' but ..."

rgaufman commented 4 years ago

Every single spec just has this as the first line:

require 'spec_helper'

Inside the spec_helper.rb file is what I posted above which includes require 'capybara/rspec' and the DSL/Helpers, etc-- what am I missing?

twalpole commented 4 years ago

@rgaufman I don't know which you're missing - I don't have access to your project. You will need to do some investigation yourself. The root cause is that you don't have Capybara::RSpecMatchers included into whatever scope your tests are running in.

rgaufman commented 4 years ago

I've shared the full spec_helper, this is the only scope the tests are running in: https://pastebin.com/yGiap1KT

How would I troubleshoot this further?

twalpole commented 4 years ago

Of course there is scope the tests are running in -- It all depends on the type of the test, what's loaded in the test file, etc, etc. Set a breakpoint in your test, see if have_content is a defined method at that location. If it's not you need to figure out what scope you're actually in and make sure Capybara::RSpecMatchers gets included in that scope. If it is a defined method there, see what its source code location is.

rgaufman commented 4 years ago

Ok, here's an example:

require 'spec_helper'

describe 'Activations', :integration, type: :request do
  it "test spec" do
    visit '/activations'
     expect(page).to have_no_css('.no_such_css')
  end
end

If I use .to_not have_css it is waiting for an extra 5 seconds because of "Capybara.default_max_wait_time = 5"

When I try with a break point of binding.pry just above the expect:

tether-it(test)> defined? page.have_css
=> nil
tether-it(test)> defined? have_css
=> "method"
tether-it(test)> defined? have_no_css
=> "method"
tether-it(test)> defined? self.have_no_css
=> "method"
tether-it(test)> defined? self.have_css
=> "method"

Seems it's a defined method. What do I do now?

twalpole commented 4 years ago

First, look at the list of test types I mentioned above and notice that request is not one of them. That's because you shouldn't be using Capybara in request specs. Second, do something like method(:have_css).source_location and I'm 99.9% sure that it's not going to be in Capybara