Rails not loading in rspec file gives error "NameError: uninitialized constant" when accessing a model - ruby

Rails rails 4.0.2 and Ruby 2.0.0
I am using watir-webdriver with rspec to test my web app. I want to pick the last item in a list where presence of an associated model is true. Basically the model on the page is Category, and in the list of categories I want to select the last one that has Tools associated with it. A Category has many Tools. My spec file:
require 'watir'
require 'watir-webdriver'
browser = Watir::Browser.new
browser.window.maximize
RSpec.configure do |config|
config.before(:each) { #browser = browser }
config.after(:suite) { browser.close unless browser.nil? }
end
url = 'localhost:3000'
serial = Time.now
describe 'it should log in and CRUD menu items' do
it 'should log in' do
#browser.goto url
#browser.text_field(id: 'username').set Logins::user
#browser.text_field(id: 'password').set Logins::password(url)
#browser.button(:text, 'Login').click
#browser.link(:text, 'Menu Settings').click
end
it 'should not delete a category with tools still associated with it' do
category_name = Category.joins(:tools).where(true).order(name: :asc).last
#browser.link(:text, category_name).click
#browser.h4(class: 'tool_list')
#browser.link(text: 'Delete').click
#browser.driver.switch_to.alert.accept
#browser.p(text: 'You can not destroy this menu item until it is empty').present?
end
end
The line that is giving me the error is:
category_name = Category.joins(:tools).where(true).order(name: :asc).last
Not sure why I can't do a simple query in my spec file. I think I'm missing something really obvious.
edit
I updated my rspec version from 2 to 3 and ran:
rails generate rspec:install
because I was missing the spec/rails_helper.rb file. Still getting the same errors.

The problem isn't in Watir, but perhaps you have to require the file how defined your model ...
I think, perhaps, you have to require needed gems; like Active Record,
it's configuration, and initialisations files.

Related

Ruby-Rspec: Initialize PageObjects once in spec_helper instead of each spec

I variables set for xpaths in a file called PageObjects. Each spec I run I initialize the page objects with "p = PageObjects.new". However, I would like to initialize "p = PageObjects.new" once in "spec_helper.rb" instead of each spec.
This still gives me "error: uninitialized constant PageObject"...
require 'selenium-webdriver'
require 'yaml'
require 'rspec/retry'
require 'pry'
require 'bundler/setup'
p = PageObject.new
RSpec.configure do |config|
config.default_sleep_interval = 1
config.default_retry_count = 4
config.verbose_retry = false
config.display_try_failure_messages = true
config.exceptions_to_retry = [Net::ReadTimeout, Capybara::ElementNotFound]
end
Is there a way to achieve my goal by initializing PageObject once inside spec_helper rather than in each spec?
RSpec helpers seems to be the perfect solution for you
define the helper.rb
module Helpers
def p
#page_object ||= PageObject.new
end
end
Configure RSpec to include it:
RSpec.configure do |c|
c.include Helpers
end
And then you can use p method that will give you the PageObject:
specify do
expect(p).to be_a(PagObject)
expect(p.object_id).to eq(p.object_id)
end
You effectively want your test database to be maintained between tests. This is dangerous for a number of reasons, the most obvious being previous tests will affect future ones. As you're dealing with the same PageObject you will need to reset it between tests.
Putting that to one side, the options for enabling / disabling this can be found at:
https://relishapp.com/rspec/rspec-rails/docs/transactions, namely:
When you run rails generate rspec:install, the spec/rails_helper.rb
file includes the following configuration:
RSpec.configure do |config|
config.use_transactional_fixtures = true
end
The name of this setting is a bit misleading. What it really means
in Rails is "run every test method within a transaction." In the
context of rspec-rails, it means "run every example within a
transaction."
The idea is to start each example with a clean database, create
whatever data is necessary for that example, and then remove that data
by simply rolling back the transaction at the end of the example.
Disabling transactions If you prefer to manage the data yourself, or
using another tool like database_cleaner to do it for you, simply tell
RSpec to tell Rails not to manage transactions:
RSpec.configure do |config|
config.use_transactional_fixtures = false
end

Browser.back is not working

Using watir, I've written scripts to check multiple links are being directed to the right page as below.
Links= ["Link", "Link1"]
Links.each do |LinkValue|
#browser.link(:text => LinkValue).wait_until_present.click
fail unless #browser.text.include?(LinkValue)
#browser.back
end
What I am trying is:
maintaining Linktext in an array
iterating with each linktext
verify
navigate to the previous page to start verifying with next linktext.
But the script is not working. It is not executing after first value and also not navigating back.
The following scrip working for me
require 'watir'
browser = Watir::Browser.new(:firefox) # :chrome also work
browser.goto 'https://www.google.com/'
browser.link(text: 'Gmail').wait_until_present.click
sleep(10)
browser.back
sleep(10)
You are calling Kernel::Fail, which will raise an exception if the condition isn't satisfied.
In this case, it looks like you are expecting that the destination page will contain the same link text that was clicked on the originating page. If that's not true, then the script will raise an exception and terminate.
Here's a contrived "working" example (which only "works" because the link text exists on both originating and destination pages):
require 'watir'
b = Watir::Browser.new :chrome
b.goto "http://www.iana.org/domains/reserved"
links = ["Overview", "Root Zone Management"]
links.each do |link|
b.link(:text => link).click
fail unless b.text.include? link
b.back
end
b.close
Some observations:
I wouldn't use fail here. You should investigate a testing framework like Minitest or rspec, which have assertion methods for validating application behavior.
In ruby, variables (and methods and symbols) should be in snake_case.

poltergeist doesn't seem to wait for phantomjs to load in capybara

I'm trying to get some rspec tests run using a mix of Capybara, Selenium, Capybara/webkit, and Poltergeist. I need it to run headless in certain cases and would rather not use xvfb to get webkit working. I am okay using selenium or poltergeist as the driver for phantomjs. The problem I am having is that my tests run fine with selenium and firefox or chrome but when I try phantomjs the elements always show as not found. After looking into it for a while and using page.save_screenshot in capybara I found out that the phantomjs browser wasn't loaded up when the driver told it to find elements so it wasn't returning anything. I was able to hack a fix to this in by editing the poltergeist source in <gem_path>/capybara/poltergeist/driver.rb as follows
def visit(url)
if #started
sleep_time = 0
else
sleep_time = 2
end
#started = true
browser.visit(url)
sleep sleep_time
end
This is obviously not an ideal solution for the problem and it doesn't work with selenium as the driver for phantomjs. Is there anyway I can tell the driver to wait for phantom to be ready?
UPDATE:
I was able to get it to run by changing where I included the Capybara::DSL. I added it to the RSpec.configure block as shown below.
RSpec.configure do |config|
config.include Capybara::DSL
I then passed the page object to all classes I created for interacting with the webpage ui.
An example class would now look like this
module LoginUI
require_relative 'webpage'
class LoginPage < WebPages::Pages
def initialize(page, values = {})
super(page)
end
def visit
browser.visit(login_url)
end
def login(username, password)
set_username(username)
set_password(password)
sign_in_button
end
def set_username(username)
edit = browser.find_element(#selectors[:login_edit])
edit.send_keys(username)
end
def set_password(password)
edit = browser.find_element(#selectors[:password_edit])
edit.send_keys(password)
end
def sign_in_button
browser.find_element(#selectors[:sign_in_button]).click
end
end
end
Webpage module looks like this
module WebPages
require_relative 'browser'
class Pages
def initialize(page)
#page = page
#browser = Browser::Browser.new
end
def browser
#browser
end
def sign_out
browser.visit(sign_out_url)
end
end
end
The Browser module looks like this
module Browser
class Browser
include Capybara::DSL
def refresh_page
page.evaluate_script("window.location.reload()")
end
def submit(locator)
find_element(locator).click
end
def find_element(hash)
page.find(hash.keys.first, hash.values.first)
end
def find_elements(hash)
page.find(hash.keys.first, hash.values.first, match: :first)
page.all(hash.keys.first, hash.values.first)
end
def current_url
return page.current_url
end
end
end
While this works I don't want to have to include the Capybara::DSL inside RSpec or have to include the page object in the classes. These classes have had some things removed for the example but show the general structure. Ideally I would like to have the Browser module include the Capybara::DSL and be able to handle all of the interaction with Capybara.
Your update completely changes the question so I'm adding a second answer. There is no need to include the Capybara::DSL in your RSpec configure if you don't call any Capybara methods from outside your Browser class, just as there is no need to pass 'page' to all your Pages classes if you limit all Capybara interaction to your Browser class. One thing to note is that the page method provided by Capybara::DSL is just an alias for Capybara.current_session so technically you could just always call that.
You don't show in your code how you're handling any assertions/expectations on the page content - so depending on how you're doing that you may need to include Capybara::RSpecMatchers in your RSpec config and/or your WebPages::Pages class.
Your example code has a couple of issues that immediately pop out, firstly your Browser#find_elements (assuming I'm reading your intention for having find first correctly) should probably just be
def find_elements(hash)
page.all(hash.keys.first, hash.values.first, minimum: 1)
end
Secondly, your LoginPage#login method should have an assertion/expectation on a visual change that indicates login succeeded as its final line (verify some message is displayed/logged in menu exists/ etc), to ensure the browser has received the auth cookies, etc before the tests move on. What that line looks like depends on exactly how you're architecting your expectations.
If this doesn't answer your question, please provide a concrete example of what exactly isn't working for you since none of the code you're showing indicates any need for Capybara::DSL to be included in either of the places you say you don't want it.
Capybara doesn't depend on visit having completed, instead the finders and matchers will retry up to a specified period of time until they succeed. You can increase this amount of time by increasing the value of Capybara.default_max_wait_time. The only methods that don't wait by default are first and all, but can be made to wait/retry by specifying any of the count options
first('.some_class', minimum: 1) # will wait up to Capybara.default_max_wait_time seconds for the element to exist on the page.
although you should always prefer find over first/all whenever possible
If increasing the maximum wait time doesn't solve your issue, add an example of a test that fails to your question.

(Ruby) Cannot use selenium commands in cucumber tests?

I am trying to save object definitions in a "home page" file and simply call those methods whenever I need to use that button/link/image/etc. But for some reason the selenium commands are bringing up a NoMethodError. When I simply run the cucumber command while on the features folder in the terminal, I get these errors:
When I click on Site Management # features/step_definitions/steps.rb:17
undefined method `find_element' for nil:NilClass (NoMethodError)
./features/lib/pages/home.rb:3:in `siteMgmt'
./features/step_definitions/steps.rb:18:in `/^I click on Site Management$/'
features/test.feature:6:in `When I click on Site Management'
So in other words, it's trying to "click on site management," the code moves to the Home class, the SiteMgmt method (great!) and then fails when trying to run the selenium find_method method. I thought I might have to add a require selenium-webdriver at the top of home.rb, but a) that's NOT the case in steps.rb and, even if I add it, it doesn't work.
Here is the folder structure:
features/
--test.feature
lib/
pages/
--home.rb
step_definitions/
--steps.rb
support/
--env.rb
env.rb
require 'selenium-webdriver'
Dir[File.dirname(__FILE__) + "/../lib/pages/*.rb"].each {|file| require file }
Before do |scenario|
#driver = Selenium::WebDriver.for :chrome
#url = "URL goes here"
end
After do |scenario|
#driver.quit
end
test.feature
Feature: Proof of Concept
Stack overflow help!
Scenario:
Given I am logged into the site
When I click on Site Management
Then the Site Management page should load
steps.rb
Given(/^I am logged into AMP$/) do
#driver.get #amp_url
end
When(/^I click on Site Management$/) do
link = Home.new.siteMgmt
link.click
end
Then(/^the Site Management page should load$/) do
# assert here
end
home.rb
class Home
def siteMgmt
elem = #driver.find_element(:xpath, '//*[#id="body"]/section[2]/ul/li[1]/h3/a')
return elem
end
end
Thanks for all your help!
The #driver instance variable that's created in the Before block isn't available to an instantiated Home object. You could add a parameter to the site_mgmt method and pass the #driver instance variable in. Here's a contrived example:
class Home
def site_mgmt(driver)
elem = driver.find_element(:id, "logo")
end
end
require 'selenium-webdriver'
#driver = Selenium::WebDriver.for :chrome
#driver.navigate.to "http://www.iana.org/domains/reserved"
link = Home.new.site_mgmt(#driver)
link.click
A couple of notes: 1) variables in ruby are snake_case'd (i.e. site_mgmt instead of siteMgmt; and 2) return elem in site_mgmt isn't needed because ruby methods implicitly return.
Well, it turns out all I had to do is turn #driver to $driver. I'm still learning Ruby and didn't realize the difference.

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