Test that a toast fires off in a Capybara feature spec? - ruby-on-rails

I have a Rails app that I've recently refactored standard flash[:success] messages to use a flash[:toast], using http://materializecss.com/dialogs.html
Here's what I'm doing with my flash partial:
<% flash.each do |type, message| %>
<% if type == "success" %>
<div class="alert alert-success alert-dismissable" role="alert">
...
</div>
<% elsif type == "toast" %>
<script>
$(function() {
Materialize.toast('<%= message %>', 3000);
});
</script>
<% else %>
<div class="alert alert-danger alert-dismissible" role="alert">
...
</div>
<% end %>
<% end %>
This works and looks awesome, especially on mobile and for complex data pages where I'm sending the user back to the middle of the page, and a standard flash success message on the top of the page would not be visible.
I can easily test if flash[:toast] is not nil in a controller test, but in capybara feature tests, I don't have access to that, and I can't use the standard code that I used to use to test flash[:success] like:
expect(page).to have_content("Your email address has been updated successfully.")
expect(page).to have_css(".alert-success")
Right now I am resorting to just testing that an alert danger is not present, like:
expect(page).to_not have_css(".alert-danger")
This works, but isn't really testing that the toast fired off. Is there any way to check that the javascript fired or that the toast appeared on the page for the 3 seconds?

As long as you're using a JS capable driver (pretty much anything but the default rack-test driver) then
expect(page).to have_content("The message shown by the toast")
should find the toast when it displays on the page. If you want to check that it appears and disappears within 3 seconds you could do something like
expect(page).to have_content("The message shown by the toast")
expect(page).not_to have_content("The message shown by the toast", wait: 3)
The first statement should wait for the text to appear and the second will wait up to 3 seconds for it to disappear. If you want to actually verify the text is being shown in the toast container then you could do
expect(page).to have_css("#toast-container", text: "The message shown") #update css selector to whatever container you are displaying the toast in
expect(page).not_to have_css("#toast-container", text: "The message shown", wait: 3)

Related

Rails flash message disappear after few seconds

I am setting my flash message in controller. But flash message is displayed only for few seconds before Vue.js render it's template and flash message disappear. My controller code looks like this:
class ResultsController < ApplicationController
def new
...
end
def create
...
tournament_match = TournamentMatch.find_by(id: tournament_match_id)
if tournament_match.score_cards.attach(params['score_cards'])
flash[:success] = 'Score card uploaded'
else
flash[:warning] = 'Score card upload failed'
end
redirect_to public_results_path
end
end
I want to display flash message bit longer. So how can I display flash message for bit longer? I tried to sleep for 5 seconds in Vue Component in beforeCreate life cycle hook but thats also not working. In Rails I tried flash.keep but that also didn't work. How can I sleep for 5 seconds in Vue.js before rendering the template ?
Update:
My view for flash message looks like below
<div class='flash'>
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>">
<%= value %>
</div>
<% end %>
</div>
So I fixed the problem by making div position: fixed. My final css looks like this
.flash {
position: fixed;
z-index: 10;
}
Vue js was taking all space in the page thats why flash message was invisible, so setting position to fixed makes div remain fixed at the top of page and I can see the flash message.

Capybara failed to find position for element

I'm using Capybara with a webkit driver and when I'm running tests with js: true it raises error listed below. When I do same things in other tests without js: true everything works fine.
PS. There is no need for js :true in this test. This code is actually inside a helper but I put it here like test , so it will be easier to understand.I'm using js: true in the other test that invokes this helper method.
Code below raises Capybara::Webkit::ClickFailed:
Failed to find position for element /html/body/div/div/div/div[2]/a[12]
scenario "adding logged days", js: true do
visit '/logged_days'
find(:xpath, "//a[contains(.,'12')]").click
# click_link("12") raises same error
expect(current_path).to eq("/logged_days/new")
fill_in "Опис виконаної роботи", with: "Some description"
fill_in "Кількість відпрацьованих годин", with: 40
click_button "Додати"
expect(current_path).to eq("/logged_days")
expect(page).to have_content("40")
end
/logged_days:
<div class="page-header">
<h2>Logged Days <small>March</small></h2>
</div>
<div class="conteiner-fluid logged_days_container">
<% for i in 1..31 %>
<%= link_to new_logged_day_path(:cal_date => "#{i}"), method: :get do %>
<div class="calendar_cell">
<p class="cell_date"><%= i %></p>
<p class= "cell_text"></p>
</div>
<% end %>
<% end %>
</div>
I think I had this error exactly once in all my years and it was about an element being covered or otherwise not being clickable.
A thorough investigation with your browser's webinspector is necessary here: You have to figure what your js does to that click target.
When using js:true your page not only runs JS but also has CSS processed. This means you can end up with elements that are are non-visible, overlapped, or moving. You need to look at what is done to the element in a real browser and make sure the element is actually clickable, or what other actions a user would have to do first to make it clickable.
Secondly, don't use .eq with current_path - it'll lead to flaky tests as you use js capable drivers. Instead use the has_current_path matcher
expect(page).to have_current_path('/logged_days/new')

Capybara choose("radio button") not working

A snapshot of my view:
<%= form_for #request do |f| %>
<div class="form-group">
<%= f.radio_button(:item, "Snow/waterproof shell (upper)") %>
<%= f.label(:item, "Snow/waterproof shell (upper)") %>
</br>
<%= f.radio_button(:item, "Headlamp") %>
<%= f.label(:item, "Headlamp") %>
</div>
Yet on my Rspec integration test file (spec/requests/requests_spec.rb), when I write (note the choose radio button is a part of the form where the user requests an item from a list, and the test is for the resulting page after submission, which should indicate the item that the user requested). I'm using gem 'rspec-rails', '2.13.1'
describe "Requests" do
subject { page }
describe "new request" do
before { visit root_path }
describe "with valid information" do
before do
choose("Snow/waterproof shell (upper)")
click_button submit
end
it { should have_content("Snow/waterproof shell (upper)")
end
end
end
I always get the error:
←[31mFailure/Error:←[0m ←[31mchoose("Snow/waterproof shell (upper)")←[0m
←[31mCapybara::ElementNotFound←[0m:
←[31mUnable to find radio button "Snow/waterproof shell (upper)"←[0m
←[36m # ./spec/requests/requests_spec.rb:24:in `block (4 levels) in <top (required)>'←[0m
Same if I try with choose("Headlamp") or any other option. Any thoughts smart people? This seemed like something that would be so easy...
I've had this issue a number of times. If you choose form elements based on their ID in the dom it's far more reliable:
before do
choose 'request_item_headlamp'
click_button submit
end
I can't tell without looking what ID rails would come up with for the other radio button. Just right click it in chrome, inspect element, and cut and paste the element ID into your test.
I suspect sometimes when choose doesn't reliably check a radio button, it may be because an animation is in progress.
If you suspect this is causing your choose calls to be unreliable, try disabling animations, say by setting their execution time to 0 seconds in your test environment. Alternatively build in a wait period in your test for the animation to finish.

Disabling Flash message without disabling cache on click on back button In RAILS

I have an application written in rails. I have flash messages displaying the user saying he logged in successfully or created or updated his profile. Once the user logs in a flash message("Welcome") is displayed and the user navigates to a different page. If the user clicks BACK button the flash message ("Welcome") is displayed again.
I dont want to disable Cache as it will affect the performance.
Any ideas to tackle this situation will be helpfull.
Thanks.
This is for people coming to this question via search engines.
Problem
When the user clicks on the back button, a fresh request is not made to the server, rather the browser's cached copy is displayed. However, during the first request, the browser has already cached the flash message, and on clicking the back button, the flash message gets displayed again.
Solution
A way to get around this is to use the browser's localStorage or sessionStorage.
Change your code so that the flash messages are shown or hidden via javascript. When the flash message is shown, store that fact in the browser's sessionStorage.
Next time, check if the flash message has already been shown and if so, do not show it again.
I prefer to use sessionStorage instead of localStorage as sessionStorage gets cleaned up on closing the browser. It is helpful if you have too many flash messages. Otherwise, you will have to write the logic for removing older entries
# app/views/layouts/application.html.erb - or wherever your flash messages reside
...
<%
flash_name_mappings = {
"notice" => "success",
"error" => "danger",
"alert" => "warning"
}
%>
<% flash.each do |name, msg| %>
<% if msg.is_a?(String) %>
<% flash_token = Time.current.to_s(:number) # or use any other random token generator, like Devise.friendly_token %>
<div id="flash-<%= flash_token %>" style="display:none" class="alert alert-<%= flash_name_mappings[name] || name %>">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<%= content_tag :div, msg.html_safe, id: "flash_#{name}" %>
</div>
<script type="text/javascript">
$(document).ready(function(){
if (typeof(Storage) !== "undefined") {
// Code for localStorage/sessionStorage.
if(sessionStorage["flash-<%= flash_token %>"] != "shown"){
$("#flash-<%= flash_token %>").show();
sessionStorage["flash-<%= flash_token %>"] = "shown";
}
} else {
// Sorry! No Web Storage support..
$("#flash-<%= flash_token %>").show();
}
}
</script>
<% end %>
<% end %>
...
Try to use Flash.now instead:
flash.now[:notice] = "Welcome"
In comparison to flash method it displays the message only until the next action is performed. Normal flash message is displayed until the next request is send to the server. I hope it helps.

How to test what image is being displayed using rspec?

Depending on what page is being viewed I want to use a different image for my logo; the logo on the homepage is bigger. I like using request specs to test behaviour, so I would like to do something like this:
describe 'Visit "advertentie/1"' do
it 'contains add details' do
add = create(:add_with_photos)
visit add_path add
page.should have_selector( 'img[alt="logo-small"]' ) # CHECK IMAGE ALT
page.should have_content( add.name )
end
end
and the test runs agains some haml generated html:
<div class='logo-wrapper'>
<h1>
<a href="/"><img alt="Logo-big" src="/assets/logo-small.png" />
<br>
<span>UpMarket</span>
</a>
</h1>
</div>
however this selector doesn't work. Is this possible, and how?
Did you try the have_css method?
have_css("img[src*='w3schools']")
(Selects every <img> element whose src attribute value contains the substring "w3schools")

Resources