webmock icon indicating copy to clipboard operation
webmock copied to clipboard

`disable_net_connect!` affects subsequent Capybara test even if WebMock is disabled

Open asterite opened this issue 5 years ago • 7 comments
trafficstars

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!

asterite avatar Jan 07 '20 12:01 asterite

@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.

bblimke avatar Jan 10 '20 11:01 bblimke

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?

davidstosik avatar Apr 24 '20 10:04 davidstosik

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

KirillKayumov avatar Dec 28 '20 13:12 KirillKayumov

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

davidstosik avatar Jan 04 '21 06:01 davidstosik

@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.

KirillKayumov avatar Jan 14 '21 11:01 KirillKayumov

@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.

davidstosik avatar Jan 15 '21 01:01 davidstosik

@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: https://github.com/bblimke/webmock/blob/d19b472d0610d4e435e44f8b86564dfbef6f6499/lib/webmock/rspec.rb#L28-L39 but it does different things that are not related to what .disable_net_connect! does https://github.com/bblimke/webmock/blob/d19b472d0610d4e435e44f8b86564dfbef6f6499/lib/webmock/webmock.rb#L117-L120 https://github.com/bblimke/webmock/blob/d19b472d0610d4e435e44f8b86564dfbef6f6499/lib/webmock/webmock.rb#L49-L54

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!

KirillKayumov avatar Jan 15 '21 08:01 KirillKayumov