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