Link doesn't hide after some time capybara - ruby-on-rails

I use capybara to test this code which located in my Comment model (5 minutes are conventional):
def editable?
self.created_at < (Time.now - 5.minute)
end
The view of link:
- unless comment.editable?
= link_to 'Edit', edit_category_theme_comment_path(#category, #theme, comment)
So after 5 minutes link to edit must hide from the page (but we need to refresh the page). Here is my code in RSpec for creating comments and testing the link-hide functionality:
def create_comment(options={})
options[:content] ||= 'I am a comment'
visit category_theme_path(category, theme)
within '.comment-form' do
fill_in 'Content', with: options[:content]
click_button 'Submit'
end
end
context 'Comment has content' do
before(:each) { create_comment }
it 'hides the edit symbol due 5 minutes after comment was created' do
using_wait_time 400 do
visit category_theme_path(category, theme)
save_and_open_page
expect(page).to have_no_css('.comment-edit')
end
end
end
But I got: Failure/Error: expect(page).to have_no_css('.comment-edit')expected #has_no_css?(".comment-edit") to return true, got false
I also try to use page.reload!, expect(page).to have_no_css('.comment-edit', wait: 400) and other related staff, but capybara don't want to wait. Maybe I use using_wait_time for a wrong place, if that - how I can test this?

There are a number of things wrong with your approach to testing this. The reason your attempt is failing is because using_wait_time just sets the amount of time Capybaras matchers will wait for their expectations to become true, it doesn't actually make the program wait. Therefore expect(page).to have_no_css('.comment-edit') will wait up to the time your using_wait_time specifies, rechecking the page every 50 milliseconds or so for the content, but it never reloads the page and doesn't wait before the loading the page. For your approach to work you would need to sleep before visiting the page
it 'hides the edit symbol due 5 minutes after comment was created' do
sleep 5.minutes
visit category_theme_path(category, theme)
save_and_open_page
expect(page).to have_no_css('.comment-edit')
end
however this is a terrible idea since your tests will become ridiculously slow.
Rather than that approach you could be generating your test comment already "old" (as pascal betz suggests), using something like FactoryGirl and specifying a created_at that is more than 5 minutes ago.
FactoryGirl.create(:comment, created_at: 5.minutes.ago)
or if you want to continue creating your comment through the interface then include something like the Timecop gem and you can do
Timecop.travel(5.minutes.from_now) do
visit category_theme_path(category, theme)
save_and_open_page
expect(page).to have_no_css('.comment-edit')
end
which will move the clock 5 minutes forward before visiting the page, and then reset it back to normal once the block if finished.
Additionally, your editable? method is comparing in the wrong direction and should be
def editable?
self.created_at > (Time.now - 5.minute)
end
Then the view should be
- if comment.editable?
= link_to 'Edit', edit_category_theme_comment_path(#category, #theme, comment)

Work with seed data that is 5 minutes old and visit that page. Like this you do not need to wait five minutes.
Also your code
- unless comment.editable?
= link_to 'Edit', edit_category_theme_comment_path(#category, #theme, comment)
should probably read
- if comment.editable?
= link_to 'Edit', edit_category_theme_comment_path(#category, #theme, comment)

Related

Capybara testing with RSpec in Ruby

on my index page I have this div:
<div class="banner">
<h1 class="glow-header">Galaxy Far, Far Away? Quick Trip to Mars?<br>
Pianeta has you covered.</h1>
<div>
In my testfile this works:
RSpec.describe 'home features' do
it 'displays the name of the app and links to the index-all planets page' do
visit root_path
expect(page).to have_content('Space is full of surprises.')
click_link('Go Beyond')
expect(current_path).to eq('/planets')
expect(page).to have_content('Galaxy Far, Far Away?')
end
end
But I would like it to be working with the h1 included.
I did this:
expect(page).to have_content('<h1 class="glow-header">Galaxy Far, Far Away? Quick Trip to Mars?<br>
Pianeta has you covered.</h1>')
end
But the test failed. What did I do wrong ?
The #has_content?/#has_text? method only checks the text content of the page. It does not look at the HTML tags.
If you want to check for content within a specific HTML element there is a #within method that takes a block and will scope the Capybara lookups within it to be within the matched element. The element referenced by #within must exist or Capybara will raise an exception.
page.within('h1.glow-header') do
expect(page).to have_content('Galaxy Far, Far Away?')
end
If you don't want to deal with scoping using within for a single expectation you could do
expect(page).to have_css('h1.glow-header', text: 'Galaxy Far, Far Away?')
If you've already got a reference to the header you could also do something like
header = find('h1.glow-header')
...
expect(header).to have_text('Galaxy Far, Far Away?')
Additionally you should not be doing expect(current_path).to eq('/planets'). Using RSpecs eq matcher with Capybara will lead to flaky tests as soon as you move to using an asynchronous (JS supporting) driver, because it prevents Capybaras auto waiting/retrying behaviors. Instead you should use the Capybara provided matcher
expect(page).to have_current_path('/planets')

doing a rails test:system that always returns no error even if I purposely made it an errof

Example 1:
click_on 'Add Cart'
assert_no_selector "#empty-cart" #note #empty-cart should be dynamically created since we added an item
accept_confirm do
click_on 'Empty Cart'
end
assert_selector #"empty-cart" #note: empty-cart should no longer be present since it was dynamically removed
but somehow I run the test and everything is fine.
Example 2
click_on 'Add Cart'
visit my_url
assert_no_selector "#empty-cart"
accept_confirm do
click_on 'Empty Cart'
end
visit my_url
assert_selector #"empty-cart"
By forcing a reload, the test script somehow is able to detect that the assertion is wrong.
Note: I'm assuming you meant the #s to be inside the quotes in the two examples you give - otherwise they're just creating comments
The concept you're missing is that all actions in the browser occur asynchronously. This means that when accept_confirm returns, the confirm modal has been clicked but any actions that triggers are not guaranteed to have occurred yet. Therefore when assert_selector "#empty-cart" is run it is highly likely that element is still on the page and the assertion will immediately pass.
For your second example you have a similar issue. Clicking on 'Add Cart' and then immediately calling visit is likely to either abort the visit call or have the visit call processed in parallel and not actually see the results of 'Add Cart'. You need to add an assertion to confirm that the 'Add Cart' actions have completed before moving on. Something like
click_on 'Add Cart'
assert_no_selector '#empty-cart' # or maybe assert_text 'Item added to cart!' etc.
visit my_url

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 matcher for presence of button or link

Users on web page don't distinguish between "button" and "link styled as button".
Is there a way to add check whether a "button or link" is present on page?
For example Capybara has step:
page.should have_button('Click me')
which does not find links styled as buttons.
Updated answer (should matcher is deprecated in RSpec 3.0+):
expect(page).to have_selector(:link_or_button, 'Click me')
Before:
page.should have_selector(:link_or_button, 'Click me')
Followed from click_link_or_button which is defined here: https://github.com/jnicklas/capybara/blob/master/lib/capybara/node/actions.rb#L12
def click_link_or_button(locator)
find(:link_or_button, locator).click
end
alias_method :click_on, :click_link_or_button
It calls a selector :link_or_button. This selector is defined here: https://github.com/jnicklas/capybara/blob/master/lib/capybara/selector.rb#L143
Capybara.add_selector(:link_or_button) do
label "link or button"
xpath { |locator| XPath::HTML.link_or_button(locator) }
end
It calls this method: http://rdoc.info/github/jnicklas/xpath/XPath/HTML#link_or_button-instance_method
# File 'lib/xpath/html.rb', line 33
def link_or_button(locator)
link(locator) + button(locator)
end
So i tried to check the presence of the selector and it worked:
page.should have_selector(:link_or_button, 'Click me')
Using the expect syntax
expect(page).to have_selector(:link_or_button, 'Click me')
This works without needing to define a custom matcher.
You can also use a custom matcher
RSpec::Matchers::define :have_link_or_button do |text|
match do |page|
Capybara.string(page.body).has_selector?(:link_or_button, text: text)
end
end
Then do
expect(page).to have_link_or_button('Login')
Personally I would give your button or link an id and look for that using
page.should have_css('#foo')
This way you can refer to the link or button without worrying about its implementation.
I always find this useful: https://gist.github.com/428105
I think you can use the find button instance method:
(Capybara::Element) find_button(locator)
Using id, name, value.
Or if you want a link
(Capybara::Element) find_link(locator)
From: http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Finders#find_button-instance_method
I had an odd case where some smoke tests marched across various customer-centric login pages that had slight variations on doing the login submit button... Driven by a Cucumber table of user, org, etc.
# A bit of a hack, org_name is normally a subdomain, but sometimes it is the complete domain
def login(user, org_name)
# Use the below to automatically hit each user's org's server
if org_name.include? '.com'
Capybara.app_host = "http://#{org_name}"
else
Capybara.app_host = "http://#{org_name}.mydomain.com"
end
visit '/'
fill_in 'username', :with => user
fill_in 'userpwd', :with => '***'
begin
page.find(:link_or_button, 'submit')
click_on 'submit'
rescue Capybara::ElementNotFound
page.find(:link_or_button, 'Log In')
click_on 'Log In'
rescue Capybara::ElementNotFound
pending "Need to determine how to invoke the Login button for #{org_name} near Line ##{__LINE__} of #{__method__} in #{__FILE__} "
end
# -----------------------
# Work-around for modal popup saying SSL is mismatched if you are using actual production URLs
# The rescue is for cases that do not exhibit the modal pop-up
page.driver.browser.switch_to.alert.accept rescue Selenium::WebDriver::Error::NoAlertPresentError
# Ensure that login was successful
page.should_not have_content 'Login failed'
end
if html:
<a class="top-menu__item">text123
<span class="label">
<span class="label">86</span>
</span>
</a>
not work:
assert page.has_selector?(:link_or_button, text: 'text123')
assert page.should have_selector(:link_or_button, text: 'text123')

Cucumber: Wait for ajax:success

I have the following typical cucumber steps in a Rails 3.1 project:
...
When I follow "Remove from cart"
Then I should see "Test Product removed from cart"
The difficulty is that "Remove from cart" button is an ajax :remote call, which returns "Test Product removed from cart" to the #cart_notice element via:
$('#cart_notice').append("<%= #product.name %> removed from cart");
The function works fine in the browser, but doesn't find the "Test Product removed from cart" text in cucumber. I'm guessing this is because Cucumber is searching for the text before the AJAX returns it?
So, in short...how do I ensure cucumber waits for the ajax to return a result before searching for the desired content?
To add to what dexter said, you may want to write a step that executes JS in the browser which waits for ajax requests to finish. With jQuery, I use this step:
When /^I wait for the ajax request to finish$/ do
start_time = Time.now
page.evaluate_script('jQuery.isReady&&jQuery.active==0').class.should_not eql(String) until page.evaluate_script('jQuery.isReady&&jQuery.active==0') or (start_time + 5.seconds) < Time.now do
sleep 1
end
end
You can then include the step as needed, or after every javascript step:
AfterStep('#javascript') do
begin
When 'I wait for the ajax request to finish'
rescue
end
end
I was having issues with the automatic synchronization, and this cleared it up.
I guess you are using cucumber with capybara. In that case, capybara comes with a resynchronize feature. "Capybara can block and wait for Ajax requests to finish after you’ve interacted with the page." - from capybara documentation
You can enable it in features/support/env.rb
Capybara.register_driver :selenium do |app|
Capybara::Driver::Selenium.new(app, :browser => browser.to_sym, :resynchronize => true)
end
But, I have seen this causing timeout issues. So, if that isn't working for you, I would recommend introducing a manual wait step before asserting the results of the ajax request.
...
When I follow "Remove from cart"
And I wait for 5 seconds
Then I should see "Test Product removed from cart"
You can define the wait step in step_definitions/web_steps.rb as
When /^I wait for (\d+) seconds?$/ do |secs|
sleep secs.to_i
end
I suppose wait_until should do the job. It will command to capybara to check something until its true for some time.
Old question, but the Spreewald gem should help
https://github.com/makandra/spreewald
You can use the patiently method from the Spreewald gem like so:
Then /^I should see "([^\"]*)" in the HTML$/ do |text|
patiently do
page.body.should include(text)
end
end
The step will maintain a loop for a period of time until the desired text appears in the test dom, else the step will fail.
(taken from https://makandracards.com/makandra/12139-waiting-for-page-loads-and-ajax-requests-to-finish-with-capybara)

Resources