Is there any difference between visit 'route' and visit '/route' in Capybara? - ruby

The following test would sometimes fail:
it 'can create a new item' do
visit 'items/new'
within 'form#item-form' do
fill_in 'Name', with: 'Item'
click_button 'Create'
end
current_path.must_equal('/items')
assert page.has_content?('Item')
end
I put a puts page.html before the within block and found out that sometimes the page would be a clear 'Not found' page. I'm using Capybara's default web driver Rack::Test.
Is there any difference between visit 'route' and visit '/route' in Capybara?

Yes there can be a difference in a number of cases depending on things like whether you've specified Capybara.app_host, already visited a url in the current test, etc. Basically if you want to go to /items/new specify /items/new
You can see the relevant code when using rack-test here and here . The other drivers all have similar behavior, so only use relative paths if you really understand what you're doing and need relative paths
On a secondary note, you should get away from doing direct assertions on current_path. It will work fine while you're using rack-test since all clicks on submits, links, etc are synchronous - but if/when you move to using a JS capable driver those actions are no longer guaranteed synchronous so you'll end up comparing current_path before it's actually changed. You should get used to using something along the lines of
assert page.has_current_path?('/items')
since that will use Capybaras waiting behavior while confirming the current path

Related

Creating a custom view

I am trying to create a landing page for an event for people to visit to see the events details. I have created the view, added a route to the event resources and made changes to the controller but something has been done incorrectly.
Here is my code:
routes.rb:
resources :events do
resources :guests
match '/landing_page', to:'events#landing_page', as: :landing_page, :via =>[:get, :post]
# resources :guestlists
end
event_controller:
def landing_page
#event = Event.find(params[:id])
end
When I open the landing page i get the following error:
"ActiveRecord::RecordNotFound (Couldn't find Event without an ID):"
In case anyone else runs into this, I wanted to document Sergio's last comment here which I believe leads to the outcome I think most people will be looking for. Nesting this inside of a member block should get you an ideal outcome:
resources :events do
member do
get :landing_page
end
end
Running rake routes should now show /events/:id/landing_page(.:format) so you can use the same method you use in your show method that just asks for params[:id].
I was wracking my brain for a while on this as rails resources seem to be dwindling on the interwebs.

Need help reliably finding asynchronous elements in React with Capybara without using sleep everywhere

Newbie engineer question here, but I've searched and can't find a solution.
Environment: Mac OS, Chrome, Capybara with Selenium Chrome-driver, Ruby with Rspec running the test.
Situation:
Testing a React app where the user logs in using a username and password, followed by clicking on a sidebar nav link that loads up....followed by other actions. Capybara continues to fail to find the sidebar nav link to click on. I believe the sidebar nav is its own React component and loads asynchronously
App & Test Behavior:
Sometimes Capybara finds the link element, clicks the link and the test passes. Other times it completely fails to find the element and fails the test.
Solutions I've tried:
Upping the default time for finder methods to continue to search to 15+ seconds(I've never noticed the app take more than 5 seconds on anything)
I only have used finder methods that SHOULD be repeat-searching for the default time for the element to appear (page.find, page.has_css?, etc)before attempting the .click action. What I have found is that after the login happens, and while the nav is loading, the test just seems to fail with element not found. The page.find does NOT seem to continue to search for 15 seconds before failing - instead, the login happens, then a second later I get a fail with element not found.
I have tried passing a wait into the find (example: page.find(some element, wait:15).click . This runs into the same problem as above where it doesn't seem to continue searching for 15 seconds for the element to appear and then click it.
What does seem to work is adding in sleeps before searching for an element (example: login, sleep(5), page.find(something).click).
Unfortunately I'm having this same problem with other elements all over in the app - for example a div may have a bunch of cards in it. When a new card is added it takes 2-3 seconds to show up in the list (probably due to sending out the card info to the database, and refreshing the cards on the page). If I try and page.find immediately after adding a card, the test will almost immediately fail with an element not found message. If I add the card, sleep(3), THEN try page.find, it will find it.
I can't just add sleep all over the place in the app, because its huge and it would slow down the tests immensely. I think I've tried all the typically suggested fixes for asynchronous loading. Any idea what is going on here and what I can do to fix it
editing to add some requested code.
I'm using capybara 3.2.
We are using a bit of a page object style framework so I"ll try and post the actual test with its methods in bold and helper methods nested in it.
Its getting caught in the before action of this particular feature on the final method to click on the sidebar. I'm a little limited on what I can show, but I think this will make sense....
The actual before action:
before do
**app.launch_app(app.url)**
# this instantiates an instance of the framework and helper methods and # goes to the app url
**app.login.full_login(app.username('Some User'), app.password)**
# def full_login(user, pass)
# enter_email(user)
# def enter_email(user)
# return if already_logged_in?
# def already_logged_in?
# page.has_css?("a[href*='/me']", wait: false)
# end
# fill_field('email', user)
# def fill_field(field, text)
# sleep 0.1
# page.find("input[type=#{field}]").send_keys text
# end
# click_button 'Log In'
# def click_button(text)
# page.click_on text
# end
# end
# login_using_second_auth(user, pass)
# def login_using_second_auth(user, pass)
# page.fill_in 'username', with: user
# page.fill_in 'password', with: pass
# click_button 'Sign In'
# end
# end
app.nav.click_sidebar_link('Admin Account', 'Admin')
# def click_sidebar_link(link, section)
# sleep(2)
# page.find('div[class^=app__wrapper] > div > div > div', text:
# section)
# .find('span[class^=nav-item]', text: link).click
# end
end
Sorry that looks so messy, but I think you guys can make sense of it.
The test is flaky so after running it a few times I can't get the exact error, but its usually element not found on the span with the text Admin

Don't run a cucumber feature if a element on the page is not available but exit without failing

I want to check a page for an element before I start running the feature file for it (It's an element that periodically appears with an event so I only want to run the feature if its present).
The approach I wanted to use was a tagged before hook to see if the element was present and if it wasn't just don't run the feature but exit without 'failing' the step just exit with a message. I tried variants on the below but
1. If I don't have a rescue clause it obviously fails the scenario when the element isn't present
2. If I do have the rescue clause it handles it and passes moving onto the features which will then fail as the event isn't available.
Is there a way to halt running the feature file if the rescue clause is invoked without the 'fail'?
Before('#event') do
begin
find('.event').visible?
rescue Capybara::ElementNotFound
puts 'THE EVENT IS NOT ON'
end
end
You shouldn't be using find if you want to make a decision based on existence. Instead you should be using the predicate methods provided by Capybara (has_selector?, has_css?, has_xpath?, etc) so you don't have to rescue exceptions.
The other thing to know is the Cucumber skip_this_sceanrio method, which means you should end up with something like
Before('#event') do
# visit '/some_page' # May not be needed if you have another `Before` already visiting the needed page
skip_this_scenario('Skipping due to missing event') unless page.has_css?('.event')
end

Capybara find_field & has_selector not working

I am using Capybara and getting errors from the finders 'find_field' & 'has_selector'.
I have tried using them like this:
page = visit "http://www.my-url-here.com"
next if page.has_selector?('#divOutStock')
page.find_field('#txtQty').set('9999')
has_selector returns the error: "NoMethodError: undefined method `has_selector?' for {"status"=>"success"}:Hash"
find_field cannot find the field. (It is present on the page and is not a hidden field.)
I have also tried using fill_in to set the field value, that doesn't work either.
How can I get this to work with Capybara?
You have a couple of issues in your code
page is just an alias for Capybara.current_session. If you assign to it you're creating a local variable and it's no longer a session
find_field takes a locator - http://www.rubydoc.info/gems/capybara/Capybara/Node/Finders#find_field-instance_method - which will be matched against the id, name, or label text. It does not take a CSS selector
Your code should be
page.visit "http://www.my-url-here.com"
next if page.has_selector?('#divOutStock')
page.find_field('txtQty').set('9999')
and you could rewrite the last line as
page.fill_in('txtQty', with: '9999')
Also you should note that (if using a JS capable driver) has_selector? will wait up to Capybara.default_max_wait_time for the #divOutStock to appear. If it's not usually going to be there and you want to speed things up a bit you could do something like
page.visit "http://www.my-url-here.com"
page.assert_text('Something always on page once loaded') #ensure/wait until page is loaded
next if page.has_selector?('#divOutStock', wait: 0) # check for existence and don't retry
page.fill_in('txtQty', with: '9999')

Why do I need to add sleep for rspec to pass with selenium 2.48.0?

Recently we upgraded our selenium web driver from 2.47.1 to 2.48.0.
With this upgrade I need to add sleep for a few seconds in rspec to pass. Spec was working properly without sleep with the older version.
sleep(inspection_time=5) // why do I need this?
my_form_page.save_button.click
// some assertion here
Edit
I tried using implicit wait instead of sleep.But it's not working. Is there any specific reason behind it?
Capybara.current_session.driver.browser.manage.timeouts.implicit_wait = 50
Generally speaking, rspec selenium tests are known to be "flakey". Sometimes rspec tries to search for an element before it appears on page due to many reasons (ie: element appears upon ajax response).
Here's a tip that may help you solve this, if you will wrap your capybara finders inside of a within block, your tests will wait until it finds that within selector FIRST before trying to run the code inside of it.
This more-often-than-not will help solve a test running too fast on a page that takes a while to load and your button or selector or whatever isn't actually on the page yet (which is why it fails).
So take a look at these 2 examples and try the within method...
# spec/features/home_page_spec.rb
require "spec_helper"
describe "the home page", type: :feature do
context "form" do
# THIS MIGHT FAIL!!!!
it "submits the form", js: true, driver: :selenium do
visit "/"
find("#submit_button").click
end
# THIS PROBABLY WILL PASS!!!
it "submits the form", js: true, driver: :selenium do
visit "/"
within "form" do
find("#submit_button").click
end
end
end
end

Resources