Skip to content

Implementation Notes & PageFactory (Minor Fixes) #1394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,15 @@ requests from the test.

## Page Component Objects
A page object does not necessarily need to represent all the parts of a
page itself. The same principles used for page objects can be used to
create "Page _Component_ Objects" that represent discrete chunks of the
page and can be included in page objects. These component objects can
page itself. This was [noted by Martin Fowler](https://martinfowler.com/bliki/PageObject.html#footnote-panel-object) in the early days, while first coining the term "panel objects".

The same principles used for page objects can be used to
create "Page _Component_ Objects", as it was later called, that represent discrete chunks of the
page and **can be included in page objects**. These component objects can
provide references to the elements inside those discrete chunks, and
methods to leverage the functionality provided by them.
methods to leverage the functionality or behavior provided by them.

For example, a Product page has multiple products.
For example, a Products page has multiple products.

```html
<!-- Products Page -->
Expand Down Expand Up @@ -242,7 +244,7 @@ Each product is a component of the Products page.
</div>
```

The Product page HAS-A list of products. This relationship is called Composition. In simpler terms, something is _composed of_ another thing.
The Products page HAS-A list of products. This object relationship is called Composition. In simpler terms, something is _composed of_ another thing.

```java
public abstract class BasePage {
Expand Down Expand Up @@ -325,7 +327,7 @@ So now, the products test would use the page object and the page component objec
public class ProductsTest {
@Test
public void testProductInventory() {
var productsPage = new ProductsPage(driver);
var productsPage = new ProductsPage(driver); // page object
var products = productsPage.getProducts();
assertEquals(6, products.size()); // expected, actual
}
Expand All @@ -336,7 +338,7 @@ public class ProductsTest {

// Pass a lambda expression (predicate) to filter the list of products
// The predicate or "strategy" is the behavior passed as parameter
var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack"));
var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack")); // page component object
var bikeLight = productsPage.getProduct(p -> p.getName().equals("Bike Light"));

assertEquals(new BigDecimal("29.99"), backpack.getPrice());
Expand All @@ -354,8 +356,7 @@ components used throughout the site (e.g. a navigation bar), then it
may improve maintainability and reduce code duplication.

## Other Design Patterns Used in Testing
There are other design patterns that also may be used in testing. Some use a
Page Factory for instantiating their page objects. Discussing all of these is
There are other design patterns that also may be used in testing. Discussing all of these is
beyond the scope of this user guide. Here, we merely want to introduce the
concepts to make the reader aware of some of the things that can be done. As
was mentioned earlier, many have blogged on this topic and we encourage the
Expand All @@ -366,11 +367,11 @@ reader to search for blogs on these topics.

PageObjects can be thought of as facing in two directions simultaneously. Facing toward the developer of a test, they represent the **services** offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page) It's simplest to think of the methods on a Page Object as offering the "services" that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system. Amongst the services it offers are the ability to compose a new email, choose to read a single email, and list the subject lines of the emails in the inbox. How these are implemented shouldn't matter to the test.

Because we're encouraging the developer of a test to try and think about the services they're interacting with rather than the implementation, PageObjects should seldom expose the underlying WebDriver instance. To facilitate this, methods on the PageObject should return other PageObjects. This means we can effectively model the user's journey through our application. It also means that should the way that pages relate to one another change (like when the login page asks the user to change their password the first time they log into a service when it previously didn't do that), simply changing the appropriate method's signature will cause the tests to fail to compile. Put another way; we can tell which tests would fail without needing to run them when we change the relationship between pages and reflect this in the PageObjects.
Because we're encouraging the developer of a test to try and think about the services they're interacting with rather than the implementation, PageObjects should seldom expose the underlying WebDriver instance. To facilitate this, **methods on the PageObject should return other PageObjects**. This means we can effectively model the user's journey through our application. It also means that should the way that pages relate to one another change (like when the login page asks the user to change their password the first time they log into a service when it previously didn't do that), simply changing the appropriate method's signature will cause the tests to fail to compile. Put another way; we can tell which tests would fail without needing to run them when we change the relationship between pages and reflect this in the PageObjects.

One consequence of this approach is that it may be necessary to model (for example) both a successful and unsuccessful login; or a click could have a different result depending on the app's state. When this happens, it is common to have multiple methods on the PageObject:

```
```java
public class LoginPage {
public HomePage loginAs(String username, String password) {
// ... clever magic happens here
Expand All @@ -388,7 +389,7 @@ public class LoginPage {

The code presented above shows an important point: the tests, not the PageObjects, should be responsible for making assertions about the state of a page. For example:

```
```java
public void testMessagesAreReadOrUnread() {
Inbox inbox = new Inbox(driver);
inbox.assertMessageWithSubjectIsUnread("I like cheese");
Expand All @@ -398,7 +399,7 @@ public void testMessagesAreReadOrUnread() {

could be re-written as:

```
```java
public void testMessagesAreReadOrUnread() {
Inbox inbox = new Inbox(driver);
assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
Expand All @@ -421,7 +422,7 @@ Finally, a PageObject need not represent an entire page. It may represent a sect

## Example

```
```java
public class LoginPage {
private final WebDriver driver;

Expand Down Expand Up @@ -490,10 +491,4 @@ public class LoginPage {
return submitLogin();
}
}

```


## Support in WebDriver

There is a PageFactory in the support package that provides support for this pattern and helps to remove some boiler-plate code from your Page Objects at the same time.
```
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,15 @@ requests from the test.

## Page Component Objects
A page object does not necessarily need to represent all the parts of a
page itself. The same principles used for page objects can be used to
create "Page _Component_ Objects" that represent discrete chunks of the
page and can be included in page objects. These component objects can
page itself. This was [noted by Martin Fowler](https://martinfowler.com/bliki/PageObject.html#footnote-panel-object) in the early days, while first coining the term "panel objects".

The same principles used for page objects can be used to
create "Page _Component_ Objects", as it was later called, that represent discrete chunks of the
page and **can be included in page objects**. These component objects can
provide references to the elements inside those discrete chunks, and
methods to leverage the functionality provided by them.
methods to leverage the functionality or behavior provided by them.

For example, a Product page has multiple products.
For example, a Products page has multiple products.

```html
<!-- Products Page -->
Expand Down Expand Up @@ -243,7 +245,7 @@ Each product is a component of the Products page.
</div>
```

The Product page HAS-A list of products. This relationship is called Composition. In simpler terms, something is _composed of_ another thing.
The Products page HAS-A list of products. This object relationship is called Composition. In simpler terms, something is _composed of_ another thing.

```java
public abstract class BasePage {
Expand Down Expand Up @@ -326,7 +328,7 @@ So now, the products test would use the page object and the page component objec
public class ProductsTest {
@Test
public void testProductInventory() {
var productsPage = new ProductsPage(driver);
var productsPage = new ProductsPage(driver); // page object
var products = productsPage.getProducts();
assertEquals(6, products.size()); // expected, actual
}
Expand All @@ -337,7 +339,7 @@ public class ProductsTest {

// Pass a lambda expression (predicate) to filter the list of products
// The predicate or "strategy" is the behavior passed as parameter
var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack"));
var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack")); // page component object
var bikeLight = productsPage.getProduct(p -> p.getName().equals("Bike Light"));

assertEquals(new BigDecimal("29.99"), backpack.getPrice());
Expand All @@ -355,8 +357,7 @@ components used throughout the site (e.g. a navigation bar), then it
may improve maintainability and reduce code duplication.

## Other Design Patterns Used in Testing
There are other design patterns that also may be used in testing. Some use a
Page Factory for instantiating their page objects. Discussing all of these is
There are other design patterns that also may be used in testing. Discussing all of these is
beyond the scope of this user guide. Here, we merely want to introduce the
concepts to make the reader aware of some of the things that can be done. As
was mentioned earlier, many have blogged on this topic and we encourage the
Expand All @@ -367,11 +368,11 @@ reader to search for blogs on these topics.

PageObjects can be thought of as facing in two directions simultaneously. Facing toward the developer of a test, they represent the **services** offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page) It's simplest to think of the methods on a Page Object as offering the "services" that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system. Amongst the services it offers are the ability to compose a new email, choose to read a single email, and list the subject lines of the emails in the inbox. How these are implemented shouldn't matter to the test.

Because we're encouraging the developer of a test to try and think about the services they're interacting with rather than the implementation, PageObjects should seldom expose the underlying WebDriver instance. To facilitate this, methods on the PageObject should return other PageObjects. This means we can effectively model the user's journey through our application. It also means that should the way that pages relate to one another change (like when the login page asks the user to change their password the first time they log into a service when it previously didn't do that), simply changing the appropriate method's signature will cause the tests to fail to compile. Put another way; we can tell which tests would fail without needing to run them when we change the relationship between pages and reflect this in the PageObjects.
Because we're encouraging the developer of a test to try and think about the services they're interacting with rather than the implementation, PageObjects should seldom expose the underlying WebDriver instance. To facilitate this, **methods on the PageObject should return other PageObjects**. This means we can effectively model the user's journey through our application. It also means that should the way that pages relate to one another change (like when the login page asks the user to change their password the first time they log into a service when it previously didn't do that), simply changing the appropriate method's signature will cause the tests to fail to compile. Put another way; we can tell which tests would fail without needing to run them when we change the relationship between pages and reflect this in the PageObjects.

One consequence of this approach is that it may be necessary to model (for example) both a successful and unsuccessful login; or a click could have a different result depending on the app's state. When this happens, it is common to have multiple methods on the PageObject:

```
```java
public class LoginPage {
public HomePage loginAs(String username, String password) {
// ... clever magic happens here
Expand All @@ -389,7 +390,7 @@ public class LoginPage {

The code presented above shows an important point: the tests, not the PageObjects, should be responsible for making assertions about the state of a page. For example:

```
```java
public void testMessagesAreReadOrUnread() {
Inbox inbox = new Inbox(driver);
inbox.assertMessageWithSubjectIsUnread("I like cheese");
Expand All @@ -399,7 +400,7 @@ public void testMessagesAreReadOrUnread() {

could be re-written as:

```
```java
public void testMessagesAreReadOrUnread() {
Inbox inbox = new Inbox(driver);
assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
Expand All @@ -422,7 +423,7 @@ Finally, a PageObject need not represent an entire page. It may represent a sect

## Example

```
```java
public class LoginPage {
private final WebDriver driver;

Expand Down Expand Up @@ -491,10 +492,4 @@ public class LoginPage {
return submitLogin();
}
}

```


## Support in WebDriver

There is a PageFactory in the support package that provides support for this pattern and helps to remove some boiler-plate code from your Page Objects at the same time.
```
Loading