stooshie45
stooshie45

Reputation: 69

Request in Capybara a GET, when it should be a POST

So this is a pretty basic Rails 5 application using Docker, which includes some user authentication that I built from scratch (not using Devise etc). Now, I want to start learning about request specs with Capybara, but I'm hitting what seems like a pretty strange issue with it.

Here's my login form (sessions.new.erb):

<%= form_tag sessions_path do %>
          <form class="m-t" role="form" action="/">
              <div class="form-group">
                <%= text_field_tag :email, params[:email], class: 'form-control', placeholder: "Email Address", required: "" %>
              </div>
              <div class="form-group">
                <%= password_field_tag(:password, nil, class: 'form-control', placeholder: "Password", required: "") %>
              </div>
              <div class="form-group">
                        <%= check_box_tag :remember_me, 1, params[:remember_me] %>
                        <%= label_tag :remember_me %>
                      </div>
              <div class="actions"><%= submit_tag "Log In", class: "btn btn-primary block full-width m-b" %></div>
          </form>
        <% end %>

And my requests/sessions_spec.rb:

require "rails_helper"

RSpec.feature "Login", :type => :feature do
  scenario "handles wrong email and password gracefully" do
    visit login_path
    fill_in "Email Address", :with => "something"
    fill_in "Password", :with => "something"
    click_button "Log In"

    expect(page).to have_text("Email or password incorrect")
  end
end

Now, this works if you test it manually so I would presume Capybara would see the same thing. But it kept failing. I've got the application configured so that if you try and access a protected controller and you're not logged in, it redirects you to /login and flashes a message to say Please log in to see this page. The Rspec test was returning that, which was weird - that suggested that Capybara was trying to visit another page.

So I tailed the test logs (docker-compose run web tail -f log/test.log)

And what I found is puzzling me:

Started GET "/login" for 127.0.0.1 at 2017-10-17 06:59:26 +0000
Processing by SessionsController#new as HTML
  Rendering sessions/new.html.erb within layouts/empty
  Rendered sessions/new.html.erb within layouts/empty (1.1ms)
Completed 200 OK in 6ms (Views: 6.1ms | ActiveRecord: 0.0ms)
Started GET "/?email=something&password=[FILTERED]&commit=Log+In" for 127.0.0.1 at 2017-10-17 06:59:26 +0000
Started GET "/locations" for 127.0.0.1 at 2017-10-17 06:59:26 +0000
Processing by LocationsController#index as HTML
Redirected to http://www.example.com/login
Filter chain halted as :authenticate rendered or redirected
Completed 302 Found in 2ms (ActiveRecord: 0.0ms)
Started GET "/login" for 127.0.0.1 at 2017-10-17 06:59:26 +0000
Processing by SessionsController#new as HTML
  Rendering sessions/new.html.erb within layouts/empty
  Rendered sessions/new.html.erb within layouts/empty (1.1ms)
Completed 200 OK in 6ms (Views: 4.9ms | ActiveRecord: 0.0ms)
   (0.4ms)  ROLLBACK

The first bit is okay, GET Login is processed by the SessionsController#new. But then, (see line 6) for some reason Capybara tried to GET the root URL, passing the email/password params in. My root URL is mapped to the LocationsController#index, which the user isn't allowed to access, so gets redirected back to /login with the message Please log in to see this page. What that button actually does is send a POST to SessionsController#create. And if you watch the logs when you do it manually, that's exactly what happens:

web_1  | Started POST "/sessions" for 172.18.0.1 at 2017-10-17 07:02:19+0000
web_1  | Processing by SessionsController#create as HTML

I can't work out why in Capybara when you press the button it performs a completely different request to when you click the button manually.

Help greatly appreciated!

Upvotes: 1

Views: 818

Answers (1)

Thomas Walpole
Thomas Walpole

Reputation: 49890

A couple of clarifications first.

  1. You're not writing request specs, you're writing feature specs (as evidenced by the use of RSpec.feature and :type => :feature
  2. You don't need to specify :type => :feature when using RSpec.feature since that's already set.

Now on to your issue. You have nested forms in your view code since form_tag creates a <form>element and then you have another <form> element directly inside that (Note: it's always better to post the actual HTML rather than the erb so people can see what the actual HTML is). Combine that with the fact you appear to be using the rack-test driver (no js: true metadata) which won't behave the same way as a real browser when the HTML is invalid (which nested forms are), and you end up with your current behavior. I would guess when you use it with a real browser the inside form element is ignored and the outer form element has a method attribute equal to "post" so it gets posted. When using rack-test it's probably submitting the inner form element which has no method attribute and therefore defaults to "get". Remove the extraneous <form class="m-t" role="form" action="/"> form element from your view and things should work.

Upvotes: 2

Related Questions