Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

disable_net_connect! affects subsequent Capybara test even if WebMock is disabled #870

Open
asterite opened this issue Jan 7, 2020 · 7 comments

Comments

@asterite
Copy link

asterite commented Jan 7, 2020

This one is pretty strange and I can't figure out what's going on.

The gist

If you have a spec like this:

require "rails_helper"

describe "home", js: true do
  it "foo" do
    WebMock.enable!
    WebMock.disable_net_connect!(allow_localhost: true)

    visit root_path

    WebMock.disable!
  end

  it "bar" do
    WebMock.disable_net_connect!(allow_localhost: false)
  end

  it "baz" do
    visit root_path
  end
end

and run it in order (rspec --order defined) then you get two failures in "baz":

          WebMock::NetConnectNotAllowedError:
            Real HTTP connections are disabled. Unregistered request: POST http://127.0.0.1:4444/session/e3eddb2c-cf51-494f-8e73-7ff862ee39fa/url with body '{"url":"http://127.0.0.1:54289/"}' with headers {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'33', 'Content-Type'=>'application/json; charset=UTF-8', 'User-Agent'=>'selenium/3.142.7 (ruby macosx)'}

          WebMock::NetConnectNotAllowedError:
            Real HTTP connections are disabled. Unregistered request: GET http://127.0.0.1:4444/session/e3eddb2c-cf51-494f-8e73-7ff862ee39fa/alert/text with headers {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Cache-Control'=>'no-cache', 'Content-Type'=>'application/json; charset=UTF-8', 'User-Agent'=>'selenium/3.142.7 (ruby macosx)'}

If we remove the first test and only keep the last two:

require "rails_helper"

describe "home", js: true do
  # it "foo" do
  #   WebMock.enable!
  #   WebMock.disable_net_connect!(allow_localhost: true)

  #   visit root_path

  #   WebMock.disable!
  # end

  it "bar" do
    WebMock.disable_net_connect!(allow_localhost: false)
  end

  it "baz" do
    visit root_path
  end
end

and run the specs with rspec --order defined, the tests pass.

Note that in both cases when we reach "baz", WebMock is disabled and configured to disallow net connections, but in the first case it fails... I don't know why, but it seems that allowing localhost and visiting the first path somehow affects what happens with subsequent test.

Where is the problem?

The problem seems to be a weird interaction of WebMock, RSPec, Selenium and Capybara. Maybe the problem is not in WebMock.

Also, if we remove js: true from the first snippet, it works fine... so maybe Capybara is setting up something that eventually messes up the way WebMock works?

But maybe you have an idea of why this is happening?

Full steps to reproduce

The short method

You can download this zip file
test_capybara_webmock.zip , uncompress it, and inside that directory run:

bundle exec rspec spec/features/home_spec.rb

The long method

  • rails new test_capybara_webmock -T
  • cd test_capybara_webmock
  • Add this to the Gemfile:
    group :test do
      gem 'rspec-rails'
      gem 'capybara'
      gem 'selenium-webdriver'
      gem 'geckodriver-helper'
      gem 'webmock'
    end
  • Run bundle
  • Create a spec/features/home_spec.rb file with the contents of the first snippet in this post.
  • Run bundle exec rspec spec/features/home_spec.rb and see it fail

The End

Let me know if something is not clear! I'm happy to help figuring out what's going on, and fixing it if possible.

Thank you!

@bblimke
Copy link
Owner

bblimke commented Jan 10, 2020

@asterite I confirm that something is weird here and I don't know why. Perhaps there is another ruby process started on the first visit which is not affected by disable. Not sure.

@davidstosik
Copy link

davidstosik commented Apr 24, 2020

have you tried adding an expectation after visit root_path that would make sure the page is loaded before the execution continues?

When running Capybara with a JS profile, the visit command only tells the browser (running in a separate process) to visit the passed path/URL, then continues execution. There is a chance the spec's teardown (after and around blocks) get run before the browser actually made the request to root_path. If you add an expectation in the form of expect(page).to have_text("something that is on your homepage"), you will ensure that your browser has made the request to your site before the tear down (including WebMock.reset! happens).

I have had a similar issue and identified that the problem was that WebMock.reset! needs to happen after Capybara.reset_sessions!.

The order of these two after blocks is defined by the order in which the require for the respective files happen.

I found out, in my project, that we were calling require "webmock/rspec" after require "rspec/rails" (which itself will lead to requiring capybara/rspec). That meant that WebMock.reset! would happen before Capybara.reset_sessions!, and that there would be a time window during which WebMock has been reset (and is ready for the next spec) while Capybara's browser was still active and likely to issue requests to the site (which itself was querying external resources that we want stubbed).

If, during that small time window, the browser is able to trigger a request to the application, and the application makes a request to the outside world, then that request would should in WebMock's registry for the next spec.

Fixing the require order (which should not have been a problem to begin with, had we followed the documentation strictly) fixed the problem for us, and it feels like it should fix it for you too.

Now I wonder how #865 could impact our problem. Would it be an improvement or make it worse?

@KirillKayumov
Copy link

If anyone is experiencing the problem, take a look at the Gotchas section: https://github.com/teamcapybara/capybara#gotchas

@davidstosik
Copy link

davidstosik commented Jan 4, 2021

@KirillKayumov Thanks for the link. Which Gotcha in particular do you think is relevant here? 🙏🏻

@KirillKayumov
Copy link

@davidstosik

If WebMock is enabled, you may encounter a "Too many open files" error. A simple page.find call may cause thousands of HTTP requests until the timeout occurs. By default, WebMock will cause each of these requests to spawn a new connection. To work around this problem, you may need to enable WebMock's net_http_connect_on_start: true parameter.

@davidstosik
Copy link

@KirillKayumov Thank you. To me, that gotcha seems unrelated to the initial problem in this issue?
Please correct me if I'm mistaken, but I don't think this is about "too many open files", and I don't think net_http_connect_on_start: true would fix the problem described in the top message.

@KirillKayumov
Copy link

@davidstosik yes, you're right, I'm sorry for the confusion.
As far as I understand, WebMock.disable_net_connect! is designed to apply the configuration globally and as a consequence, it affects the following test scenarios.
Here .reset! is called after each example:

config.before(:suite) do
WebMock.enable!
end
config.after(:suite) do
WebMock.disable!
end
config.around(:each) do |example|
example.run
WebMock.reset!
end
but it does different things that are not related to what .disable_net_connect! does
def self.reset!
WebMock::RequestRegistry.instance.reset!
WebMock::StubRegistry.instance.reset!
end

def self.disable_net_connect!(options = {})
Config.instance.allow_net_connect = false
Config.instance.allow_localhost = options[:allow_localhost]
Config.instance.allow = options[:allow]
Config.instance.net_http_connect_on_start = options[:net_http_connect_on_start]
end

So, I may recommend doing:

  1. Do not call .enable! and .disable! methods as they're probably already called by the integration with rspec
  2. Use . allow_net_connect! to reset webmock configuration between test scenarios.

I hope it'll help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants