5

Apologies if this is an improper question, as it's actually a multi-part question (But it seems like unnecessary clogging of the site to make multiple posts).

Im writing my first PoM framework, it's using Ruby and Capybara (With Selenium for the webdriver). Currently I have it setup as such (Folder wise, - represents depth):

spec
-support
--Modules
--PageObjects
(actual test .rb files)

However im running into a few "Design/Architecture" questions that I can't seem to find a solid opinion on:

  1. The first one is best way to set up "Selectors". Currently each Page Object is a class (All inheriting from one Base Page Object class). Right now my selectors all essentially ruby methods (Private methods to be specific). I wonder if this is overkill...and it makes more sense to just make variables at the top similar to how (http://elementalselenium.com/tips/7-use-a-page-object) does it? Do I gain anything making them private methods at all?

  2. Speaking of the base page object, currently it only contains methods (Selectors) and other public methods for dealing with common objects (Header/Footer) or items that appear on every page. But ALSO common "helper" functions (Such as selectors/special functions for dealing with quirky react selects and so forth). Does it make sense to keep those here? I feel like maybe helper functions should maybe go in a "Helper" module or something (Currently Modules is empty, since im not sure what to put there)

  3. This maybe be specific to Capybara, as im not working with Selenium directly. But im handling sessions within each page object creation. I notice in most PoM examples with Selenium an instance of "driver" is created with initialization (Can be seen here: https://github.com/SeleniumHQ/selenium/wiki/PageObjects) specifically as:

public LoginPage(WebDriver driver) {
        this.driver = driver; 

From my understanding, this allows us to chain together methods correct? Capybara seems to work slightly different and we don't create drivers the same way but can do the same thing with sessions.

Currently I am initializing a @session variable and calling all the methods with that variable (and then returning self or a new page object depending on navigation). This works, however my question is I actually have this initialize method in my BASE page object, and then call super in the child page objects....does this work as I expect? Or should I be calling a specific initialize method for each child object?

My base page object looks like this with regards to session/driver(Child objects just call super so basically the same thing:

attr_reader :session

def initialize(session)
  @session = session
end

From which I call session.find/fill_in/etc... in the rest of the class. And then I actually create the session via (in the test scripts themselves):

let(:login_page) {LoginPage.new(Capybara.current_session)} 

Which I then use for all the it test cases. I create a new current_session/page object everytime I do a new describe test context.

  1. Sort of an extra side question....but how discrete should my methods be? For instance let's pretend In my Login Page object I have a Login method (Fills in the username and the password). Does it make any sense/benefit to also have methods for the username/password fill in's that the main login method calls? Or is that just overkill?

Apologies for wall of text!

2
  • 1
    The capybara session is the equivalent of the driver your sample code shows (the session wraps the driver) and you only want one session initialized per concurrent user you need in your test. Commented Nov 8, 2019 at 15:31
  • @ThomasWalpole I made an edit to show exactly what my code does in regards to the @session variable
    – Mercfh
    Commented Nov 8, 2019 at 17:33

1 Answer 1

6

To answer your questions:

  1. You are correct in that Page Objects are classes and inheriting from a base/parent class is good. When it comes to selectors, the most common option is to have them be variables. When I create frameworks like this, all my selectors are private variables so that only the Page Object class knows about those selectors. Depending on the language, using getter/setters for selectors is another good approach.

  2. Having a base class contain common variables or methods is good. However, using a base class to have your header, footer, and other common features can create a maintenance issue. It can also cause the base class to be too large.

Keep in mind, you want to keep your code DRY (don't repeat yourself) and SOLID as much as possible. Having your base class like you describe violates SOLID principle of "single responsibility". Instead, it would be recommended to have a separate Page Object for your header, another one for your footer, and a separate one for each feature that is common to multiple pages. Helper or utility methods should be in a helper or utility class.

Martin Fowler has a good article on Page Objects and says:

Despite the term "page" object, these objects shouldn't usually be built for each page, but rather for the significant elements on a page.

  1. While I haven't used Capybara before, having Page Objects handle WebDriver sessions doesn't sound right. The creation of the session should happen in the test/spec file, commonly in a "beforeAll" method. Ending the session can be used in an "afterAll" method. This is dependent on what unit test library is being used. The Page Object should only be aware of the web driver via an instance of it, as you mentioned.

  2. As for discrete methods -- I write methods that best resemble user actions or chain of actions. In your login example, yes, it's good to only have one "login" method and having separate "user" and "password" methods wouldn't make sense, since the user performs both input actions together. You can enhance this method by making the username and password parameters optional, so that if needed, you can use the same method to handle other test cases (like, user only inputs username and no password; or only the password and no username; or both empty).

Overall, it sounds like you've done a lot of research on this topic and are well on your way to creating a maintainable automation framework.

5
  • For your remark on #3, The page object passed the "current_session" into the class on object creation. So it looks something like this: let(:login_page) {LoginPage.new(Capybara.current_session)} Then the class object uses that @session variable to make calls. (Hopefully that's what you mean). So the test spec itself actually handles when the session is created, the page objects handle how the session is passed around (So I can chain methods for instance)
    – Mercfh
    Commented Nov 8, 2019 at 16:47
  • 1
    @Mercfh How you describe it here makes sense. Yep, this is what I meant. I was trying to answer in a language agnostic way, so there may be some language specifics in Ruby/Capybara that work differently, but the concepts should be similar, which it sounds like they are.
    – Lee Jensen
    Commented Nov 8, 2019 at 17:15
  • I guess im confused what you were worried I was doing? Im guessing handling the creation of sessions in the actual page objects? Because the page objects are passing along the sessions to different page objects within the page object themselves (Either by returning self or returning another different page object). But they aren't actually creating the session (The test script is in the pre-test setup).
    – Mercfh
    Commented Nov 8, 2019 at 17:25
  • 1
    Correct. "But im handling sessions within each page object creation." sounded to me like you were creating the sessions in the Page Object, which you've since clarified you are not. Page Objects passing along the sessions is the typical approach. Returning self or another Page Object is also typical. Overall, how you are handling sessions is right approach.
    – Lee Jensen
    Commented Nov 8, 2019 at 17:33
  • 1
    Awesome thanks!
    – Mercfh
    Commented Nov 8, 2019 at 17:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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