2

So i've been reading a lot on Page Object Model. Im going to be starting developing a framework for a new project. We are using Selenium Webdriver w/Capybara (Ruby).

I've seen most examples of PoM in java, where classes feel more obvious...however I still am a bit lost on some of the PoM setup (especially for Ruby).

  1. Should each page still be a class? Should they inherit from one "Main" class (What would it contain? maybe basic driver setup)
  2. When do I make and return class objects? Let say I have a test that goes through 3 pages, do I make 3 class objects in the beginning? Do they share data between each other?
  3. I notice on some examples the methods for a page (like filling in a form or something similar) typically return the page object at the end. Why do they do this?
  4. This is more of a general "Framework" question. But when making a framework for a project what should I be doing in a general sense? Obviously writing helper functions but besides making everything in PoM how else am I making a "framework"? (I always get confused when people say they are writing a framework for their automated tests and what that entails)
  • I recommend looking at the Taza framework for web ui automation with ruby. It might give you some ideas on designing a framework. github.com/scudco/taza/wiki. this link is from 2011 and its now maintained by hammernight instead of scudco. I saw that the framework was used for a long time at a popular online shopping company whose name I won't disclose. – MasterJoe2 May 8 at 5:18
1

Ruby makes it easier to roll your own.
Here's one approach:

First, add

require 'yaml'

to spec_helper.rb

You don't need a gem for yaml but you do need the require.

Now create a PageObject class and also a yml file containing the locators.

page_object.rb # (you'll probably `include` this class in each spec)
class PageObject
end

file:locators.yml
member_signup: 'mbrSignAccessOne'
member_last_name: 'input#MbrName001'
...

and use meta-programming to create the methods using _send_ by setting the initialize to read and load the yaml file with the locators.

class PageObject
  def initialize file
    page_object = YAML.load File.read(file)
    page_object.each do |k, v|
      self.class.__send__ :define_method, k do v end
    end
  end
end

Instantiate the class

p = PageObject.new

and now use the english named methods

click_on p.member_signup
fill_in p.member_last_name, with: 'value'
...

instead of

click_on 'mbrSignAccessOne'
fill_in 'input#MbrName001', with: 'value'

Now the above will create one global page object class (shortened to 'p' for convenience and brevity). So not truly a PAGE object class. More a 'global page objects' class. You may find that in many instances this is all you need. I have worked in several companies with smaller applications and this worked quite well.

In larger applications with more pages and sections and forms and tree hierarchies you may want to add some scoping. You do not have to stick to scoping at the page level. I have actually found that using a combination of global Page Objects along with some workflow ones (domain specific but usually around workflows with a bunch of forms collecting user input) can be a good way to go.

For example:

globals = PageObject.new('globals.yml')
p = PageObject.new('signup_workflow.yml')
spouse = PageObject.new('spouse.yml')

and in those yml files you'll have the following locators defined so that this will work

click_on globals.signup
fill_in p.member_last_name
select '31' from spouse.age

So for a given spec you'll need to figure out which workflow / section / page Page Objects you'll need to instantiate.

  • I don't quite understand what self.class.__send__ :define_method, k do v end exactly is doing or what the purpose of _send_ is? – Mercfh Mar 20 at 14:03
  • Sure. So for the current class (self) we are using the __send__ method. This allows you to send a message to the class and in this case the message is "define method" so the class is going to define a method. The name of (each of) the method is k, which is the key from the file, i.e. the first column which is the english name for that Page Object. Then the value that the method returns is simply 'v' - the second column in the file which represents the actual page locator. – Michael Durrant Mar 20 at 15:32
1

Firstly, given your tech stack, I'd recommend investigating the site_prism gem - it may provide good base for you.

Now, for the questions:

Should each page still be a class?

Each page should be an object that provides method to interact with elements and retrieve information about them. Additionally, you should extensively create fragments to represent each element of the page, creating an object tree similar to the DOM.

E.g., if your page have a table, you can extract three classes:

  • A Page class, which have a Table object;
  • A Table class, which contains a list of Rows objects;
  • A Row object, which contains the individual elements of each row.

That's the idea behind the Single Responsibility Principle.

Should they inherit from one "Main" class (What would it contain? maybe basic driver setup)

I prefer to call Base Page, given that Main is often use on other contexts. It will depend your architecture. I prefer to keep it as lean as possible, and create more specific Base pages as needed. For instance:

  • (abstract) BasePage: Holds an element call baseElement, which contains the DOM room of the child object. Enforces any method implementation you want: I like to enforce a method called waitLoad, when the Page Object framework doesn't enforces.

  • (abstract) PageWithHeader: Inherits from BasePage and contains an object of type Header.

  • (concrete) UserPage: Inherits from PageWithHeader.

When do I make and return class objects? Let say I have a test that goes through 3 pages, do I make 3 class objects in the beginning?

The knowledge of the flow should be placed on the Page Objects, as much as you can.

E.g.:

  • LoginPage object will provide a method called login(username, password) and return an HomePage.

  • HomePage object will provide a method called logout() which will return a LoginPage.

If you are testing a wrong login process, you can:

loginPage = new LoginPage();
loginPage.login("dasdasdsad", "dffsvsdfds");
assertTrue(loginPage.isErrorDisplayed?()) // Ignoring the HomePage object returned

Do they share data between each other?

Try to avoid static/global data - they break encapsulation and make object more dependent of each other. Try to pass information between them, not share.

I notice on some examples the methods for a page (like filling in a form or something similar) typically return the page object at the end. Why do they do this?

I think it's covered above.

This is more of a general "Framework" question. But when making a framework for a project what should I be doing in a general sense? Obviously writing helper functions but besides making everything in PoM how else am I making a "framework"? (I always get confused when people say they are writing a framework for their automated tests and what that entails)

A framework is indeed a very generic term. I would say that most people mean that they are creating infrastructure to control the different tools (Selenium, reporting, API utilities, test data management) in a way. I would just raise the point that for this to be a framework indeed, you would probably have to separate the framework from the implementation itself. If you are:

1 - Not fetching the framework code as a dependency;

2 - Have project specific information in the framework.

I would say that you are not building a framework, because you cannot re-use the framework in another project with similar goals.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.