How do you POST to a URL in Capybara?

29,386

Solution 1

More recently I found this great blog post. Which is great for the cases like Tony and where you really want to post something in your cuke:

For my case this became:

def send_log(file, project)
  proj = Project.find(:first, :conditions => "name='#{project}'")
  f = File.new(File.join(::Rails.root.to_s, file))
  page.driver.post("projects/" + proj.id.to_s + "/log?upload_path=" + f.to_path)
  page.driver.status_code.should eql 200
end

Solution 2

You could do this:

rack_test_session_wrapper = Capybara.current_session.driver
rack_test_session_wrapper.submit :post, your_path, nil
  • You can replace :post which whatever method you care about e.g. :put or :delete.
  • Replace your_path with the Rails path you want e.g. rack_test_session_wrapper.submit :delete, document_path(Document.last), nil would delete the last Document in my app.

Solution 3

If your driver doesn't have post (Poltergeist doesn't, for example), you can do this:

session = ActionDispatch::Integration::Session.new(Rails.application)
response = session.post("/mypath", my_params: "go_here")

But note that this request happens in a new session, so you will have to go through the response object to assert on it.

As has been stated elsewhere, in a Capybara test you typically want to do POSTs by submitting a form just like the user would. I used the above to test what happens to the user if a POST happens in another session (via WebSockets), so a form wouldn't cut it.

Docs:

Solution 4

Capybara's visit only does GET requests. This is by design.

For a user to perform a POST, he must click a button or submit a form. There is no other way of doing this with a browser.

The correct way to test this behaviour would be:

visit "project/:id/edit" # This will only GET
attach_file "photo", File.open('cute_photo.jpg')
click_button 'Upload' # This will POST

If you want to test an API, I recommend using spec/request instead of cucumber, but that's just me.

Solution 5

I know the answer has already been accepted, but I'd like to provide an updated answer. Here is a technique from Anthony Eden and Corey Haines which passes Rack::Test to Cucumber's World object:

Testing REST APIs with Cucumber and Rack::Test

With this technique, I was able to directly send post requests within step definitions. While writing the step definitions, it was extremely helpful to learn the Rack::Test API from it's own specs.

# feature
  Scenario: create resource from one time request
    Given I am an admin
    When I make an authenticated request for a new resource
    Then I am redirected  
    And I see the message "Resource successfully created" 

# step definitions using Rack::Test
When /^I make an authenticated request for a new resource$/ do
  post resources_path, :auth_token => @admin.authentication_token
  follow_redirect!
end

Then /^I am redirected$/ do
  last_response.should_not be_redirect
  last_request.env["HTTP_REFERER"].should include(resources_path)
end

Then /^I see the message "([^"]*)"$/ do |msg|
  last_response.body.should include(msg)
end
Share:
29,386
Clinton
Author by

Clinton

Builds things

Updated on April 23, 2020

Comments

  • Clinton
    Clinton about 4 years

    Just switched from Cucumber+Webrat to Cucumber+Capybara and I am wondering how you can POST content to a URL in Capybara.

    In Cucumber+Webrat I was able to have a step:

    When /^I send "([^\"]*)" to "([^\"]*)"$/ do |file, project|
      proj = Project.find(:first, :conditions => "name='#{project}'")
      f = File.new(File.join(::Rails.root.to_s, file))
      visit "project/" + proj.id.to_s + "/upload",
            :post, {:upload_path => File.join(::Rails.root.to_s, file)}
    end
    

    However, the Capybara documentation mentions:

    The visit method only takes a single parameter, the request method is always GET.always GET.

    How do I modify my step so that Cucumber+Capybara does a POST to the URL?

  • Abhinav Kaushal Keshari
    Abhinav Kaushal Keshari over 13 years
    This sounds right. I have a situation where I want to make sure users with a specific permission cannot POST to a URL. So I guess what you're saying is Capybara+Cucumber should not be used for that.
  • Ramon Tayag
    Ramon Tayag almost 13 years
    Unfortunately I'm getting undefined method 'post' for #<Capybara::Selenium::Driver:0x3f75e10> (NoMethodError).
  • Clinton
    Clinton over 12 years
    Looks like the latest versions of capybara break this -- my gemfile currently restricts to an earlier version: gem 'capybara', "0.4.1.2"
  • Zubin
    Zubin over 12 years
    Although it's not required for your example, it's better to use page.driver.post so it's in the same context as other steps.
  • B Seven
    B Seven over 11 years
    Yep, this happened for me too. Used to work on another computer a few months ago. I guess running an earlier version will fix it.
  • evedovelli
    evedovelli over 10 years
    It may be useful to test a POST. For example, if can use it to test your code is resilient against bots trying to post things where they should not be allowed to.
  • Jaco Pretorius
    Jaco Pretorius over 7 years
    I get undefined method submit' for #<Capybara::Poltergeist::Driver:0x007f8cdcc699c0>` - Capybara 2.9.0
  • Todd
    Todd about 7 years
    Dont use Capybara for making HTTP POST requests. Its designed to emulate a user using a web browser. The user doesnt form HTTP POST requests, the user clicks buttons and links and submits forms and such. Use RSpec Request specs for testing responses from POST, PUT, DELETE requests. Check out this blog post: matthewlehner.net/rails-api-testing-guidelines
  • jwadsack
    jwadsack almost 7 years
    This seems to be the most up-to-date response and works with Webkit driver as well (which doesn't have the post method)
  • Todd
    Todd over 6 years
    This seems like an unconventional use of Capybara, and while it may allow the OP to achieve desired functionality, it may induce an antipattern into the test suite. RSpec Request specs are much better suited to testing the behavior of an HTTP POST to an application.
  • Luke Rohde
    Luke Rohde almost 6 years
    This is beautiful - needed to post though the api, to see something happen in the user's browser via websockets. THANKS!
  • oliverguenther
    oliverguenther over 5 years
    The response object returned by post is just the status code, you can access the response object with session.response afterwards, which also has the #body method. Together with this piece of information, POSTing outside Capybara works perfectly!
  • XtraSimplicity
    XtraSimplicity about 5 years
    It's not that simple, anymore, as Rack::Test::Methods requires the instance method app to return an instance of the Rack application. github.com/rack-test/rack-test/blob/master/lib/rack/test/…
  • jangosteve
    jangosteve about 5 years
    In my case, the user actually does submit an HTTP request from their command line, which returns some data, including a unique URL, which they then visit in their browser. The integration test I need would perform a GET request, receive data including a URL, and then follows the URL in the browser and ensures the displayed data in the browser matches the data returned by the GET request. The problem with blanket statements like, "The user doesn't..." is that they assume you know what everyone's users do.
  • Arnaud Meuret
    Arnaud Meuret over 4 years
    @jangosteve Todd merely said that Capybara was designed to emulate a user using a browser. Your users do not use a browser, do not expect Capybara to be able mimic them.
  • Ian Vaughan
    Ian Vaughan over 4 years
    Its needed when testing 3rd party callbacks that make post requests.