Keep Session open - Capybara/Cucumber - ruby

I have a feature that requires in excess of 100 Scenarios to be run, the first step being to log into my application. this is normally handled with a Before hook and a After hook which will log you out. This keeps my tests independent of each other and for me is normally a good idea. However in this instance I just want to log into my application, run all my scenarios and log out.
I seem to be having an issue with sessions as after a scenario has finished I am redirected to about:blank and my session is killed.
I have already tried
class Capybara::Selenium::Driver < Capybara::Driver::Base
def reset!
# Use instance variable directly so we avoid starting the browser just to reset the session
if #browser
begin
##browser.manage.delete_all_cookies <= cookie deletion is commented out!
rescue Selenium::WebDriver::Error::UnhandledError => e
# delete_all_cookies fails when we've previously gone
# to about:blank, so we rescue this error and do nothing
# instead.
end
#browser.navigate.to('about:blank')
end
end
end
But then i get the error below in my console
expected not to find xpath "/html/body/*", found ...
So my question is how can i finish a scenario and then just click another link in my site and then conduct the next scenario
Thanks

This is a good example of when it would be a good idea to use the Background feature of Cucumber.
If you get the user to log in inside the background tag, they will be logged in for all subsequent scenarios.

It turns out after reading the Poltergeist Docs I could implement a simple solution
This is what I came up with:
Given(/^I setup the form with the correct template \- benchmarking$/) do
if !defined? $i == true
login_to_app
elsif page.current_url == "about:blank"
options = { domain: 'mydomain',
httponly: true,
name: 'name',
path: '/',
secure: true
}
page.driver.set_cookie("name", $session_cookie, options)
page.visit('url')
end
click_link('mylink')
wait_for_ajax
end
Then('This is my final step')
$i = 1
$session_cookie = page.driver.cookies["PHPSESSID"].value
end
So in the first iteration $i is not defined so I can login, by the second scenario it is defined so this means I can then set the cookie info based on the last session and access the page I require in my application using the same credentials as if I had logged in.

Related

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

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

Why the object references are not correctly passed in this RSpec script?

I have to say I am new both to Ruby and to RSpec. Anyway I completed one RSpec script but after refactoring it failed. Here is the original working version:
describe Site do
browser = Watir::Browser.new :ie
site = Site.new(browser, "http://localhost:8080/site")
it "can navigate to any page at the site" do
site.pages_names.each do |page_name|
site.goto(page_name)
site.actual_page.name.should eq page_name
end
end
browser.close
end
and here is the modified version - I wanted to have reported all the pages which were visited during the test:
describe Site do
browser = Watir::Browser.new :ie
site = Site.new(browser, "http://localhost:8080/site")
site.pages_names.each do |page_name|
it "can navigate to #{page_name}" do
site.goto(page_name)
site.actual_page.name.should eq page_name
end
end
browser.close
end
The problem in the latter case is that site gets evaluated to nil within the code block associated with 'it' method.
But when I did this:
...
s = site
it "can navigate to #{page_name}" do
s.goto(page_name)
s.actual_page.name.should eq page_name
end
...
the nil problem was gone but tests failed with the reason "browser was closed"
Apparently I am missing something very basic Ruby knowledge - because the browser reference is not working correctly in modified script. Where did I go wrong? What refactoring shall be applied to make this work?
Thanks for your help!
It's important to understand that RSpec, like many ruby programs, has two runtime stages:
During the first stage, RSpec loads each of your spec files, and executes each of the describe and context blocks. During this stage, the execution of your code defines your examples, the hooks, etc. But your examples and hooks are NOT executed during this stage.
Once RSpec has finished loading the spec files (and all examples have been defined), it executes them.
So...trimming down your example to a simpler form, here's what you've got:
describe Site do
browser = Watir::Browser.new :ie
it 'does something with the browser' do
# do something with the browser
end
browser.close
end
While visually it looks like the browser instance is instantiated, then used in the example, then closed, here's what's really happening:
The browser instance is instantiated
The example is defined (but not run)
The browser is closed
(Later, after all examples have been defined...) The example is run
As O.Powell's answer shows, you can close the browser in an after(:all) hook to delay the closing until after all examples in this example group have run. That said, I'd question if you really need the browser instance at example definition time. Generally you're best off lazily creating resources (such as the browser instance) when examples need them as they are running, rather than during the example definition phase.
I replicated your code above using fake classes for Site and Watir. It worked perfectly. My only conclusion then is that the issue must lie with either one of the above classes. I noticed the Site instance only had to visit one page in your first working version, but has to visit multiple pages in the non working version. There may be an issue there involving the mutation happening inside the instance.
See if this makes a difference:
describe Site do
uri = "http://localhost:8080/site"
browser = Watir::Browser.new :ie
page_names = Site.new(browser, uri).page_names
before(:each) { #site = Site.new(browser, uri) }
after(:all) { browser.close }
pages_names.each do |page_name|
it "can navigate to #{page_name}" do
#site.goto(page_name)
#site.actual_page.name.should eq page_name
end
end
end

Page objects in watir splash -- how to ensure that the proper page object is instantiated

I am using the Watir Splash framework to test a web application, and I have setup two page classes. The first is the "Login" page which is detailed here:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login_btn
modify button(:id => 'btnLogin'), :click => lambda {redirect_to VehicleSelection}
end
The other page class is the "Vehicle Selection" page. I have used the modify method as shown in the documentation here to ensure that the vehicle selection page object is available for RSpec after a successful login.
But what happens if the login failed? I have some test cases that deliberately feed incorrect information into the login form to ensure that the authentication is working properly. RSpec would need the methods defined in the "Login" class to access the correct elements to complete the test case. In this case, the way that I have specified the method a "VehicleSeleciton" object will be returned regardless. (or so it appears)
Any help is appreciated. Also, I'm open to other suggestions for testing frameworks, especially if there is more example code for me to reference.
Below are a couple of approaches I have tried. I was not using the WatirSplash framework, but the same concepts applied (though the attempted WatirSplash example code might not be 100% accurate).
Solution 1: Do return page objects
My personal preference is to not have page objects returning page objects. Instead, I find it easier to read/work with explicit initializations of each page object within the test. Alister Scott discussed this in his blog.
Your tests would then look like:
#For login successful tests
page = App::Page::Login.new
page.login_btn.click
page = App::Page::VehicleSelection.new #The VehicleSelection page is explicitly initialized
page.validate_page #or whatever you want to do with the page
#For login failed tests
page = App::Page::Login.new
page.login_btn.click
page.validate_page #or whatever you want to do with the page
Solution 2: Create multiple methods for login
Another solution, would be to create two login methods - one for successful login and one for unsuccessful login.
The page object could be:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login(user, password)
#Do whatever code to input name and password and then click the button
#Then redirect to the VehicleSelection page since that is where you will want to go most often
redirect_to VehicleSelection
end
def login_failed(user, password)
login(user, password)
#Return the Login page (instead of the VehicleSelection page).
redirect_to Login
end
end
end
end
With the tests being:
#For login successful tests
login_page = App::Page::Login.new
vehicle_page = login_page.login(user, password)
vehicle_page.validate_page #or whatever you want to do with the Vehicle Selection page
#For login failed tests
login_page = App::Page::Login.new
login_page.login_failed(user, password)
login_page.validate_page #or whatever you want to do with the Login page
Solution 3: Make the button know where it is going
Another solution, would be to have the login button know which page to redirect to.
The page object could be:
module App
module Page
class Login < WatirSplash::Page::Base
url "http://[removed].com"
def login_btn(login_successful=true)
if login_successful
modify button(:id => 'btnLogin'), :click => lambda {redirect_to VehicleSelection}
else
modify button(:id => 'btnLogin'), :click => lambda {redirect_to Login}
end
end
end
end
end
With the tests being:
#For login successful tests
login_page= App::Page::Login.new
vehicle_page = login_page.login_btn.click
vehicle_page.validate_page #or whatever you want to do with the Vehicle Selection page
#For login failed tests
login_page= App::Page::Login.new
login_page.login_btn(false).click
login_page.validate_page #or whatever you want to do with the Login page
Thanks for trying out my gem WatirSplash. I would have written something in the lines of solution #2 - e.g. create two separate methods for successful login and failed login. Using #modify is not needed in either method, like Justin did.
Also, i'd suggest you to use my other gem test-page instead, which is more or less the same, as Page Objects in WatirSplash, but it is extracted into separate gem - WatirSplash will be deprecated in the long term due to all of its parts being exctracted into separate gems giving better control of which functionality is needed in each project.

Scrubyt gives 404 Error when clicking link using _details method

This might be a similar problem to my earlier two questions - see here and here but I'm trying to use the _detail command to automatically click the link so I can scrape the details page for each individual event.
The code I'm using is:
require 'rubygems'
require 'scrubyt'
nuffield_data = Scrubyt::Extractor.define do
fetch 'http://www.nuffieldtheatre.co.uk/cn/events/event_listings.php'
event do
title 'The Coast of Mayo'
link_url
event_detail do
dates "1-4 October"
times "7:30pm"
end
end
next_page "Next Page", :limit => 20
end
nuffield_data.to_xml.write($stdout,1)
Is there any way to print out the URL that using the event_detail is trying to access? The error doesn't seem to give me the URL that gave the 404.
Update: I think the link may be a relative link - could this be causing problems? Any ideas how to deal with that?
I had the same issue with relative links and fixed it like this... you have to set the :resolve param to the correct base url
event do
title 'The Coast of Mayo'
link_url
event_detail :resolve => 'http://www.nuffieldtheatre.co.uk/cn/events' do
dates "1-4 October"
times "7:30pm"
end
end
sudo gem install ruby-debug
This will give you access to a nice ruby debugger, start the debugger by altering your script:
require 'rubygems'
require 'ruby-debug'
Debugger.start
Debugger.settings[:autoeval] = true if Debugger.respond_to?(:settings)
require 'scrubyt'
nuffield_data = Scrubyt::Extractor.define do
fetch 'http://www.nuffieldtheatre.co.uk/cn/events/event_listings.php'
event do
title 'The Coast of Mayo'
link_url
event_detail do
dates "1-4 October"
times "7:30pm"
end
end
next_page "Next Page", :limit => 2
end
nuffield_data.to_xml.write($stdout,1)
Then find out where scrubyt is throwing an exception - in this case:
/Library/Ruby/Gems/1.8/gems/scrubyt-0.3.4/lib/scrubyt/core/navigation/fetch_action.rb:52:in `fetch'
Find the scrubyt gem on your system, and add a rescue clause to the method in question so that the end of the method looks like this:
if ##current_doc_protocol == 'file'
##hpricot_doc = Hpricot(PreFilterDocument.br_to_newline(open(##current_doc_url).read))
else
##hpricot_doc = Hpricot(PreFilterDocument.br_to_newline(##mechanize_doc.body))
store_host_name(self.get_current_doc_url) # in case we're on a new host
end
rescue
debugger
self # the self is here because debugger doesn't like being at the end of a method
end
Now run the script again and you should be dropped into a debugger when the exception is raised. Just try typing this a the debug prompt to see what the offending URL is:
##current_doc_url
You can also add a debugger statement anywhere in that method if you want to check what is going on - for example you may want to add one between line 51 and 52 of this method to check how the url that is being called changes and why.
This is basically how I figured out the answer to your previous questions.
Good luck.
Sorry I have no idea why this would be nil - every time I have run this it returns a url - the method self.fetch requires a URL which you should be able to access as the local variable doc_url. If this returns nil also may you should post the code where you have included the debugger call.
I've tried to access doc_url but that seems to also return nil. When I have access to my server (later in the day) I'll post the code with the debugging bit in it.

Resources