Class methods vs Instances when using Page object model in ruby - ruby

I hope you are having a great day!
I'm wandering something, when using the POM (Page object model) we always create new instances of our classes, like the following simple example:
class LoginPage
def initialize(driver)
#driver = driver
end
def click_button
#driver.find_element(xpath: "//button[#title='Login']").click
end
end
# We create a new instance and we click the button
login_page = LoginPage.new(driver)
login_page.click_button
This makes sense for me, we can create multiple pages if we need to, we can update the state of whatever we need.
However, if we only would have one page open at the time, and we know nothing changes, doesn't it make more sense to take a class method based approach like?:
class LoginPage
class << self
def click_button
#driver.find_element(xpath: "//button[#title='Login']").click
end
end
end
# We create a new instance and we click the button
LoginPage.click_button
Please let me know your ideas, and if you have tried this approach before

Related

Use one class in a different class (basic oop understanding)?

I've been working with ruby for quite a while now and I am kind of stuck using an object oriented approach. I have read things about objects, classes, the solid principles and some tutorials but these often come up with only one single class, like in this intro (scroll to the very bottom of the page).
Here's the example from the linked website.
My question is how I can implement another class, let's say a Client best?
class ClientAccount
attr_accessor(:id,:limit)
def initialize(id, money, limit)
#id = id
#money = money
#limit = limit
end
def below_limit?(limit)
self.money < limit
end
def alert
if below_limit?(limit)
puts 'Client has no more money.'
else
puts 'Everything is fine'
end
end
protected
attr_accessor(:money)
end
What I want to do is to add one or more class, e.g. the client class below, which interact with the first one like:
account = ClientAccount.new('x234x19ue24', -245, -150)
id = account.read
client = Client.new('Mr X', '1.1.1900', id)
This can't be the correct way ...
Here's the idea of my Client class, I would either put the entire object in the contructor (or as parameter) or I would try to create a singleton(?) ...
class Client
def initialize(name, date_of_birth)
#name = name
#date_of_birth = date_of_birth
#account_id = account_id
end
private
def account
How can I access the ClientAccount here? Is this the correct way?
end
end
Even more complex if we add a third class, e.g. class ClientXYZ, but for this example two are maybe already enough ...
As you can see, pretty basic things and I would like to know more about the link between several classes. Currently my own code often feels like procedural code with some objects. I am sure that there are several approaches but I am really missing a medium level example. Related to the code above, how can I get the account.read method into the Client class to fill the account_id.
P.S. Can you give me an example how it could look like? What I could do while working on the code? Or even recommend a good tutorial on this?
To keep it consistent with the way you developed it, try:
class Client
def initialize(name, date_of_birth, account)
#name = name
#date_of_birth = date_of_birth
#account = account
end
private
def account
#account
end
public
def alert_on_account
account.alert
end
end
I also recommend you to read some tutorials available for free on github.
Tutorials in the form of blog posts only take you so far. They are often targeted to a really specific topic and your mileage will vary. To develop a broader understanding of OOP in Ruby, I suggest the book Practical Object-Oriented Design: An Agile Primer Using Ruby.

Ruby on Rails DB entry in a model (RoR 3)

Skip to EDIT2 which works
There is:
Project (has_many :group_permissions)
GroupPermission (belongs_to :project)
I have a form where you can create a new project. A project has several attributes like name, status etc. and now important: iit. iit is selectable with radio buttons: yes or no.
What I want:
If someone selects yes on iit in the Project form, there should be a new record in GroupPermission. So in EVERY project where iit= 1 there should be a certain GroupPermisson.
Can I make / check this in the GroupPermission model? Like if
class GroupPermission < ActiveRecord::Base
if Project.where(iit: 1)
make me a record for each project
end
Can I even make database entries in the model like so?
Is this the right way?
EDIT1:
In the Project controller I added:
if params[:iit] = 1
record = GroupPermission.new(cn: 'ccc-ml', project_id: params[:id])
record.save
end
It then adds a new record in GroupPermissions. But I need the :id of the project. How can I access the id of the project which is about to be saved?
EDIT2
In the Project Controller
after_filter :iit_test, :only => [:create]
...
private
def iit_test
if #trial.iit == 1
record = GroupPermission.new(cn: 'ccc-ml', project_id: #project.id, name: 'CATEGORY_3')
record.save
end
end
EDIT2 works fine. I just have to check it with update etc.
Thank you in advance.
the EDIT2 will only work for apis hitting the controller and not when you add a project via rails console, use in tests etc.
Also this :iit_test will not correctly work for update action as you are creating a new GroupPermission everytime. (GroupPermission may already exists for this project)
You should probably add a callback after_save in the project model and handle adding of a new GroupPermission there.
Write a before action in Project controller like below
def find_project
#project = Project.find_by(id: params[:id])
end
by calling above method in before action you will get the instance of the Project.
Hence you can create the Group Permission by
def create_group_permission
#project.group_permissions.create(cn: 'ccc-ml')
end
The above will create a GroupPermission record with a project id which was found in before action.
Instead of having a method in your projects_controller, you can move the logic to a seperate service. That will make your code easy to test etc..
Something like..
class ProjectsController < ApplicationController
def create
#project = ProjectService.create(project_params)
# rest of your controller code
end
end
#app/services
class ProjectService
def self.create(project_params)
project = Project.new(project_params)
if project.save && project.iit == 1
GroupPermission.create(cn: 'ccc-ml', project_id: project.id, name: 'CATEGORY_3')
end
project
end
end

How to add a section to a SitePrism page object dynamically?

I'm using SitePrism to test my web application. I have a number of classes that extend SitePrism::Page and a number of often-used HTML snippets are represented by matching classes extending SitePrism::Section
class Login < SitePrism::Section
element :username, "#username"
element :password, "#password"
element :sign_in, "button"
end
class Home < SitePrism::Page
section :login, Login, "div.login"
end
The problem is, the application I'm working on is based on a CMS, in which a page can be assembled by selecting a Template based on pre-defined content and then drag-and-dropping any number of available components onto the page.
The initial developers created a Page Object to mirror every available Template. This was fine as long as the number of tests was low and there weren't too many variants of pages that we had to test in our feature files.
With the addition of multiple test cases, the page objects started growing at an alarming rate.
While we can easily mitigate code duplication by defining Sections for every component available in the CMS and reusing them across Page Objects, there's just a lot of properties that rarely get used.
class BlogPost < SitePrism::Page
section :logo, MySite::Components::Logo, '.logo'
section :navigation, MySite::Components::Navigation, '.primary-navigation'
section :header, MySite::Components::BlogHeader, '.header'
section :introduction, MySite::Components::Text, '.text .intro'
# and so on, a lot of dynamic staff that could potentially be dropped onto the page
# but does not neccessarily be there, going in dozens of lines
end
Is there a way in SitePrism to dynamically add a section to an instance of a Page Object as opposed to a whole class?
Then(/^Some step$/) do
#blog = PageObjects::BlogPost.new()
#blog.load("some url")
#blog.somehow_add_a_section_here_dynamically
expect (#blog.some_added_section).to be_visible
end
It also worries me that doing something like this would potentially cause CSS selectors to leak into the step definitions, which is generally a bad practice.
Another way to work around this would be to build Page Objects for specific examples of pages as opposed to the versatile templates. The Template Page Objects could just contain whatever's baked into the templates and be extended by other Page Objects that mirror specific pages, taking care of the differences. It sounds like a much cleaner approach so I'm probably going to write my tests this way
Anyway, the technical part of the question stands. Regardless of how good or bad an idea it is, how could I dynamically extend a page object with an additional section? I'm just curious.
I had at one point wanted to do what you're talking about for pretty much the same reason. We had pages that could have new content-sections dragged into them; making them very dynamic. I experimented with ways to do this and never found any that I particularly liked.
Methods like element and sections in site-prism each define a number of methods for the class. You could call MyPage.section in your test or add a method that calls self.class.section and use that to add on new sections. But those will exist for all instances of that page; probably not what you want.
You could alternatively tack them on to through the singleton_class:
my_page = MyPage.new
my_page.singleton_class.section(:new_section, NewSection, '#foo')
But that's getting a bit ugly to toss into your tests, right?
I've long thought that Sections should have a default_locator (but tough to get patches accepted)
With that we could generalize this a bit:
class DynamicSection < SitePrism::Section
def self.set_default_locator(locator)
#default_locator = locator
end
def self.default_locator
#default_locator
end
end
class DynamicPage < SitePrism::Page
# add sections (and related methods) to this instance of the page
def include_sections(*syms)
syms.each do |sym|
klass = sym.to_s.camelize.constantize
self.singleton_class.section(sym, klass, klass.default_locator)
end
end
end
And then you can use these as the parents.
class FooSection < DynamicSection
set_default_locator '#foo'
element :username, "#username"
end
class BlogPostPage < DynamicPage
# elements that exist on every BlogPost
end
In the tests:
#page = BlogPostPage.new
#page.include_sections(:foo_section, :bar_section)
expect(#page.foo_section).to be_visible
On the other-hand it really might be easier to just create a few different variations of the page-object for use in tests. (Are you really going to test that many variations? Maybe..maybe not.)
You can add a section to just a page object instance by modifying its singleton class.
Then(/^Some step$/) do
#blog = PageObjects::BlogPost.new
#blog.load("some url")
# You can see that #blog does not have the logo section
expect(#blog).not_to respond_to(:logo)
# Add a section to just the one instance of BlogPost
class << #blog
section(:logo, MySite::Components::Logo, '.logo')
end
# You can now see that #blog has the logo section
expect(#blog).to respond_to(:logo)
end
This will likely result in duplicate the section definition in multiple steps. To address this, you could create a method within the BlogPost to dynamically add the specified sections.
In the following BlogPost class, a dictionary of available components is created. The class has a method that adds components based on the dictionary definition.
class BlogPost < SitePrism::Page
COMPONENT_DICTIONARY = {
logo: {class: MySite::Components::Logo, selector: '.logo'},
navigation: {class: MySite::Components::Navigation, selector: '.primary-navigation'},
header: {class: MySite::Components::BlogHeader, selector: '.header'}
}
def add_components(*components)
Array(components).each do |component|
metaclass = class << self; self; end
metaclass.section(component, COMPONENT_DICTIONARY[component][:class], COMPONENT_DICTIONARY[component][:selector])
end
end
end
As an example of the usage:
# Create a blog post that just has the logo section
#blog = BlogPost.new
#blog.add_components(:logo)
# Create a blog post that has the navigation and header section
#blog2 = BlogPost.new
#blog2.add_components(:navigation, :header)
# Notice that each blog only has the added components
expect(#blog).to respond_to(:logo)
expect(#blog).not_to respond_to(:navigation)
expect(#blog).not_to respond_to(:header)
expect(#blog2).not_to respond_to(:logo)
expect(#blog2).to respond_to(:navigation)
expect(#blog2).to respond_to(:header)
Use page.find for that purpose
class MyPage < SitePrism::Page
element :static_selector_element, "#some-static-id"
def dynamic_element(id)
find "label[for=\"dynamic-value-#{id}\"]"
end
end
in your test:
RSpec.feature 'My Feature' do
scenario 'Success' do
p = MyPage.new
p.visit '/'
p.static_selector_element.click
p.dynamic_element(SomeObject.fist.id).click
end
end

Referencing parent objects from children

Say you have a User class:
class User
attr_accessor :widgets
end
and a Widget:
class Widget
attr_accessor :owner
end
and you assign some widgets to a user:
user = User.new
widget = Widget.new
widget.owner = user
widget2 = Widget.new
widget2.owner = user
user.widgets = [widget, widget2]
Now you have a recursion of user → widgets → owner. user.inspect shows the same user reference once for every widget, cluttering the output:
user.widgets.first.owner.widgets.first.owner
=> #<User:0x00000001cac820 #widgets=[#<Widget:0x00000001ca45f8 #owner=#<User:0x00000001cac820 ...>>, #<Widget:0x00000001c87a20 #owner=#<User:0x00000001cac820 ...>>]>
If we were to reduce this data structure to a hash we'd have:
{ user:
{ widgets: [ { widget: ... },
{ widget: ... } ]
}
}
We could pass this around instead of assigning widget.owner and it would be easy enough to reference the parent user.
I wonder if there's a way to access the parent object through the child without having to assign owner to all child objects, an interface that could work like this:
user = User.new
widget = Widget.new
user.widgets = [widget]
widget.parent
# => #<User:... #widgets=[#<Widget:...>]>
What you're looking for is a custom writer. There is no parent method or equivalent on the Object or BaseObject class, because implementing that would require objects to track every other object that happened to point to it. When you want that functionality though, custom writers make it simple and easy to implement.
class Widget
attr_accessor :owner
end
class User
attr_reader :widgets
def widgets=(widgets)
#widgets = widgets
widgets.each do |widget|
widget.owner = self
end
end
end
user = User.new
widget = Widget.new
user.widgets = [widget]
widget.owner #=> #<User:... #widgets=[#<Widget:...>]>
Note that this custom writer only covers regular assignment, like user.widgets = [widget]. If you wanted to do something like user.widgets << widget, the new widget wouldn't be assigned an owner. If you want to be able to do that, you'll either have to monkeypatch Array like this (not recommended), or you'll have to create a WidgetCollection class that likely inherits from Array. That's what ActiveRecord::Associations does. Speaking of which, if you happen to be using Rails, definitely look into using ActiveRecord to do all this for you. It looks like you're asking about plain old ruby so I'm giving you a vanilla ruby answer.
Thought of sharing the explanation I've come up with. It has no solid proof, but might help.
Firstly, there isn't any problem with loop chaining objects like that. The code wouldn't work fine just like that if there was a problem with loop chains, it would either crash or show an error. So it might be handling these kind of loop references in a way, but it really makes sense if you understand that variables are just references to objects.
I mean when you simply access a User instance user, it doesn't just load up everything inside it recursively. It just does nothing or maybe just takes out the reference. What really sets up the recursion is the inspect method, which recursively inspects all the instance variables inside the instance. But it does handle the deep inspects, with the .....
So your real problem should only be with making inspects look compact. You can override that method, so that it won't recurse, and gives you a nice message. Example :
class User
attr_accessor :widgets
def initialize
#widgets =[]
end
def inspect
"[User:objid=#{object_id};widgets=#{widgets.size}]"
end
end
class Widget
attr_accessor :owner
def inspect
"#[Widget:objid=#{object_id}]"
end
end
The interface can remain the same.
user = User.new
widget = Widget.new
widget.owner = user
widget2 = Widget.new
widget2.owner = user
user.widgets = [widget, widget2]
user.widgets.first.owner.widgets.first.owner
# => #[User:objid=-590412418;widgets=2]

How to call a page-object from a class.rb at Support folder

I am using the page-object gem. Suppose i have a page-object on features/bussines/pages/booking_page.rb for a page like:
class Booking
include PageObject
span(:txtFirstName, :id => 'details_first_name')
end
...and i use a "tools" class located at features/support/tools.rb with something like:
class MyTools
def call_to_page_object
on Booking do |page|
puts page.txtFirstName
end
end
end
...but this approach fails because calling to the object from the class is not allowed:
undefined method `on' for #<Booking:0x108f5b0c8> (NoMethodError)
Pretty sure i'm missing some concept on the way to use the page-object from a class but don't realize whats the problem. Can you please give me an idea about what could be wrong here, please?
Thank you very much!
============================
Justin found the reason why the call to the class crash. The final class code results:
class MyTools
#Include this module so that the class has the 'on' method
include PageObject::PageFactory
def initialize(browser)
#Assign a browser object to #browser, which the 'on' method assumes to exist
#browser = browser
end
def getCurrentRewards
on Booking do |page|
rewards_text = page.rewards_amount
rewards_amount = rewards_text.match(/(\d+.*\d*)/)[1].to_f
puts "The current rewards amount are: #{rewards_amount}."
return rewards_amount
end
end
end
And the call to the function:
user_rewards = UserData.new(#browser).getCurrentRewards
Why it did not work me? Two main reasons:
I didn't pass the browser object to the class <== REQUIRED
I didn't include the PageObject::PageFactory in the class <== REQUIRED for the "on" method.
Thanks all!
To use the on (or on_page) method requires two things:
The method to be available, which is done by including the PageObject::PageFactory module.
Having a #browser variable (within the scope of the class) that is the browser.
So you could make your MyTools class work by doing:
class MyTools
#Include this module so that the class has the 'on' method
include PageObject::PageFactory
def initialize(browser)
#Assign a browser object to #browser, which the 'on' method assumes to exist
#browser = browser
end
def call_to_page_object
on Booking do |page|
puts page.txtFirstName
end
end
end
You would then be calling your MyTools class like:
#Assuming your Cucumber steps have the the browser stored in #browser:
MyTools.new(#browser).call_to_page_object
What are you trying to do?
Did you read Cucumber & Cheese book?
Pages should be in the features/support/pages folder. You can put other files that pages need there too.
If you want to use on method in a class, you have to add this to the class:
include PageObject
The code from MyTools class looks to me like it should be in Cucumber step file, not in a class.
Your class should use the extend keyword to access special class methods like span:
class Booking
extend PageObject
span(:txtFirstName, :id => 'details_first_name')
end
I hope this works.

Resources