After updating to Rails 4.1, I got an interesting problem with Cucumber and Capybara in a new project.
Inside a view I placed some thumbnail portraits. The user is supposed to click on a thumbnail image link to receive more information about the person he has chosen. Through the magic of AJAX the information then appears below the thumbnails. Here's how i did it in the view:
<%= link_to( image_tag( ... ), "/controller/action.js&person=#{#person.nickname}", id: #person.thumb_id , remote: true) %
The controller follows the usual proceeding for cases like this with
respond_to do format.js end
etc.
Works perfectly in the browser and I love it.
However, Cucumber and Capybara don't work so smoothly. Here's the Capybara line that's giving me a lot of headache:
When(/^I click on one of the portraits to display the person's stuff$/) do
click_link("jack_sparrow_THUMB") # #user.thumb_id
end
Running the scenario with Cucumber, I receive this error message for the statement above:
Security warning: an embedded <script> tag on another site requested protected
JavaScript. If you know what you're doing, go ahead and disable forgery protection
on this action to permit cross-origin JavaScript embedding.
(ActionController::InvalidCrossOriginRequest)
The problem must have to do with this
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
Just have a look at the CROSS_ORIGIN_JAVASCRIPT_WARNING provided ... :(
Is there anything I can do to make my tests run again without downgrading to rails < 4.1 or even turning off Request Forgery Protection in general? Help would be very much appreciated.
As per "CSRF protection from remote tags " from the rails guide:
In the case of tests, where you also doing the client, change from:
get :index, format: :js
To:
xhr :get, :index, format: :js
http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#csrf-protection-from-remote-script-tags
Related
I'm trying to create an "asset controller" shim which will filter static asset requests so only authorized users can get retrieve certain assets. I wanted to continue to use the asset pipeline so I setup a route like this
get 'assets/*assetfile' => 'assets#sendfile'
Then I created an AssetsController with one method "sendfile". Stripping it down to only the stuff that matters, it looks like this:
class AssetsController < ApplicationController
def sendfile
# Basically the following function forces the file
# path to be Rails.root/public/assets/basename
assetfilename=sanitize_filename(params[:assetfile] + '.' + params[:format])
send_file(assetfilename)
end
end
It looks like I have to run this in production mode as rails by-passes my route for assets in development. So I precompile my assets and I can verify in the controller that the files exist where they are expected to be.
However, now the problem is that I'm getting a "ActionController::InvalidCrossOriginRequest" when the Javascript asset is requested (just using the default application.* assets for now). I've read about this error and I understand that as of Rails 4.1 there are special cross-origin protections for Javascript assets. Sounds good to me, but I don't understand where the "cross-origin" part is coming from. Using firebug, I can see that the asset requests are being requested from the same domain as the original page.
I am certain that this is the problem because I can solve it by putting "skip_before_action :verify_authenticity_token" in the beginning of my controller. However, I really don't want to do this (I don't fully understand why this check is necessary, but I'm sure there are very good reasons).
The application.html.erb file is unchanged from the default generated file so I assume it's sending the CSRF token when the request is made, just as it would if I didn't have my own controller for assets.
So what am I missing?
Ok, I think I answered my own question (unsatisfactorily). Again, long post, so bear with me. I mistakenly forgot to add this to my original questions, but I'm using Ruby 2.2.0 and Rails 4.2.4.
From looking at the code in "actionpack-4.2.4/lib/action_controller/metal/request_forgery_protection.rb", it looks like Rails is doing two checks. The first check is the "verify_authenticity_token" method which does the expected validation of the authenticity token for POST requests. For GET requests, it ALSO sets a flag which causes a second check on the formed computed response to the request.
The check on the response simply says that if the request was NOT an XHR (AJAX) request AND the MIME Type of the response is "text/javascript", then raise an "ActionController::InvalidCrossOriginRequest", which was the error I was getting.
I verified this by setting the type to "application/javascript" for ".js" files in "send_file". Here's the code:
if request.format.js?
send_file(assetfilename, type: 'application/javascript')
else
send_file(assetfilename)
end
I can skip the response check all together by just adding the following line to the top of my controller class:
skip_after_action :verify_same_origin_request
The check on the response seems pretty weak to me and it's not clear how this really provides further protection against CSRF. But I'll post that in another question.
In my rails controller i have:
def update
render #bill if #bill.update(bill_params)
end
On the form i have a remote true, so the render gets loaded on the page using ajax.
But in my capybara tests i have:
fill_in('bill_period', with: #bill.period)
fill_in('bill_groupname', with: #bill.groupname)
click_button 'update bill'
save_and_open_page
The page now opens in the render instead of the page it should be rendering on. It doesnt do this in the application itself only via capybara.
How to i prevent capybara from following the render?
It sounds like you're using the default capybara driver (rack_test) which doesn't support javascript. You need to switch to the selenium driver or another third party driver in order to have javascript (and hence ajax) support - https://github.com/jnicklas/capybara#selecting-the-driver
I have a Rails 4.2 application....I was adding content compression via this thoughtbot blog post, but I get an error such as:
undefined method `get' for #<RSpec::ExampleGroups::Compression:0x00000009aa4cc8>
Perusing over the capybara docs, it seems like you shouldn't be using get. Any idea how to test the below then in Rails 4?
# spec/integration/compression_spec.rb
require 'spec_helper'
feature 'Compression' do
scenario "a visitor has a browser that supports compression" do
['deflate','gzip', 'deflate,gzip','gzip,deflate'].each do|compression_method|
get root_path, {}, {'HTTP_ACCEPT_ENCODING' => compression_method }
response.headers['Content-Encoding'].should be
end
end
scenario "a visitor's browser does not support compression" do
get root_path
response.headers['Content-Encoding'].should_not be
end
end
In a capybara test you would use visit not get (as described here), but that answer won't actually help you because the test you've written above is not an integration test, it's a controller test.
Move it to spec/controllers and use the controller-specific helpers describe/context/it etc. to construct your tests for your controller. You can set the headers and do the sorts of checks that you're doing in the code you're showing.
I'm writing an integration test for a rails application using webrat. After filling out a form, the user presses submit and an account is created.
click_button "Submit"
assert_contain "Your Account Has Been Created"
However, the test fails:
expected the following element's content to include "Your Account Has Been Created":
You are being redirected.
<false> is not true.
Normally to follow a redirect I would use post_via_redirect, but from just looking at Webrat's examples, click_button followed by assert_contain should work
I just started using Webrat, so am I missing something obvious here? Why am I stuck with the redirect response?
Thanks!
Deb
With a new Rails 3 app, I also had this problem testing a simple method which included a redirect_to call in the controller. The method itself worked fine, but Webrat would return the "You are being redirected." response.
Adding in a 'Then show me the page' step in cucumber (so the page that webrat sees opens in the browser) showed the 'You are being redirected." response with a link to an example.org link.
Based on this I discovered Yannimac's patch ( http://groups.google.com/group/webrat/browse_thread/thread/fb5ff3fccd97f3df ):
#/lib/webrat/core/session.rb
#starting at line 288
def current_host
- URI.parse(current_url).host || #custom_headers["Host"] || "www.example.com"
+ URI.parse(current_url).host || #custom_headers["Host"] || default_current_host
end
+ def default_current_host
+ adapter.class==Webrat::RackAdapter ? "example.org" : "www.example.com"
+ end
Making these changes fixed the issue, so redirect_to calls with Webrat now work correctly.
There are some issues with rails 3 and webrat. Please see:
http://baldowl.github.com/2010/12/06/coercing-cucumber-and-webrat-to-cooperate.html
Do you have any authentication in your apps? I presume the redirection is because of you have not been authenticated. If my assumption is right, write a setup to login first with Webrat.
Here is the gist with exactly what you need to do to solve this problem.
https://gist.github.com/752766
Hi I do not have any front end in my app. I am willing to release just a RESTful API which can be used by different clients. Any pointers how should I proceed towards testing it with cucumber? Every action in the controller generates XML feed only. Any pointers or suggestions?
The visit function of webrat accepts a http_method as a second parameter. You can also test your api like in the following cucumber rule:
When /^I restfully delete (?:|the )user "([^\"]*)"$/ do |login|
visit(path_to("user \"#{login}\" page"), :delete)
end
I think Webrat is more than what you need.
For XML feed testing, you don't need a browser simulator like Webrat which would load pages and analyse all the markup (links, forms etc.) when you really don't have any HTML pages.
You rather need something like Curl (http://curl.haxx.se) or Curb (on rubyforge, which are ruby bindings for Curl), or Patron (on rubyforge).
These libraries can make a request header as per your liking (e.g. setting Content-Type, choosing among GET PUT POST DELETE HEAD etc.) and obtain the response, and probably follow 302 redirections when needed.
The response returned, can be then turned into XML object, and XML parsers available for Ruby can be used to test the output. Also, you can write XMLMapping classes (on rubyforge) to convert XML output into Ruby objects and test their attributes etc. This is much cleaner, IMHO.
jayzes has shared his cucumber test steps examples using Rack::Test::Methods, JSONpath, Nokogiri etc to write test for json/xml API, you might want to refer and create more for your own steps.
https://github.com/jayzes/cucumber-api-steps
Once you've set up your RESTful routes, you should be able to use Webrat to visit the different routes. You can then test that each route returns XML which meets your expectations.
Here's a blog post that describes how to test XML output in RSpec:
Testing XML output
Webrat is a headless browser, which simply means that you can simulate a browser without having to open a real browser like FireFox on your development machine. This means that you can simply type something like "visit 'users/'" into your defined steps and simulate a user accessing your application.
Finally the Pragmatic book on RSpec (still in beta), is a great resource on how to use Cucumber, Webrat and RSpec together and drive your application development with BDD.
I was trying to do that and got stuck in a major problem with restful_authentication (using AASM, one of the internal model of restful_auth it seems) and got to that solution to log in:
Given /^I am logged in with a new account$/ do
login = "test"
#current_user = User.new(
:login => login,
:password => 'generic',
:password_confirmation => 'generic',
:email => "#{login}#example.com",
:state => "active"
)
#current_user.save
x = User.find_by_login(login)
x.state = "active"
x.save!
visit "/login"
fill_in("login", :with => login)
fill_in("password", :with => 'generic')
click_button
response.body.should =~ /Logged in successfully/m
end
Modularize it for cleaner testing corpus, this is to demo the concept I found.