Should elements ever be made available outside of a page object?
Asked Answered
M

2

8

This is a question I cannot find a definitive source on and am hoping to get some answers based on users previous experience mainly with explanations as to why a certain approach DID NOT work out.

I am using webdriver for automation via Protractor and am having a debate on whether or not page elements should ever be made available outside of page object itself. After researching it appears that there are a few different approaches people take and I cannot fully grasp the long term implications of each.

I've seen the following different implementations of the page object model:


Locators are declared in the page object and exported

This is my least favorite approach as it means element are actually being identified in the test. This seems like a bad standard to set as it could encourage automaters to use new locators, not from the page object, directly in the application. Also any which require any dynamic information cannot be directly set when PO is initialized require further editing.

pageobject.js

export default class HomePage {
    constructor() {
        this.passwordField = '#password';
        this.usernameField = '#user';
    }
}

test.js

const homePage = new HomePage();
$(homePage.usernameField ).sendKeys('admin');
$(homePage.passwordField ).sendKeys('password');

Elements declared in page object and exported, locators not

pageobject.js

export default class HomePage {
    constructor() {
        this.passwordField = $('#password');
        this.usernameField = $('#user');
    }
}

test.js

const homePage = new HomePage();
homePage.usernameField.sendKeys('admin);
homePage.passwordField.sendKeys('password);

Elements declared in Page Object and only used directly within the page object, only methods exported

This is the approach I have used in the past and we ended up with many, many functions. For instance we had setUsename(), getCurrentUsername(), getUsernameAttibute(), verifyUsernameExists() and same for password element and many other elements. Our page object became huge so I don't feel like this is the best approach any longer. One of the advantage however is that our tests look very clean and are very readable.

pageobject.js

export default class HomePage {
    constructor() {
        var passwordField= $('#password');
        var usernameField = $('#user');
    }

    setUserName(name){
       username.sendKeys(name);
    };

    setPassword(password){
       passwordField.sendKeys(password);
    };
}

test.js

const homePage = new HomePage();
homePage.setUsername('admin');
homePage.setPassword('123');

I'm very interested to get some feedback on this so hopefully you can take the time to read.

Motherland answered 29/7, 2019 at 9:41 Comment(1)
I like both answers submitted so far but I'm adding a bounty to try generate some other perspectives.Motherland
U
6

I prefer and believe that the last approach is the best.

Keeping aside the fact that we are talking about automation, any good/great software has following traits.

  • It is composed of individual modules/pieces/components
  • Each individual module/piece/component is cohesive in terms of data/information (selector, webdriver API calls in case of automation) specific to its API/methods to interact with the data.

The last approach provides just that with the added benefit of test cleanliness that you pointed out.

However, most of the times, for whatever reasons, we tend to ignore the modularity and make the existing POs bloated. This i have seen and was part of. So, in way, POs getting bloated is not because of the approach but the way automators/testers/developers being conscious to keep POs modular, composed and simpler. This is true whether it is about POs or the application functional code

Boated POs:

Specific to the problem of bloated POs, check if you can separate out the common elements out of the POs. For ex. Header, Footer, Left Nav, Right Nav etc are common across pages. They can be separated out and the POs can be composed of those individual pieces/sections.

Even within the main content, separate common content (if its across two or more pages if not all) into their own component API if its logical and can be reused across pages.

It is normal that the automation specs perform extensive regression for ex. an element is a password field, length of the text box is so and so etc. In such cases it doesn't make sense to add methods for every single spec use case (or expect). Good thing is, here also the commonality takes the centerstage. Basically, provide API that is used across specs and not if its used in one spec, literally.

Take for ex. a password field should be masked. Unlikely that you would want to test that in multiple spec files. Here, either we can add a method for it in LoginPO like isPasswordMasked() or we can let the password field accessible from LoginPO and the spec do the actual check for password type field. By doing this, we are still letting LoginPO in control of password field information that matters to other API (login(), logout() etc) i.e only PO knows how and where to get that password element. With the added advantage of pushing spec testing to spec file.

POs that expect/assert

At any point, its not a good idea to make any testing (or expect) to be part of the PO API. Reason(s):

  • The PO and its API is reusable across suites and should be easy for anyone to look at it and understand. Their primary responsibility is to provide common API.
  • They should be as lean as possible (to prevent bloating up)
  • More importantly, browser automation is slower inherently. If we add test logic to the POs and its API methods, we will only make it slower.

FWIW, i have not come across any web page that mandates a bloated API.

Exposing Elements in POs:

It depends on the use case i believe. May be an element that is used in only one spec can be a base case to expose. That said, in general, the idea is that the specs should be readable for the tester/developer AND for those who look at them later. Whether its done with meaningful element variable name or a method name is largely a preference at best. On the other hand, if an element is meant to have a bit of involved interaction (for ex hover open menu link) its definitely a candidate for exposing through API alone.

Hope that adds some clarification!

Usually answered 29/7, 2019 at 13:57 Comment(2)
I like the approach you have mentioned about spitting up page object into different sections. Do you know of any example frameworks or documentation regarding this approach where I could read more about it? I'm leaving this question open for a while to hopefully generate some other perspectivesMotherland
The approached I am taking is based mainly off this answer and example. We will be breaking page object down into component object, each page object will initialize each component object in it's constructor. No elements will be made available outside each page/component object itself. We will a helper class file which each page/component object will extend so that the helpers can access the elements. I'm confident in this approach and interested to see how it holds up when we start on a lot of automationMotherland
M
5

The last way is the correct way to implement page objects. The idea behind the page object is that it hides the internals of the page and provides a clean API for a script to call to perform actions on the page. Locators and elements should not be exposed. Anything you need to do to the page should be exposed by a public method.

One way you can avoid a getter and setter for each field on the page is to consolidate methods. Think about actions that the user would take on the page. Rather than having .setUsername(), .setPassword(), and .clickLoginButton() methods, you should just have a login() method that takes the username and password as parameters and does all the work to log in for you.

References

Martin Fowler is generally considered the inventor of the "page object" concept but others coined the name "page object". See his description of the page object.

Selenium's documentation on page objects.

Monohydroxy answered 29/7, 2019 at 19:0 Comment(6)
The only issue I have this is if we are using the third approach it won't be sufficient to have simply a login function as we will have functional tests around the username and password fields. For instance we might need to verify that password is masked and therefore required getAttributesForPassword() or we may need to verify username accepts underscores so getUsername() would be required. If there are many such validations (which there were in my case) the page object becomes many 100's of lines of code with many methods.Motherland
Agreed. There are times when what you describe is necessary. That doesn't mean that it's still not the best approach. If those 100s of lines of code don't go in the page object for that page, where should they go?Monohydroxy
You can likely boil many of the positive and negative tests into loginAndExpectSuccess() and loginAndExpectFailure(). Pass your usernames with underscores to the success method and you should still be able to log in. Pass your usernames with unsupported characters and expect to be denied as well as if you pass in the wrong credentials or any other fail scenario.Monohydroxy
@Monohydroxy Started to respond here but updated the response as it was getting lengthier. Please see my second Update in the answer.Usually
@RakeshKumarCherukuri It's widely debated as to whether validations should be part of page objects or not so there is no standard there. Having said that, I wasn't referring to validations in the two "expect" methods, I was referring to flows. A successful login will take you to some landing page where an unsuccessful login will stay on the login page with an error message.Monohydroxy
@Monohydroxy Got it. The API methods should be able to support negative test scenarios. Makes sense.Usually

© 2022 - 2025 — McMap. All rights reserved.