Introduction to Page Objects

After we have finished this lesson, we:

  • Know what a page object is.
  • Understand why we should use page objects.
  • Know how we can create good page objects.

What is a Page Object?

Martin Fowler specifies the term page object as follows:

A page object wraps an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements without digging around in the HTML.

In other words, a page object hides the structure of the HTML page or fragment from our end-to-end tests and provides methods that allow our tests to either interact with the page or find information from it.

Page objects help us to write end-to-end tests that are easy to read, write, and maintain because of these two reasons:

  • Page objects help us to eliminate duplicate code because our test suite has only one place that has knowledge of the structure of the specific HTML page or page fragment.
  • If we give application-specific names for the methods of our Page objects, we can create a domain-specific language for our end-to-end tests.

Next, we will take a look at “best practices” that help us to write good page objects.

The “Best Practices” of Page Objects

There are three “best practices” that help us to write clean page objects. These best practices are:

First, even though these objects are called page objects, we shouldn’t create one page object per HTML page. Instead, we should divide the HTML page into significant sections and create one page object per section. For example, a task list page object could contain a list of task list item page objects.

Also, when we divide an HTML page into page objects, we should ignore the parts of the HTML that are used only to structure the user interface and model the structure of the HTML page in a way that makes sense to the users of our application.

If we follow this technique, we can create a domain-specific language for our end-to-end tests. This allows us to write end-to-end tests that highlight the essence of our test cases.

Second, the methods of a page object should return either “basic” types or other page objects. Typically a page object has three types of methods:

  • The methods that find information from the current page should return either primitive data types or other “basic” objects such as String, Integer, or LocalDate objects.
  • The methods that allow us to access other sections of the current page, should return other page objects.
  • The methods that navigate to another page or reload the current page, should return a new page object that represents the loaded page.

If we follow this rule, we can enjoy from these two benefits:

  • Most of the time we don’t have to expose the underlying WebDriver instance to our end-to-end tests. This means that we can pretty much ignore the technical API of the WebDriver interface and write our end-to-end tests by using the domain-specific language that is defined by our page objects.
  • If we change the navigation path of a feature, we can simply change the signature of the appropriate method and see immediately which test classes are affected by the change because they don’t compile anymore.
One consequence of this rule is that if the same action has different results, we have to model these results by using different methods. For example, if a different page is rendered after a successful and a failed login, we have to add two login methods to the page object that represents the login page.

Third, page objects should not make any assertions. As we remember, Page objects are responsible for providing a domain-specific language that allows us interact with the underlying page and find information from it.

On the other hand, assertions help to specify the conditions that must be true after our test method has been run. In other words, assertions help us to specify the business rules of our application, and that’s why I think that we should add our assertions to our test methods. This helps us to separate the responsibilities of our page objects and test methods.

If we add these assertions to our page objects, we end up mixing the presentation logic with the business logic of our application. In other words, we write “bloated” page objects that have too many responsibilities.

Also, some people argue that if we add our assertions to our page objects, we can eliminate duplicate assertion logic and provide better error messages. I admit that this is true, but we can achieve the same benefits by using a proper assertion library such as AssertJ that allows us to create custom assertions. AssertJ allows us to write clean assertions and it doesn’t force us to mix the presentation logic with business logic.

This is a bit controversial topic and some people think that page objects should have assertions. Take a look at the following articles and make up your own mind. That being said, the examples of this course don’t add assertions to page objects.

Additional Reading:

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us five things:

  • A page object hides the structure of an HTML page or page fragment from our end-to-end tests.
  • Page objects allow us to eliminate duplicate code and help us to create a domain-specific language for our end-to-end tests.
  • We shouldn’t create ONLY one page object per HTML page. Instead, we should divide the HTML page into significant sections and create one page object per section.
  • The methods of a page object should return either “basic” types or other page objects.
  • We shouldn’t add assertions to our page objects because we shouldn’t mix presentation logic with business logic.