Serenity BDD

April 24, 2020

I've used Serenity BDD for testing a web application, but I did it in the context of a defined way of working. I've recently had the time to step back and try it from scratch, and this post documents how it worked.

Gherkin Syntax

Gherkin Syntax is used to define a test using Given/When/Then style syntax, for example

Feature: Todo List
  Exercise the functionality of the todo list

  Scenario: Can see a list of todos
    Given I am on the todos page
    Then I should be able to see a list of todo items

  Scenario: Can add to the list of todos
    Given I am on the todos page
    When I enter "a" into the title and "b" into the description
    And then I click Create
    Then "a" should be added to the todo list

  Scenario: Can see todo detail
    Given I am on the todos page
    When I click on todo title "a"
    Then I can see the todo description "b"

The lives in a file in src/test/resources/features, in my case it was called todo.feature.

Cucumber

Cucumber is used to match these Gherkin statements using regular expressions and/or parameter matching.

@Given("I am on the todos page")
public void i_am_on_the_todos_page() {
    this.toDoPage.open();
}

@Then("I should be able to see a list of todo items")
public void i_should_be_able_to_see_a_list_of_todo_items() {
    assertTrue(this.toDoPage.toDoListVisible());
}

@When("I enter {string} into the title and {string} into the description")
public void i_enter_into_the_title_and_into_the_description(String title, String description) {
    this.toDoPage.enterNewTodo(title, description);
}

This code was in a ToDoStepDefinitions.java file.

Page Objects

To keep things simple for my example, I used a single Page object referenced directly from the cucumber step definitions. There is support for Step libraries between these two levels that I haven't dug very deep into yet. For something this simple, I don't think they're required.

public class ToDoPage extends PageObject {
    @FindBy(css = "ul.heroes")
    private WebElementFacade toDoListWithItems;

    @FindBy(css = "ul.heroes:first-child")
    private WebElementFacade firstToDoItem;

    @FindBy(css = "ul.heroes:first-child a.delete")
    private WebElementFacade firstToDoItemDeleteLink;

    @FindBy(css = "ul.heroes:first-child a.title")
     private WebElementFacade firstToDoItemTitleLink;

    @FindBy(id = "todo-description")
    private WebElementFacade selectedToDoItemDescription;

    @FindBy(id = "todo-new-title")
    private WebElementFacade newTodoTitleBox;

    @FindBy(id = "todo-new-description")
    private WebElementFacade newTodoDescriptionBox;

    @FindBy(id = "todo-new-create")
    private WebElementFacade newTodoCreateButton;

    @FindBy(id = "latest_id")
    private WebElementFacade newToDoId;

    public boolean toDoListVisible() {
        return this.toDoListWithItems.isVisible();
    }

    public void enterNewTodo(String title, String description) {
        this.newTodoTitleBox.typeAndTab(title);
        this.newTodoDescriptionBox.typeAndTab(description);
    }

    public void clickCreate() {
        this.newTodoCreateButton.click();

    }

    public WebElement findToDoByTitle(String title) {
        List<WebElement> listOfTodoItems = this.toDoListWithItems.findElements(By.cssSelector("a.title"));
        for (WebElement eachTodoItem : listOfTodoItems) {
            if (eachTodoItem.getText().contentEquals(title)) {
                return eachTodoItem;
            }
        }
        return null;
    }

    public WebElement findToDoById(String id) {
        WebElement matchingToDo = this.toDoListWithItems.findElement(By.id("TODO-" + id)).findElement(By.cssSelector("a.title"));
        return matchingToDo;
    }

    public boolean descriptionIsVisible(String description) {
        return description.equals(this.selectedToDoItemDescription.getText());
    }

    public String getNewTodoId() {
        return this.newToDoId.getText();
    }

Notice that the attributes match things from the DOM, either by using css or an id.

Key imports for this code are:

import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import net.serenitybdd.core.annotations.findby.FindBy;
import net.serenitybdd.core.pages.PageObject;
import net.serenitybdd.core.pages.WebElementFacade;

Test Runner

The only other file that was needed was the test runner, which can be used to control which features are run for a given test:

package starter;

import io.cucumber.junit.CucumberOptions;
import net.serenitybdd.cucumber.CucumberWithSerenity;
import org.junit.runner.RunWith;

@RunWith(CucumberWithSerenity.class)
@CucumberOptions(
        plugin = {"pretty"},
        features = "src/test/resources/features"
)
public class CucumberTestSuite {}

References

I started my project using the example code provided at https://github.com/serenity-bdd/serenity-cucumber-starter