Tests UJS Rails with Capybara, Selenium and Rspec - ruby-on-rails

On my page I have :
-->a form that scrapes the content of another website with a Ajax request made with ujs (remote: true):
<%= simple_form_for #listing, :url => listings_fetch_url, remote: true do |f| %>
<%= f.input :url %>
<%= f.button :submit, id: 'import_button' %>
<% end %>
-->another form that is populated by the scraping results from the form above
Manually it works...
However when I'm trying to automatically test it, it doesn't.
Capybara finds the submit button, clicks on it but then it leaves me on a blank page.
Here is the code for the test:
feature "Listing import" do
scenario "fills in the form fields", js: true do
visit new_search_path
fill_in 'Url', :with => 'www.google.fr'
find('input#import_button').click
expect(page.body).to have_content 'query was'
end
end
The controller actions are the following:
def fetch
url = listing_params[:url]
#query = Query.new(ScraperService.new({url: url}).perform)
render :fill
end
# Renders an js.erb file
def fill
end
It seems like rspec with selenium can't render js.erb files?
Any help is appreciated!
EDIT
Thanks to #ThomasWalpole, I fixed an error 500 which was preventing Mechanize to scrape the web. The content is now scraped correctly but the problem remains: the page after clicking on the submit button becomes blank and the tests ends right afer.
In the logs I have a lengthy message :
Could not log "render_template.action_view" event. NoMethodError:
undefined method example_group' for nil:NilClass
["/Users/Greg/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rspec-rails-3.6.0/lib/rspec/rails/view_rendering.rb:67:in
current_example_group'"
Which seems to be the same problem as : 1 & 2

Solved it!
The problem came from the fact that my js.erb wasn't adding a new element on the current page (i.e.: a new class, a new id, a new DOM element...).
Capybara never waited for the Ajax request to complete!
I appended a new line to my js.erb:
$("#import_button").addClass("imported");
This way I know that when the class imported is present on the current page it means the Ajax request is over.
Then in my test I added the following line:
expect(page).to have_selector :css, '#import_button.imported', wait: 10
Everything works fine now 🤘
P.S.: this website helped me understand the problem as well

Related

Rails 6 prompt to download on mobile

I have a Rails 6 app where users can submit changes to a model and a confirmation email will be sent to site admins. The changes are submitted through a modal popup and once submitted the popup is replaced with another that confirms that the email has been sent.
The problem I am having is when this is done on a mobile device (I'm using an iPhone, not sure if this happens on Android), once the change is submitted another popup appears prompting me to download a file of size 0 with the name of the model being updated (it happens in both Safari and Chrome). This doesn't happen on the desktop version of the site or in the mobile emulator on the desktop, so I can't think of how I could diagnose this issue.
Here is the code being called just before the download popup appears:
<%= button_tag "Submit", type: 'submit',
id: 'modal-subimt',
class: 'btn btn-primary',
onClick: 'replaceModal()' %>
Here is the create function being called when the form is submitted:
def create
... # Setting the parameters for the model being changed
if #model.save
#model.send_confirmation_email()
else
# Irrelevant because the email gets sent
end
end
Here is the send_confirmation_email function being called in create:
def send_confirmation_email()
UserMailer.model_confirmation(self).deliver_now
end
And here is the model_confirmation function being called by send_confirmation_email:
def model_confirmation(model)
#model = model
recipient = <admin email>
mail to: recipient, subject: "Model Confirmation"
end
Nowhere in this code can I see where I might be prompted to download a file, but alas it is happening. Any help would be appreciated on how to properly diagnose or solve this problem. Thanks!
EDIT:
I changed my code so that the model would be updated without sending a confirmation email Also, the modal is no longer replaced with the new one verifying an email has been sent, and I am still having the same issue. This leads me to believe that the problem is something with the creation of the model.
EDIT 2:
Previously I was creating the model and remaining on the page (with the confirmation that the email was sent). I changed my controller to redirect_to request.referer to reload the page once the change is made, and when that is done I am no longer prompted to download the empty file. Unfortunately, the way I want this to work, reloading the page isn't optimal. Is there any reason that I would be prompted for a download when updating a model without reloading the page?
SOLUTION:
I was able to solve this problem by adding remote: true to the form_for line like the following:
<%= form_for(Model.new, remote: true) do |f| %>
...
<%= button_tag "Submit", type: 'submit',
id: 'modal-subimt',
class: 'btn btn-primary',
onClick: 'replaceModal()' %>
<% end %>
I'm not sure why exactly this solved my problem though so if anyone can provide some insight that would be much appreciated!
The problem is related to how you instantiated the form_for. When you submit the form to a Model.new object, form_for try to infer the controller with the create method for handling it. See more how form_for works in https://apidock.com/rails/ActionView/Helpers/FormHelper/form_for
With that said, the create method is probably outputting something or nothing instead of redirecting, that's why you get a download on a mobile device and in browsers it's just an empty page because they can handle it. If there is no render on the method, Rails will answer with a 204 No Content or an empty text page. You must check your method about this.
When you use remote: true, I will quote from the docs:
:remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the submit behavior. By default this behavior is an ajax submit.
So instead of reloading the page, UJS sends a XMLHttpRequest to the "backend" page, that's why you don't get a redirect. The result of the submit is treated without redirecting. But based on the result of the request the content of the page can come. So it's important that you give a response accordingly. In the links below you will find the right way to do it.
See more in these questions:
How does ':remote => true' works in rails
:remote => true confusion with form_for
Also, in the Rails Guides:
https://guides.rubyonrails.org/form_helpers.html
https://guides.rubyonrails.org/working_with_javascript_in_rails.html
This is also a good reference:
http://www.korenlc.com/remote-true-in-rails-forms/

In Ruby (2.2) on Rails How Do I Have A Confirmation Box Pop Up Then Redirect To Another Page

In my controller I have an if condition and I want an alert/confirmation box to pop up if it is satisfied, then when the user clicks "ok" to redirect to another page.
I've tried
flash[:alert] = 'Successfully checked in'
redirect_to check_in_path
However it just skips the alert part and goes straight to the redirect without an alert message.
My Ruby version is ruby 2.2.6p396 and rails is 5.0.1
Any help would be appreciated
You need to create a js.erb file. You can then use your existing rails code along with a js alert box. Js.erb files let you combine rails and JavaScript code in the same file. As mentioned flash[:alert] is not an alert box, alert is a css class.
Rails is limited to the respond to HTTP request with HTML. And what you describe sounds more like a JavaScript alert. What you tried, if configured properly*, will simply show the flash alert when the check_in_path page is rendered.
* Get familiar with how the flash works, Rails Guides
You have your controller set up correctly. The only other part I meant by configured correctly is that in your views you should display said flash message:
<% flash.each do |name, msg| -%>
<%= content_tag :div, msg, class: name %>
<% end -%>
It's common to use the name of the flash to style it to make to make it's intent clear; commonly red for failure, green for success.
you can do it as follows
render :update do |page|
page << "alert('Successfully checked in')"
page.redirect_to check_in_path
end
but partial rendering in controller is most commonly used together with Ajax calls. so you may have to make an ajax call to your action from your view page. Hope this will help you

Rails link_to with remote: true processing html instead of js after page refresh

I have a search page in my app, where there is an ajax search form. The search form works properly, passing parameters to the model to filter the search, and the model returning a collection of results. Upon search submit, #results are rendered on the page. Each #result then has a link to an action on it, like:
<%=link_to "Message", message_user_path(:id => user.id), :remote => true%>
Where this action in the controller is:
respond_to :js, :html
def message
#user_id = params[:id]
#user = User.find_by_id(#user_id)
respond_to do |format|
format.html
format.js
end
end
and this responds with message.js.erb, which triggers a messaging panel to pop up with a message to the user. All of this is working correctly, checking the log I see the correct get request sent, and the correct format being processed:
Started GET "/users/3/message"
Processing by UsersController#message as JS
However, if I refresh the page and try to click the same link that was working before, I get the error Template is Missing. Checking the log, I see that now there are two requests sent, first an html then the same js request.
Started GET "/users/4/message"
Processing by StudentsController#message as HTML
...
Completed 406 Not Acceptable in 3ms (ActiveRecord: 1.0ms)
Started GET "/users/4/message"
Processing by StudentsController#message as JS
The html request throws the missing template error. Does anyone know why refreshing the page causes rails to attempt to respond with an html request to a remote link?
EDIT: routes.rb
resources :students do
member do
get 'message'
end
end
Does your app/assets/javascripts/application.js contain
//= require jquery
//= require jquery_ujs
?
And your erb contains <%= javascript_include_tag "application" %>?
I was just struggling with a problem like this for HOURS and the last of those two points fixed it; I saw the first point mentioned in some other questions so I'll repeat it here.
Hope that helps.
(Credit where credit's due)
What solver it for me was adding :format => "js"
So in your case:
<%=link_to "Message", message_user_path(:id => user.id, :format => "js"), :remote => true %>
In general, when you use link_to on a particular button or so, when you press the button, as js request is send to the controller, but also searches for the respective .js.erb file and so on.
My solution was to replace
format.json do
with
format.js do
you can troubleshoot the request by setting a breakpoint (i use pry) in the controller and then look at the variable
request.format
For newer versions of Rails, this should be fixed where using remote: true within the link_to code, as the original poster was doing, will only look for a .js format to respond with. As others have said, if you never need an html response, then you can remove that from your code all together; you won't even need a respond_to, respond_with, etc as Rails will auto respond with JS looking for the template you already have made. So your controller code would look like this:
def message
#user_id = params[:id]
#user = User.find_by_id(#user_id)
end
And the link would still be this:
<%=link_to "Message", message_user_path(:id => user.id), :remote => true %>
Or this would work as well (my preferred way of syntax):
<%=link_to "Message", message_user_path(id: user.id), remote: true %>
This code will call the controller action which will look for the template message.js.erb.
I know this question is old now, but for anyone looking for answers and using current Rails 6+ (I'm using 7.0.0alpha), and if you are getting this same type of issue where both HTML and JS templates are being requested; check that turbolinks is not what is causing the issue. Sometimes turbolinks can cause a request to be sent twice and it may be sending the first request as an HTML request.
My form with remote: true was inside another form and I didn't know it.
So make sure it isn't inside another form.

Add section to form by rendering a partial when link is clicked

UPDATE 3:
For anyone who reads this, this is why it wasn't working as expected in update 2 below: Passing a local variable to a partial that is rendered after the view has already loaded
If anyone knows how to solve that issue, let me know please.
UPDATE 2:
I updated the javascript with the quotation marks and it partially works...in the sense that the javascript is now functional and it will cause a string of text to appear on the page when I click the link as long as I have the partial only contain a string of text. However, when the partial includes the form fields code, something goes wrong.
If I just paste the following render code directly into the form in the new.html.erb view, it produces a new form section properly.
<%= render "add_round", f: f %>
However, when I try to include similar code in comps_helper.rb and then reference it from the link_to, it does not work:
In comps_helper.rb:
def addRound(f)
render "add_round", f: f
end
In new.html.erb:
<%= link_to "render it!", addRoundLink_path, remote: true %>
<div id="some_id"></div>
And I changed addRoundLink.js.erb to:
$("#some_id").html("<%=j addRound(f) %>"); #Is this the correct change to have made here?
Clicking the link_to link does nothing in that case.
Any thoughts?
UPDATED CODE:
Thanks for the reply. I've made the following changes and it still does not appear to be working. The link appears at the bottom of the form but when clicked does not change anything. What am I missing?
routes.rb:
resources :comps
match '/new_competition', :to => "comps#new"
get "/addRoundLink" => "comps#addRoundLink", :as => :addRoundLink
Note: I included the other 2 lines related to "comps" just in case those would cause an issue.
comps_controller.rb:
def addRoundLink
respond_to do |format|
format.js
end
end
comps_helper.rb:
def addRound
render "add_round"
end
addRoundLink.js.erb:
$("#some_id").html(<%=j addRound %>);
comps/new.html.erb:
<%= link_to "render it!", addRoundLink_path, remote: true %>
<div id="some_id"></div>
Thanks.
ORIGINAL QUESTION
First off, I'm new to rails. I've read and tried many solutions to similar questions but nothing has worked so far.
I created a form with rails form_for and fields_for. The form creates a new competition (comp). The competition has many rounds. The top half of the form (the form_for section) accepts the details about the competition as inputs and the bottom half of the form accepts details about each round (the fields_for section). The form works perfectly in this basic format.
I took all the code that is in the fields_for section and put it into a partial. My plan was to then create a "add new round" link to the bottom of the form that would simply display the partial above the link each time the link is pressed. This would add a new section to the form for a new round and allow the user to input as many rounds as they'd like. This is the part that I am struggling to make work.
I added this code to my comps_helper:
def addNewRound
render "add_round"
end
This renders the file /views/comps/_add_round.html.erb.
My question is: how do I get this to render in the form when a link is clicked. As far as I can get with the research I have done is:
<%= link_to "Add new round", { }, :remote => true %>
I don't exactly know what is supposed to go in the {} that will execute the addNewRound method. And I don't know what, if anything, I need to add to my comps_controller file.
Thanks so much for the help.
You have to create an action in your controller
app/controllers/some_controller.rb
def hello
respond_to do |format|
format.js
end
end
and define a route to this action.
routes.rb
get "/hello" => "some#hello", :as => :hello
then create a link to this action like that:
<%= link_to "render it!", hello_path, remote: true %>
<div id="some_id"></div>
When you click this link it will find its way to your action and respond with js(javascript) because we told action to respond with only js.
At the end render the partial to anywhere you want in your view(*in this example to the some_id div*)
app/views/some/hello.js.erb
$("#some_id").html("<%=j addNewRound %>");
WARNING: Creating dynamic forms is a pain. You will face a lot of problems (like setting different ids for new form elements etc...). I highly recommend you to use ryan bates nested_form gem

Can not send a ajax request from a form_tag

I am trying to send a ajax request from a form tag, but no XHR request is sent. If I do the same from a link everything works perfectly.
Here's the view from which I can send the request either from a link or a form:
<div id="request_test">
<%= link_to "test", explorer_test_path(:format => :js), :remote => true %>
<%= form_tag explorer_test_path(:format => :js), :id => "testform", :remote => true, :method => :get do %>
<%= submit_tag "Send test request"%>
<% end %>
</div>
<div id="response_test"></div>
In my controller I then check if it is a XHR request or not and then render the javascript responding to the ajax request:
def test
if request.xhr?
logger.debug "xhr"
else
logger.debug "not xhr"
end
respond_to do |format|
format.js
end
end
My test.js.erb is very simple:
$("#response_test").html("<b>hi</b>");
The result is that if I click the link I get the behavior I am looking for, ie that the response_test div reads "hi". If I submit the form I simply get the javascript in test.js.erb written out as a string and the server log telling me that the form didn't submit a XHR request. If i debug the whole thing in firebug I get the same result, the link sends an ajax request, the submitted form does not. In both cases my console tells me that the server is processing the request as JS, so it shouldn't be any data-type problem.
Running Rails 3.0.4 with jquery UJS.
Thankful for any hints to what the problem might be, I have tried to figure this out for quite some time now but since I am relatively new to Rails I suspect that I am missing something basic.
UPDATE: Adding the following javascript from (http://www.alfajango.com/blog/rails-jquery-ujs-now-interactive/) fixes the problem, which suggests that the :remote => true on my form tag isn't found or interpreted correctly by the jquery ujs. I see this as a workaround more than a permanent fix so if anybody have any idea of what is wrong in my original code I would be very grateful.
$('#testform').live('submit', function(e) {
$.rails.handleRemote( $(this) );
e.preventDefault();
});

Resources