PoM Design/Architecture questions



  • 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!



  • 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.



Suggested Topics

  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2
  • 2