> Markdown version of [Creating Maintainable Tests using Page Objects](https://vaadin.com/docs/v10/testbench/testbench-maintainable-tests-using-page-objects). Section index: [llms.txt](https://vaadin.com/docs/v10/testbench/llms.txt)

# <a id="testbench.maintainable.pageobject"></a>Creating Maintainable Tests using Page Objects

The [Page Object Pattern](https://martinfowler.com/bliki/PageObject.html) is an abstraction commonly used when performing actions on a web page. The abstraction hides the implementation details (which elements/components are used, how can they be found on the page etc) from the test methods and allows the test methods to focus on the logic to test. The page object depends on how the page/view is implemented and offer high level methods representing actions that a real user could perform on the page. The separation enables the test method to be independent of the implementation details, so refactoring a view and moving components around only require updates to the page object (if any change is required at all) and not the individual tests.

> **Note:** The objects are tradititonally called *Page* objects even though they should not represent the whole page but rather a smaller part of it.

## <a id="testbench.maintainable.pageobject.defining"></a>Creating a Page Object

Regardless of the name, a *page object* in reality encapsulates a DOM element and is also sometimes called a *TestBench Element class*.

An element class must:

1. Extend `TestBenchElement`

2. Define an `@Element("tag-name")` annotation

The `@Element` annotation defines the tag name of the element which can be located by the element class. The tag name does not have to be unique as the element query user always defines what type of element she is looking for.

When creating a page object for your view, you should use the root tag of the view in the `@Element` annotation. For views created using Java, this might be e.g. `div` while for templates it is a custom element, e.g. `main-view`.

## <a id="testbench.maintainable.pageobject.template"></a>Page Objects for Templates

A page object for a template based login view can look like:

```java
@Element("login-view") // map to <login-view>
public class LoginViewElement extends TestBenchElement {

    protected TextFieldElement getUsernameField() {
        return $(TextFieldElement.class).id("username");
    }

    protected PasswordFieldElement getPasswordField() {
        return $(PasswordFieldElement.class).id("password");
    }

    protected ButtonElement getLoginButton() {
        return $(ButtonElement.class).id("login");
    }

    public void login(String username, String password) {
        getUsernameField().setValue(username);
        getPasswordField().setValue(password);
        getLoginButton().click();
    }
}
```

When mapping to a template, it’s typically enough to define the tag name as each template has its unique custom tag name.

## <a id="testbench.maintainable.pageobject.java"></a>Page Objects for Java Classes

For views created using Java, the tag name is not unique enough and the page object will find lots of unrelated elements unless you define more restrictions using the `@Attribute` annotation. The `@Attribute` annotation allows you to define additional restrictions using attribute values, in the same way as `attribute(name,value)` on an `ElementQuery`. `@Attribute` can be used for any attribute on the element but there are two attributes it is commonly used for: id and CSS class name.

Given a Java login view:

```java
public class LoginView extends Div {
```

and a login view element:

```java
@Element("div")
public class LoginViewElement extends TestBenchElement {
```

A query such as `$(LoginViewElement.class).first()` would find the first `<div>` on the page. To make the page object find only the `LoginView`, you can either define an `id`:

```java
public class LoginView extends Div {
    public LoginView() {
        setId("login-view");
    }
```

or add a `class name`:

```java
public class LoginView extends Div {
    public LoginView() {
        addClassName("login-view");
    }
```

The page object can then use the `id` as:

```java
@Element("div")
@Attribute(name = "id", value = "login-view")
public class LoginViewElement extends TestBenchElement {
```

or the `class name` as:

```java
@Element("div")
@Attribute(name = "class", contains = "login-view")
public class LoginViewElement extends TestBenchElement {
```

The rest of the page object would be the same in both cases (Template and Java class), the only difference is how you find the element you want to interact with.

> **Note:** You should use `contains` when you are matching `class` or similar multi value attributes, so that `login-view` matches even when there are multiple class names, e.g. `class="dark login-view active"`.

> **Tip:** An `@Attribute` `value` or `contains` property can be set to `Attribute.SIMPLE_CLASS_NAME` to make it match the simple class name of the page object class with any `Element` or `PageObject` suffix removed. As `@Attribute` annotations are inherited, you can add this on a base class for your elements.

> **Note:** All Vaadin component integrations for TestBench can also be considered *page objects* even though they only provide a high level API for a single component. There is no conceptual difference between creating elements for web components and elements for templates or classes representing a whole view.

## <a id="testbench.maintainable.pageobject.using"></a>Using a Page Object

To be able to use the helper methods from a page object, you need to get an instance of the page object. You use the standard `ElementQuery` methods to retrieve an instance of your page object, e.g. to handle login in a test you can do:

```java
public class LoginIT extends TestBenchTestCase {

   // Driver setup and teardown omitted

    @Test
    public void loginAsAdmin() {
        getDriver().open("http://localhost:8080");
        LoginViewElement loginView = $(LoginViewElement.class).first();
        loginView.login("admin@vaadin.com", "admin");
        // TODO Assert that login actually happened
    }
}
```

## <a id="testbench.maintainable.pageobject.chaining"></a>Chaining Page Objects

Whenever an action on a page object results in the user being directed to another view, it is good practice to find an instance of the page object for the new view and return that. This allows test methods to chain page object calls and continue to perform actions on the new view.

For the `LoginViewElement` we could accomplish this by updating the `login` method:

```java
public MainViewElement login(String username, String password) {
    getUsernameField().setValue(username);
    getPasswordField().setValue(password);
    getLoginButton().click();
    // Find the page object for the main view the user ends up on
    // onPage() is needed as MainViewElement is not a child of LoginViewElement.
    return $(MainViewElement.class).onPage().first();
}
```

> **Tip:** When the login view finds the main view element, it automatically validates that the login succeeded or the main view will not be found.

A test method can now do:

```java
@Test
public void mainViewSaysHello() {
    getDriver().open("http://localhost:8080");
    LoginViewElement loginView = $(LoginViewElement.class).first();
    MainViewElement mainView = loginView.login("admin@vaadin.com", "admin");
    Assert.assertEquals("Hello", mainView.getBanner());
}
```

You can find a fully functional page object based test example in the demo project at <https://github.com/vaadin/testbench-demo/tree/master/src/test/java/com/vaadin/testbenchexample/pageobjectexample>.

### <a id="testbench.maintainable.pageobject.extending"></a>Extending a Page Object

If you want to add functionality to an existing element, you can extend the original element class and add more helper methods, e.g.

```java
public class MyButtonElement extends ButtonElement {

   public void pressUsingSpace() {
     ....
   }
}
```

You can then use your new element by replacing

```java
ButtonElement button = $(ButtonElement.class).id("ok");
...
```

by

```java
MyButtonElement button = $(MyButtonElement.class).id("ok");
button.pressUsingSpace();
```
