Ruby PageObject, visiting webpages outside of step defintions - ruby

I am trying to call page object methods outside of my step definitions for visiting pages so I can avoid duplication within my steps. I believe the issue now is with how I am creating my browser session, and the object I created in my hooks is not accessible by anything outside of other hooks / steps definitions.
Directory structure:
├── features/
│ ├── Gemfile
│ ├── feature_files
│ │ ├── example1.feature
│ │ ├── example2.feature
│ ├── hooks
│ │ ├── web_hooks.rb
│ ├── step_definitions
│ │ ├── web_steps.rb
│ ├── Support
│ │ ├── example1.feature
| | | |──pages
| | | | |──test.rb
| | | |──controller
| | | | |──controller.rb
My Web hooks are nothing more than the basic new watir session + logging:
#browser = Watir::Browser.new(:chrome)
So when I am visiting my pages within my step definitions that all works as expected, but when I change my step to call a method outside of the step definitions I get back.
Unable to pick a platform for the provided browser or element: nil.
nil was passed to the PageObject constructor instead of a valid browser or element object.
My controller class looks like this:
class TestController
class << self
include PageObject
include PageObject::PageFactory
def test_visit(url)
visit TestPage, :using_params => {:id => url} do |page|
page.populate
embed(#browser.screenshot.base64, 'image/png', "STUB Setup for #{code}")
end
end
end
end
I have several variations of the above controller including turning it into a module and extending pageobject functionality, trying to pass #browser into the method from the step definition etc but they all result in the following. I have also tried requiring everything under my support structure prior to running my cucumber tests.

The visit method assumes that #browser is defined and available. You will need to define it within the test_visit method.
Passing browser instance
The most straightforward solution may be to pass in the Watir::Browser object:
def test_visit(url, browser)
#browser = browser
visit TestPage, :using_params => {:id => url} do |page|
page.populate
embed(#browser.screenshot.base64, 'image/png', "STUB Setup for #{code}")
end
end
With the step definitions presumably looking like:
TestController.test_visit('some_url', #browser)
If you are making a lot of calls to the TestController and always using the same browser instance, you could setup a #browser in the class. This would save you from having to always pass in the browser instance.
class TestController
class << self
include PageObject
include PageObject::PageFactory
def browser=(browser)
#browser = browser
end
def test_visit(url)
visit TestPage, :using_params => {:id => url} do |page|
page.populate
embed(#browser.screenshot.base64, 'image/png', "STUB Setup for #{code}")
end
end
end
end
In your web hooks, where you start the browser, you could configure the TestController to have the browser instance:
#browser = Watir::Browser.new(:chrome)
TestController.browser = #browser
Then your step definitions do not need to pass in the #browser:
TestController.test_visit('some_url')
Make methods available to step definitions
Another option is to make the shared methods directly available to step definitions. As the methods will be in the scope of the step, #browser and embed will be available.
First, define your methods in a module:
module TestController
def test_visit(url)
visit TestPage, :using_params => {:id => url} do |page|
page.populate
embed(#browser.screenshot.base64, 'image/png', "STUB Setup for #{code}")
end
end
end
In your env.rb (or similar), make the module methods available to the step definitions by using World:
World(TestController)
Your step definitions can then simply call the methods directly:
test_visit('some_url')

Related

Wrong number of arguments Ruby while instantiating a Subclass

I have 3 classes that hare related through inheritance (it's a course exercise)
I have a Brand < Product < DBHandler classes
They are in a subfolder of the project, and all of them use require to access each other:
├── class
│   ├── Brand.rb
│   ├── Category.rb
│   ├── DBHandler.rb
│   └── Product.rb
├── db
│   ├── catalogo_categorias.txt
│   ├── catalogo_marca.txt
│   ├── catalogo_producto.txt
│   └── inventario_final.txt
├── init.rb
└── README.md
Clases
The Product file has in it:
require "#{Dir.pwd}/class/DBHandler"
class Product < DBHandler
attr_reader :key, :final_price, :db
attr_accessor :name, :unit, :brand, :category, :price
def initialize(name, unit, brand, category, price)
#name = name
#unit = unit
#brand = brand
#category = category
#price = price
#final_price = set_final_price
#db = DBHandler.new
#key = set_key
end
def set_key
#key = "#{#db.how_many+1}#{self.name[0..2].upcase}3BS"
end
def set_final_price
#final_price = self.price * 1.19
end
def search_product(search_expresion)
#db.db_search(search_expresion)
end
def alta_product
#db.write(
"#{key},#{name},#{unit},#{brand},#{category},#{price},#{final_price}\n"
)
end
end
and the Brand file has:
require "#{Dir.pwd}/class/Product"
class Brand < Product
attr_reader :brand_key, :a_brands, :q_brands
attr_accessor :brand
def initialize(brand)
# use super to use parent's attributes
super(brand)
#brand_key = set_brand_key
#a_brands = get_file.read
.split("\n")
.map { |abrand| abrand.split(',') }
#q_brands = #a_brands.count
end
def set_brand_key
"#{#q_brands}#{self.brand[0..2].upcase}3BS"
end
def get_file
File.open("#{Dir.pwd}/db/catalogo_marca.txt")
end
def alta_brand
get_file.write("#{#brand_key},#{#brand}", mode: 'a')
end
end
Init file
All the files are called from the init.rb file in order to call some instance methods.
this is my Init.rb file first lines.
require './class/Product'
require './class/Brand'
require './class/DBHandler'
require './class/Category'
The problem
when I want to create a new instance of Brand class, I have tried:
brand = Brand.new(name, unit, brand, category, price) and it returns wrong number of arguments (given 5, expected 1) (ArgumentError)
brand = Brand.new(brand) and it returns wrong number of arguments (given 1, expected 5) (ArgumentError)
I don't know what may I doing wrong.
I need to create a Brand instance with only brand argument
Your problem is here
super(brand)
This is saying "call the parent constructor with brand as the only argument". The parent constructor is Product#initialize, which takes five arguments. So no matter how you call Brand#initialize, it's going to fail since it calls Product#initialize with the wrong number of arguments. You need to call super with all five.
But I challenge the frame. Subclassing is a very tight coupling, an "is-a" relationship. What you're claiming is that every brand is a product, and I fail to see how that's true. So rather than fixing the super call, you might reconsider your design and ask yourself if you really meant to make a subclass.

How to request separate folder view path based on controller name in Sinatra?

Here's the contents of my app/controllers/application_controller.rb:
require 'sinatra/base'
require 'slim'
require 'colorize'
class ApplicationController < Sinatra::Base
# Global helpers
helpers ApplicationHelper
# Set folders for template to
set :root, File.expand_path(File.join(File.dirname(__FILE__), '../'))
puts root.green
set :sessions,
:httponly => true,
:secure => production?,
:expire_after => 31557600, # 1 year
:secret => ENV['SESSION_SECRET'] || 'keyboardcat',
:views => File.expand_path(File.expand_path('../../views/', __FILE__)),
:layout_engine => :slim
enable :method_override
# No logging in testing
configure :production, :development do
enable :logging
end
# Global not found??
not_found do
title 'Not Found!'
slim :not_found
end
end
As you can see I'm setting the views directory as:
File.expand_path(File.expand_path('../../views/', __FILE__))
which works out to be /Users/vladdy/Desktop/sinatra/app/views
In configure.ru, I then map('/') { RootController }, and in said controller I render a view with slim :whatever
Problem is, all the views from all the controllers are all in the same spot! How do I add a folder structure to Sinatra views?
If I understand your question correctly, you want to override #find_template.
I stick this function in a helper called view_directory_helper.rb.
helpers do
def find_template(views, name, engine, &block)
views.each { |v| super(v, name, engine, &block) }
end
end
and when setting your view directory, pass in an array instead, like so:
set :views, ['views/layouts', 'views/pages', 'views/partials']
Which would let you have a folder structure like
app
-views
-layouts
-pages
-partials
-controllers
I was faced with same task. I have little experience of programming in Ruby, but for a long time been working with PHP. I think it would be easier to do on it, where you can easily get the child from the parent class. There are some difficulties. As I understand, the language provides callback functions like self.innereted for solving of this problem. But it did not help, because I was not able to determine the particular router in a given time. Maybe the environment variables can help with this. But I was able to find a workaround way to solve this problem, by parsing call stack for geting caller class and wrapping output function. I do not think this is the most elegant way to solve the problem. But I was able to realize it.
class Base < Sinatra::Application
configure do
set :views, 'app/views/'
set :root, File.expand_path('../../../', __FILE__)
end
def display(template, *args)
erb File.join(current_dir, template.to_s).to_sym, *args
end
def current_dir
caller_class.downcase!.split('::').last
end
private
def caller_class(depth = 1)
/<class:([\w]*)>/.match(parse_caller(caller(depth + 1)[1]))[1]
end
def parse_caller(at)
Regexp.last_match[3] if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
end
end
The last function is taken from here. It can be used as well as default erb function:
class Posts < Base
get '/posts' do
display :index , locals: { variables: {} }
end
end
I hope it will be useful to someone.

java.lang.NoClassDefFoundError CRFClassifier in a Rails app

I'm trying to run the CRFClassifier on a string to extract entities from the string. I'm using the Ruby bindings for the Stanford NLP entity recognizer from here: https://github.com/tiendung/ruby-nlp
It works perfectly fine on its own class say (nlp.rb). When I run ruby nlp.rb it works fine. However, I've tried to create an object of this class inside one of my controllers in my rails app and for some reason I'm getting the following error:
java.lang.NoClassDefFoundError: edu/stanford/nlp/ie/crf/CRFClassifier
Here is the code that works fine on its own but not inside a controller.
def initialize
Rjb::load('stanford-postagger.jar:stanford-ner.jar', ['-Xmx200m'])
crfclassifier = Rjb::import('edu.stanford.nlp.ie.crf.CRFClassifier')
maxentTagger = Rjb::import('edu.stanford.nlp.tagger.maxent.MaxentTagger')
maxentTagger.init("left3words-wsj-0-18.tagger")
sentence = Rjb::import('edu.stanford.nlp.ling.Sentence')
#classifier = crfclassifier.getClassifierNoExceptions("ner-eng-ie.crf-4-conll.ser.gz")
end
def get_entities(sentence)
sent = sentence
#classifier.testStringInlineXML( sent )
end
It's the same exact code in both cases. Anyone has any idea of what's happening here!?
Thanks in advance!
I think you need this:
Rjb::load('/path/to/jar/stanford-postagger.jar:/path/to/jar/stanford-ner.jar', ['-Xmx200m'])
I just tried this and it works. Create a dir in lib called nlp. Put the jars there and then create a class which loads the jars using the full path:
So you end up with:
├── lib
│   ├── nlp
│   │   ├── stanford-ner.jar
│   │   └── stanford-postagger.jar
│   └── nlp.rb
require 'rjb'
class NLP
def initialize
pos_tagger = File.expand_path('../nlp/stanford-postagger.jar', __FILE__)
ner = File.expand_path('../nlp/stanford-ner.jar', __FILE__)
Rjb::load("#{pos_tagger}:#{ner}", ['-Xmx200m'])
crfclassifier = Rjb::import('edu.stanford.nlp.ie.crf.CRFClassifier')
maxentTagger = Rjb::import('edu.stanford.nlp.tagger.maxent.MaxentTagger')
maxentTagger.init("left3words-wsj-0-18.tagger")
sentence = Rjb::import('edu.stanford.nlp.ling.Sentence')
#classifier = crfclassifier.getClassifierNoExceptions("ner-eng-ie.crf-4-conll.ser.gz")
end
def get_entities(sentence)
sent = sentence
#classifier.testStringInlineXML( sent )
end
end
Little test class:
require_relative 'lib/nlp'
n = NLP.new
n.get_entities("Good afternoon Rajat Raina, how are you today?")
output:
ruby t.rb
Loading classifier from /Users/brendan/code/ruby/ruby-nlp/ner-eng-ie.crf-4-conll.ser.gz ... done [1.2 sec].
Getting data from Good afternoon Rajat Raina, how are you today? (default encoding)
Good afternoon <PERSON>Rajat Raina</PERSON>, how are you today?

How to get object instance by filename in ruby

There is a class in a ruby file test.rb
#test.rb
class AAA<TestCase
def setUp()
puts "setup"
end
def run()
puts "run"
end
def tearDown()
end
end
In another file test2.rb, i want to get the instance of AAA by file name "test.rb".
In python, i can do this by below:
casename = __import__ ("test")
for testcase in [getattr(casename, x) for x in dir(casename)]:
if type(testcase) is type and issubclass(testcase, TestCase):
#do something with testcase()
How can i implement the same function in ruby now.
Thanks
Just require the filename without the .rb extension like so:
require './test'
Suppose you have this directory structure:
+-- project/
| |
| +-- folder1/
| | |
| | +-- file1.rb
| |
| +-- folder2/
| |
| +-- file2.rb
|
+-- file3.rb
in this case you may want to add specific directories to the load path like so:
# in file3.rb
$LOAD_PATH.unshift './folder1'
this way you can require files by their name without specifying the folder every time:
require 'file1'
Now for the second part, getting an instance. You could just do AAA.new but i suppose you want to dynamically create instances of classes that are subclasses of TestCase. First you have to find all subclasses of TestCase:
class Class
def subclasses
constants.map do |c|
const_get(c)
end.select do |c|
c.is_a? Class
end
end
end
this will enable you to get a list of subclasses like so:
TestCase.subclasses
#=> [TestCase::AAA]
from which you can construct your objects
TestCase.subclasses.map{|klass| klass.new }
#=> [#<TestCase::AAA:0x007fc8296b07f8>]
or even shorter if you do not need to pass arguments to new
TestCase.subclasses.map(&:new)
#=> [#<TestCase::AAA:0x007fc8296b07d0>]
So far, so good. But if i get this right you are trying to build a test runner. Don't. There are plenty of testing libraries and great test runners out there. Ruby has the built-in Minitest and this blog post explains very well how you can best run your tests.

View specific stylesheets in Sinatra (by naming convention)

I would like to have my Sinatra app include a view specific stylesheet in the layout.
Consider this simple app:
app_folder_root/
| my_app/
| my_app.rb
| public/
| css/
| index.css
| layout.css
| views/
| index.haml
| layout.haml
config.ru
config.ru:
require 'rubygems'
require './my_app/my_app'
map '/' do
run MyApp
end
app.rb:
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/' do
haml :index
end
end
I tried setting a variable in my_app.rb that sets the name of the view and tried to reference it in layout.haml, but that did not work (I probably wouldn't have went with this as a final solution to the problem since I felt this was a code smell, but was just trying different possibilities).
This is using Haml, but I am hoping that is irrelevant - thinking it should be the same for erb, etc.
In layout.haml, I would like to reference the view that will be rendered and include a view specific stylesheet by a naming convention. For example, if index.haml is going to render, I would like to include css/index.css. What is the best way to go about doing this?
I solved this by doing the following:
In index.haml (at the top) I created or appended an array named views:
- #views = Array.new unless defined? #views
- #views << 'index'
In layout.haml I reference #views:
%head
- #views.each do |view|
- haml_tag :link, {:rel => 'stylesheet', :type => 'text/css', :href => "css/#{view}.css"}
I am a little disappointed with having to check #views in the view to make sure it is defined before appending to it, but for now it is manageable.
EDIT: Solved the problem with having to check if #views is defined in each view. In config.ru add the following:
before do
#views = Array.new
end
I can now remove this line from the views:
- #views = Array.new unless defined? #views

Resources