Rails 5 Capybara not founding already clicked button - capybara

I have the following test:
it 'shows the current quantity of items inside cart' do
item = create(:item)
visit root_path
click_link("add-item-#{item.id}")
wait_for_ajax
page.find('#notice-modal-ok').click
click_link("add-item-#{item.id}")
wait_for_ajax
page.find('#notice-modal-ok').click
expect(page).to have_selector('#cart-item-count', text: '2')
end
It's basically a button with ajax thats show a success modal when return status 200. But, when the second ajax happens, for reason that I can't understand, the modal doesn't shows up. I can do this normally in dev environment.
Thanks for help!
Edit #1
I added a save_and_open_screenshot to try to debug this. The code in the end looked this way:
it 'shows the current quantity of items inside cart' do
item = create(:item)
visit root_path
click_link("add-item-#{item.id}")
page.find('#notice-modal-ok', wait: 10).click
expect(page).not_to have_selector('#notice-modal-ok')
click_link("add-item-#{item.id}")
save_and_open_screenshot # image
page.find('#notice-modal-ok', wait: 10).click # this fails
expect(page).to have_selector('#cart-item-count', text: '2', wait: 10)
end
Imagem number 1
As the images shows, the modal isn't appering on the second call. Here is the javascript thats shows it:
$(document).ready(function() {
$("a:regex(id,add-item-[0-9]+)").click(function(event) {
event.preventDefault();
var link = $(this).attr("href");
$.ajax({
url: link,
method: "GET",
dataType: "json",
success: function(data) {
$('#notice-modal').modal('show');
if(data.quantity) {
$("#cart-item-count").css('display', 'block');
$("#cart-item-count").html(data.quantity);
} else {
$("#cart-item-count").hide()
}
}
});
})
});
And in development mode, it works normally. Hope this new infos help!

Firstly, if the element with id of 'notice-modal-ok' is only appearing after the ajax request has completed then wait_for_ajax isn't required. Beyond that, assuming that clicking the modal doesn't affect the behavior of the add item link there are a few possibilities. One is that the modal hasn't actually disappeared before the link click happens and that suppresses link behavior. Another would be that `Capybara.default_max_wait_time' isn't set high enough for the hardware you're running on. To test for these you can wait for the modal to be gone before clicking the link for the second time and also temporarily increase the max wait time
Update: the root cause of the issue is that semantic-ui thinks the modal is still active (even though it has been closed) if you click the 'ok' button to close it before it has fully animated into place. The way around this in the tests is to make sure the modal has class 'active' before clicking the ok button. An alternate solution would be to disable all animations in semantic-ui when testing (if it has that option) which would also speed up tests.
it 'shows the current quantity of items inside cart' do
item = create(:item)
visit root_path
click_link("add-item-#{item.id}")
page.find('#notice-modal.active #notice-modal-ok').click
expect(page).not_to have_selector('#notice-modal') #verify/wait for the modal to disappear
click_link("add-item-#{item.id}")
page.find('#notice-modal.active #notice-modal-ok').click
expect(page).to have_selector('#cart-item-count', text: '2')
end

Related

Rails redirect flash[:notice] persisting when not supposed to

I have a button with a redirect_to that runs my controller action, which creates a new row for my model. The first time, it creates it correctly and redirects. After my redirect, I go to the previous page through the menu and repeat the same action. After I click on the button, it redirects to the correct page (which it shouldn't yet... caching?), and then my previous flash message displays. So it's duplicated. I put a debugger statement in to see where it happens on the second run through - it actually happens before my button action executes. After running the rest of the code, it redirects (but since it redirected prematurely, redirects to same page) correctly with the corresponding (second) flash. How do I get rid of the extra initial flash message?
Here's a picture showing what I mean:
If you look at the blue loading bar right underneath the URL, it shows that the page hasn't loaded yet (I stopped it with debugger statement as shown below). However, the redirect and flash has already happened, which it isn't supposed to, since the intended redirect and flash will happen after turbolinks finishes the page load.
Initial link:
<%= link_to create_wager_from_favorite_wager_path(favorite_wager), data: { confirm: 'Create this wager?' } do %>
Create Wager
<% end %>
Controller action:
def create_wager_from
debugger
# on second run through, the redirect and flash happens before I reach this point
#favorite_wager = FavoriteWager.find(params[:id])
#anchor = params[:anchor]
set_member_statuses()
result_message = #favorite_wager.create_wager_from_favorite(current_user)
respond_to do |format|
format.html { redirect_to my_wagers_path(:game => #favorite_wager.game), notice: "Wager created successfully!" }
end
end
At this point, it follows the standard path and I'm 99% sure the rest of the code is irrelevant.
I've tried checking the flash params at the point that the page with the button action loads, but it's empty. So I'm not sure what's causing this issue. Any insight appreciated.
Update: changing to flash.now[:notice] makes the duplicates stop, but the flash only displays on the first click of the button. Then it doesn't appear anytime after. And refreshing the page will allow the error to be repeated.
To prevent elements from being visible in cached pages (including previews), remove the element on turbolinks:before-cache. For example you could include something like this in your main application JavaScript file:
addEventListener('turbolinks:before-cache', () => {
document.querySelectorAll('.flash').forEach(element => element.remove())
})
For more on this checkout https://github.com/turbolinks/turbolinks#understanding-caching
After reading up on Turbolinks, I'd determined that the cause of this issue is the natural built-in functionality in turbolinks called a "page preview". This is where it will display the previous cached page as a sort of "preview" before the server response arrives, which gives the illusion that the page already loaded.
However, the cached redirect page in my case was cached the moment it was served up, meaning the flash message was also captured into that cache. So on the second time I clicked the create button, it would load the cached page with the flash, then redirect for real and flash again (like it's suppoed to).
So the solution here is to either a. disable all page previews or b. disable turbolinks for that specific link. I chose b. because it won't affect the rest of my program, at the expense that the blue loading motion is not longer there. Here is the solution below (very simple):
Before:
<%= link_to create_wager_from_favorite_wager_path(fw, :anchor => anchor), data: { confirm: 'Create this wager?' }, class: "red-btn create-favorite-wager-btn" do %>
Create Wager
<% end %>
After:
<%= link_to create_wager_from_favorite_wager_path(fw, :anchor => anchor), data: { confirm: 'Create this wager?' }, "data-turbolinks": "false", class: "red-btn create-favorite-wager-btn" do %>
Create Wager
<% end %>

Cucumber test can't render full page, only header

I'm fairly new to Cucumber/BDD and I'm kind of stumped. I'm trying to do a simple test of page contents but it seems that my test suite can only "find" the page header.
For example:
Scenario: User sees the menu page
Given a menu exists
When I visit the menu
Then I should see "menu" page title
And I should see "tuna poke" item name
Step definitions:
Given("a menu exists") do
user = User.create!(email: "test#valid.email", password: "hello")
restaurant = Restaurant.create!(name: "eleven tables", user_id: user.id)
menu = Menu.create!(restaurant_id: restaurant.id)
category = Category.create!(name: "appetizer")
menu_item = MenuItem.create!(name: "tuna poke", description: "it's light", price: 9, category: category, menu: menu)
end
When("I visit the menu") do
visit restaurant_menu_path
end
Then("I should see {string} page title") do |string|
expect(page).to have_title "#{string}"
end
And("I should see {string} item name") do |item_name|
expect(page).to have_content "#{item_name}"
end
Gives me the result:
Feature: View Menu
In order see the menu
I want to view the menu
I want to see item name
I want to see item description
I want to see item price
Scenario: User sees the menu page # features/view_menu.feature:8
Given a menu exists # features/step_definitions/view_menu_steps.rb:1
When I visit the menu # features/step_definitions/view_menu_steps.rb:9
Then I should see "menu" page title # features/step_definitions/view_menu_steps.rb:13
And I should see "tuna poke" item name # features/step_definitions/view_menu_steps.rb:17
expected to find text "tuna poke" in "menu sign in" (RSpec::Expectations::ExpectationNotMetError)
./features/step_definitions/view_menu_steps.rb:18:in `"I should see {string} item name"'
features/view_menu.feature:12:in `And I should see "tuna poke" item name'
Failing Scenarios:
cucumber features/view_menu.feature:8 # Scenario: User sees the menu page
1 scenario (1 failed)
4 steps (1 failed, 3 passed)
0m1.180s
The thing I find most peculiar is this part of the error:
expected to find text "tuna poke" in "menu sign in"
Which doesn't make a lot of sense to me. The only time those three words appear together is when you're on the /menu page of my application as a logged-out user in the nav bar. Which is precisely the case here. But I can't figure out why it can't read the contents of the entire page.
The <html> element has permitted content of one <head> element followed by one <body> element. In your case you have a <header> element outside the <body> element. This is causing whatever driver you're using (I assume rack_test since it is least lenient) to generate an implied <body> element around the <header> element so you end up with something like
<html>
<head>...</head>
<body>
<header>...</header>
<body>
<body> You're actual page content </body>
</html>
Since only one <body> element is allowed, the second is then being ignored. Fix your HTML to be valid (move the <header> inside the main <body>) and it should fix your issue

Toggling a Bootstrap collapsible is failing in Rails/Capybara feature tests

I'm new to Capybara and feature testing. I've been trying test a minor feature on a Rails app that toggles comments on a post into and out of view. The first test for toggling the comments into view passes, but the second test for toggling them out of view doesn't. (I am using the headless-chrome webdriver).
context 'viewing comments', js: true do
scenario 'toggling comments into view' do
#post.comments.create(body: 'This is a comment.', user_id: #commenter.id)
visit authenticated_root_path
click_button 'Toggle comments'
expect(page).to have_content('This is a comment')
end
scenario 'toggling comments out of view' do
#post.comments.create(body: 'This is a comment.', user_id: #commenter.id)
visit authenticated_root_path
click_button 'Toggle comments'
expect(page).to have_content('This is a comment')
click_button 'Toggle comments'
expect(page).to_not have_content('This is a comment')
end
end
Initially, I had click_button 'Toggle comments' twice, back-to-back. Neither iteration of the test work. I also tried using sleep n in between the two actions, but to no avail.
Failures:
1) Comment management viewing comments toggling comments out of view
Failure/Error: expect(page).to_not have_content('This is a comment')
expected not to find text "This is a comment" in "OdinFB PROFILE REQUESTS 0 LOG OUT The Feed Create post Luna Lovegood said... Body of post 0 Likes 1 Comments Like Comment Share Toggle comments This is a comment. Morfin Gaunt on Sep 18 2017 at 4:22PM"
The button itself works when the app is fired up locally. It appears to become inactive once activated the first time around in testing.
Any insight would be appreciated, and thanks for reading.
What's happening here is the second button click is occurring after the expected text becomes visible on the page but before the animation has completed. The bootstrap collapse code then gets confused and doesn't collapse the comments since it considers them not fully opened yet. A sleep for a second or so immediately before the second click_button will fix this since it delays long enough for the animation to complete. The other option (and better from a test time perspective) is to disable animations in test mode.

Capybara and remote links

I have this link in records_path page:
link_to "CLICK HERE", edit_record_path(record), remote: true
The controller:
def edit
puts request.format # Just to show you part of the issue
end
And when clicked, it executes the edit.js.coffee and renders a modal for editing the record.
It works great. The problem I have is with Capybara.
context 'when on records list page' do
before { visit records_path }
context 'when clicking on "CLICK HERE"', js: true do
before { click_link('CLICK HERE') }
it 'shows the record name' do
expect(page).to have_content record.name
end
end
end
When I run the test, it raise an error:
Capybara::ElementNotFound: Unable to find link "CLICK HERE"
But if I remove the js: true from the context, it works (It executes the edit method on the RecordsController) but the printed format is html instead of js. I don't even have an edit.html, I just have the edit.js.coffee and that is the one that should be rendered in the test.
What am I doing wrong? If the link has the remote: true prop, and the test has the js: true shouldn't be enough for make it work?
In your setup you don't actually create a record. I am assuming this is the cause of your problem. The edit_record link will not show up if you do not have records to edit. I could be wrong but from what you pasted I think this could be the cause.

How do I simulate an "Edit" or "Destroy" button click in Rails 3.2 w/ MiniTest?

How can I write an integration test for editing an item? My "create" test looks like this:
it "lets a user create a product" do
login_user
click_link("Products")
click_link("New")
fill_in "Identifier", :with => "MyString"
click_button "Create"
assert page.has_content?("Product was successfully created")
end
And that works great. What I am confused about is how to do the Edit and Destroy tests. My index page provides a list of all products. So first I use a factory to create a couple of products. Now I am in the situation where there are multiple "Edit" and "Destroy" buttons. I can't just say:
click_button "Destroy"
because there are two of them. How do I tell it which one to click?
And if I do get the correct "Destroy" button clicked, how do I hit the "OK" button in the Javascript window that pops up?
Assuming that you're using Webrat, you can use the "within" selector.
The Webrat "within" method takes a CSS selector as an argument. Supposing your "Destroy" button is in a div with an id like "#product-2", you can isolate that button with:
within "#product-2" do |scope|
scope.click_button "Destroy"
end
If you need to / would rather use XPath, you can do something like:
response.should have_xpath(xpath) do |button|
click_button(button)
end
Alternatively, if you are using Capybara, then you can use the "find" method:
find("#product-2").find("button").click
find(:xpath, "//div/div/button").click

Resources