1. Introduction

In this article we look at how to interact with web pages in the Serenity Journey pattern. Beyond the standard shortcuts that come with Serenity (such as the Serenity PageObject base class), the Serenity Journey Pattern implementation provides a number of shortcuts to streamline your tests further.

2. A simple Journey Pattern test

The following test is a simple example of a Serenity Journey Pattern test:

private CurrentFilter theCurrentFilter = new CurrentFilter();

@Test
public void should_indicate_what_filter_is_currently_being_used() {

    givenThat(james).wasAbleTo(OpenTheApplication.onTheHomePage());

    when(james).wasAbleTo(AddTodoItems.called("Walk the dog", "Put out the garbage"));
    then(james).should(seeThat(theCurrentFilter, is(All)));

    when(james).attemptsTo(FilterItems.byStatus(Active));
    then(james).should(seeThat(theCurrentFilter, is(Active)));
}

Let’s see how we interact with the web application in this test.

In Serenity tests, you usually interact with a web page in two places: 1. in the Action classes (such as FilterItems.byStatus(Active)), where you actively do something to the page (click a button, enter a value in a field, etc), and 2. in the Question classes (such as CurrentFilter), where you observe the state of the application (read a value, check if a button is read-only, etc).

3. Interacting with the page in Action classes

Serenity describes how a user interacts with an application in terms of three layers: * Goals that represent the high level business objectives; * Tasks that describe the high-level steps the user takes to achieve these goals; and * Actions that describe how the user interacts with the application to perform each step.

You can of course define your own domain-specific actions, but Serenity provides a number of bundled ones for interacting with web pages that are designed to help you author your tests faster. These include actions that let you open the browser on a particular URL, click on things, enter values into fields, select values in drop-downs, and so on.

3.1. Opening a URL

Let’s step through the test presented above to see some examples. The first way we interact with a web application is to open a web page to the desired URL.

This happens at the start of the test, using the OpenTheApplication task:

givenThat(james).wasAbleTo(OpenTheApplication.onTheHomePage());

The OpenTheApplication task is a simple task that opens the browser to the home page of the TodoMVC application:

public class OpenTheApplication implements Task {

    private ApplicationHomePage applicationHomePage;                    (1)

    public static OpenTheApplication onTheHomePage() {
        return instrumented(OpenTheApplication.class);
    }

    @Step("{0} opens the application on the home page")
    public <T extends Actor> void performAs(T actor) {
        actor.attemptsTo(Open.browserOn().the(applicationHomePage));    (2)
    }
}
1 Page Objects are automatically instantiated inside Journey Pattern tasks
2 Use the Open action class to open the browser to the default URL for the applicationHomePage Page Object.

The ApplicationHomePage extends the Serenity PageObject class as a convenient way to provide entry points to the application, using the @DefaultUrl annotation as shown here:

@DefaultUrl("http://todomvc.com/examples/angularjs/#/")
public class ApplicationHomePage extends PageObject {}

3.2. Clicking on elements

To click on a link, a button, or any other element, you can use the Click action object. An example is shown here in the ClearCompletedItems class:

public class ClearCompletedItems implements Task {

    @Step("{0} clears all the completed items")
    public <T extends Actor> void performAs(T actor) {
        actor.attemptsTo(Click.on(ClearCompleted.BUTTON));
    }
}

The ClearCompleted class is the Journey Pattern equivalent of a Page Object - a simple class that knows how to locate a number of related web elements. The best way to locate an element is to use the Target class, which lets you provide both a human-readable name and a (CSS or XPath) selector:

public class ClearCompleted {
    public static Target BUTTON = Target.the("Clear completed button")
                                        .locatedBy("#clear-completed");
}

3.3. Entering values into fields

You can also use the Serenity Action classes to enter values into a field. The AddATodoItem task, shown below, is a good example of how this is done.

when(james).attemptsTo(AddATodoItem.called("Buy some milk"));

The implementation of this class uses the Enter action to enter a value into a field, and then press the RETURN key:

public class AddATodoItem implements Task {

    private final String thingToDo;

    protected AddATodoItem(String thingToDo) { this.thingToDo = thingToDo; }

    @Step("{0} adds a todo item called #thingToDo")
    public <T extends Actor> void performAs(T theActor) {
        theActor.attemptsTo(
                Enter.theValue(thingToDo)                                               (1)
                     .into(NewTodoForm.NEW_TODO_FIELD)                                  (2)
                     .thenHit(Keys.RETURN)                                              (3)
        );
    }

    public static AddATodoItem called(String thingToDo) {
        return Instrumented.instanceOf(AddATodoItem.class).withProperties(thingToDo);
    }
}
1 What value are we Entering
2 What field are we entering it into
3 (Optional) We can also hit one or more keys afterwards

3.4. Selecting values in drop-downs

You can select an entry in a dropdown by using the SelectFromOptions class, which lets you select by value, visible text or index:

theActor.attemptsTo(SelectFromOptions.byVisibleText("Red")
                                     .from(ClientDetails.FAVORITE_COLOR));

4. Reading values

The other way to interact with a web page is to observe the state of the page. In the Journey Pattern implementation in Serenity, this is generally done in a Question, or as a precondition for a task or action.

4.1. UI Interaction classes

When you implement Question classes, you often need to query the web page. You can do this in several ways. For example suppose we want to be able to write something like this:

then(james).should(seeThat(theCurrentFilter, is(Active)));                          (1)
1 Active is an enum value from the TodoStatusFilter class

One way to do this might look like the following:

@Subject("the displayed todo items")
public class CurrentFilter implements Question<TodoStatusFilter> {

    @Override
    public TodoStatusFilter answeredBy(Actor actor) {
        String selectedValue = BrowseTheWeb.as(actor)
                                           .findBy("#filters li .selected")         (1)
                                           .getText();
        return TodoStatusFilter.valueOf(selectedValue);                             (2)
    }
}
1 Look up the field using a CSS selector
2 Convert the selected value to an enum

We could also use the UI Action classes bundled with Serenity to simplify this code to the following:

@Subject("the displayed todo items")
public class CurrentFilter implements Question<TodoStatusFilter> {

    @Override
    public TodoStatusFilter answeredBy(Actor actor) {
        return Text.of(FilterSelection.SELECTED_FILTER)     (1)
                   .viewedBy(actor)                         (2)
                   .asEnum(TodoStatusFilter.class);         (3)
    }
}
1 Read the text value from the SELECTED_FILTER field
2 As viewed by the current actor
3 And convert it to the TodoStatusFilter enum

This saves typing and makes the intent of the code clearer. UI Action classes in the net.serenitybdd.screenplay.questions package let you access almost anything visible on the web page, with direct mappings for most of the getter functions of the WebElementState class, including: Text: Return the text value attribute of a field Value: Return the value attribute of a field SelectedStatus: Indicate whether a checkbox is selected SelectedValue: Get the selected value in a drop-down list SelectedOptions: Get the list of selected options in a drop-down list CSSValue: Get the value of a given CSS attribute Visibiliy: Indicate whether a checkbox is visible

You can also convert the retrieved values to other types, such as numbers, dates or enums. For example, the following code would return the retrieved value in the form of a Joda DateTime object:

return Text.of(ClientPage.DATE_OF_BIRTH).viewedBy(actor).asDate("dd/MM/yyyy")

If a target matches more than one element, you can also return lists of values, by using the asList() method:

return Text.of(ClientPage.FAVORITE_COLOR).viewedBy(actor).asList()

You can also convert the returned results to a list of enums, e.g.

return Text.of(ClientPage.FAVORITE_COLOR).viewedBy(actor).asListOf(Color.class)

4.2. Web state matchers

The WebElementStateMatchers class provides a number of Hamcrest matchers that you can use in your should() methods, for example:

dana.should(seeThat(the(NewTodoForm.NEW_TODO_FIELD)), isVisible()));
dana.should(seeThat(the(NewTodoForm.NEW_TODO_FIELD)), isEnabled()));
dana.should(seeThat(the(NewTodoForm.NEW_TODO_FIELD)), containsText("Feed the cat")));

You can also enhance these with domain-specific exceptions using the orComplainWith() method (see Semantic Exceptions for more details):

theActor.should(seeThat(the(deleteButtom), isEnabled())
        .orComplainWith(DeleteButtonShouldBeEnabledException.class));

5. Conclusion

The Serenity Journey pattern comes bundled with a large number of Action and Question classes that you can use to write your acceptance tests more quickly.