Introduction to Serenity BDD
Serenity BDD (formerly Thucydides) is a powerful open-source library that enhances behavior-driven development by providing exceptional reporting and living documentation capabilities. Built on top of popular BDD (as discussed in BDD: From Requirements to Automation) frameworks like Cucumber and JBehave, Serenity transforms test automation into comprehensive, stakeholder-friendly documentation.
This guide explores Serenity’s integration with BDD (as discussed in Cucumber BDD Automation: Complete Guide to Behavior-Driven Development Testing) frameworks, the Screenplay pattern for maintainable test automation, and its industry-leading reporting capabilities.
Living Documentation: Tests as Documentation
Serenity automatically generates rich, narrative reports that serve as living documentation of your system’s behavior.
Project Setup
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>net.serenity-bdd</groupId>
(as discussed in [Gauge Framework Guide: Language-Independent BDD Alternative to Cucumber](/blog/gauge-framework-guide)) <artifactId>serenity-core</artifactId>
<version>4.0.30</version>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-cucumber</artifactId>
<version>4.0.30</version>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-screenplay</artifactId>
<version>4.0.30</version>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-screenplay-webdriver</artifactId>
<version>4.0.30</version>
</dependency>
</dependencies>
<plugin>
<groupId>net.serenity-bdd.maven.plugins</groupId>
<artifactId>serenity-maven-plugin</artifactId>
<version>4.0.30</version>
<executions>
<execution>
<id>serenity-reports</id>
<phase>post-integration-test</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
Serenity with Cucumber Integration
// src/test/java/runners/CucumberTestSuite.java
@RunWith(CucumberWithSerenity.class)
@CucumberOptions(
features = "src/test/resources/features",
glue = "steps",
tags = "@smoke or @regression"
)
public class CucumberTestSuite {}
// Step definitions with Serenity
public class AuthenticationSteps {
@Steps
AuthenticationActions authActions;
@Given("I am on the login page")
public void navigateToLogin() {
authActions.navigateToLoginPage();
}
@When("I login with username {string} and password {string}")
public void loginWithCredentials(String username, String password) {
authActions.enterCredentials(username, password);
authActions.clickLoginButton();
}
@Then("I should see the dashboard")
public void verifyDashboard() {
authActions.verifyDashboardDisplayed();
}
}
Step Libraries
public class AuthenticationActions {
LoginPage loginPage;
DashboardPage dashboardPage;
@Step("Navigate to login page")
public void navigateToLoginPage() {
loginPage.open();
}
@Step("Enter credentials: {0} / {1}")
public void enterCredentials(String username, String password) {
loginPage.enterUsername(username);
loginPage.enterPassword(password);
}
@Step("Click login button")
public void clickLoginButton() {
loginPage.clickLogin();
}
@Step("Verify dashboard is displayed")
public void verifyDashboardDisplayed() {
dashboardPage.shouldBeVisible();
}
}
Screenplay Pattern: Actor-Centric Testing
The Screenplay pattern provides a more maintainable and expressive approach to test automation.
Screenplay Components
// Actors
public class Actors implements Cast {
@Override
public Actor actorNamed(String actorName, WebDriver driver) {
return Actor.named(actorName)
.whoCan(BrowseTheWeb.with(driver));
}
}
// Tasks
public class Login implements Task {
private final String username;
private final String password;
public static Login withCredentials(String username, String password) {
return new Login(username, password);
}
@Override
@Step("{0} logs in with username #username")
public <T extends Actor> void performAs(T actor) {
actor.attemptsTo(
Open.browserOn().the(LoginPage.class),
Enter.theValue(username).into(LoginPage.USERNAME_FIELD),
Enter.theValue(password).into(LoginPage.PASSWORD_FIELD),
Click.on(LoginPage.LOGIN_BUTTON)
);
}
}
// Questions
public class DashboardVisibility implements Question<Boolean> {
public static DashboardVisibility isDisplayed() {
return new DashboardVisibility();
}
@Override
public Boolean answeredBy(Actor actor) {
return actor.asksFor(
WebElementQuestion.the(DashboardPage.DASHBOARD_CONTAINER)
.isVisible()
);
}
}
// Interactions
public class AddToCart implements Interaction {
private final String productName;
public static AddToCart product(String productName) {
return new AddToCart(productName);
}
@Override
@Step("{0} adds #productName to cart")
public <T extends Actor> void performAs(T actor) {
actor.attemptsTo(
Click.on(ProductPage.addToCartButton(productName)),
WaitUntil.the(CartPage.CONFIRMATION_MESSAGE, isVisible())
);
}
}
Screenplay Test Example
@RunWith(SerenityRunner.class)
public class ShoppingCartTest {
@Managed(driver = "chrome")
WebDriver browser;
Actor customer = Actor.named("Customer");
@Before
public void setUp() {
customer.can(BrowseTheWeb.with(browser));
}
@Test
public void customer_can_add_products_to_cart() {
givenThat(customer).wasAbleTo(
NavigateTo.theHomePage(),
Login.withCredentials("user@example.com", "password123")
);
when(customer).attemptsTo(
SearchFor.product("Laptop"),
AddToCart.product("Laptop Pro 15"),
AddToCart.product("Wireless Mouse")
);
then(customer).should(
seeThat(TheCartCount.value(), equalTo(2)),
seeThat(TheCartTotal.value(), equalTo("$1,029.98"))
);
}
}
Report Features Comparison
Feature | Serenity BDD | Cucumber HTML | Extent Reports |
---|---|---|---|
Living Documentation | Excellent | Basic | Good |
Screenshots | Automatic | Manual | Manual |
Test History | Yes | No | Yes |
Requirements Tracing | Yes | No | Limited |
Tag Filtering | Advanced | Basic | Good |
Performance Metrics | Yes | No | Limited |
Stakeholder-Friendly | Excellent | Poor | Good |
JBehave Integration
// JBehave configuration
public class MyStories extends SerenityStories {
@Override
public Configuration configuration() {
return new MostUsefulConfiguration()
.useStoryLoader(new LoadFromClasspath(this.getClass()))
.useStoryReporterBuilder(
new StoryReporterBuilder()
.withDefaultFormats()
.withFormats(CONSOLE, HTML, JSON)
);
}
}
Advanced Reporting Features
Requirements Hierarchy
@Narrative(
title = "E-Commerce Shopping Features",
text = {"As a customer",
"I want to browse and purchase products",
"So that I can receive items at home"},
cardNumber = "SHOP-123"
)
@WithTag("epic:shopping")
public class ShoppingFeatures {}
Custom Report Data
@Step
public void verifyProductDetails(String productName) {
Serenity.recordReportData()
.withTitle("Product Details")
.andContents(productName);
Serenity.setSessionVariable("productName")
.to(productName);
}
Screenshot Capture
// Automatic on failure
@Step
public void performCriticalAction() {
// Screenshots taken automatically on failure
}
// Manual screenshot
@Step
public void captureEvidence() {
Serenity.takeScreenshot();
}
Practical Use Case: E-Commerce Flow
@RunWith(SerenityRunner.class)
@WithTag("epic:checkout")
public class CheckoutJourneyTest {
Actor customer = Actor.named("Premium Customer");
@Test
@WithTag("type:smoke")
@Title("Complete purchase with saved payment method")
public void complete_purchase_with_saved_payment() {
givenThat(customer).wasAbleTo(
Login.asRegisteredUser("premium@example.com", "pass123"),
NavigateTo.theProductCatalog()
);
when(customer).attemptsTo(
SearchFor.product("Laptop"),
AddToCart.product("Laptop Pro 15").withQuantity(1),
AddToCart.product("Wireless Mouse").withQuantity(2),
ProceedTo.checkout(),
SelectShippingAddress.saved("Home"),
SelectPaymentMethod.saved("Visa ending 1234"),
ApplyCoupon.code("SAVE10"),
ConfirmOrder.withAgreement()
);
then(customer).should(
seeThat("Order is confirmed",
OrderConfirmation.status(), equalTo("CONFIRMED")),
seeThat("Discount applied",
OrderTotal.afterDiscount(), equalTo("$926.98")),
seeThat("Confirmation email sent",
EmailNotification.wasSent(), is(true))
);
}
}
Best Practices
1. Organize Step Libraries
// Domain-focused step libraries
public class ProductCatalogActions {
@Step("Search for product: {0}")
public void searchProduct(String name) { }
@Step("Filter by category: {0}")
public void filterByCategory(String category) { }
}
public class CheckoutActions {
@Step("Select shipping method: {0}")
public void selectShipping(String method) { }
@Step("Apply discount code: {0}")
public void applyDiscount(String code) { }
}
2. Use Meaningful Step Names
// Good: Descriptive business language
@Step("Customer adds {0} to shopping cart")
public void addToCart(String product) { }
// Bad: Technical implementation detail
@Step("Click element with ID 'add-to-cart-btn'")
public void clickAddButton() { }
3. Leverage Tags for Organization
@WithTag("epic:authentication")
@WithTag("capability:login")
@WithTag("type:smoke")
public class LoginTest { }
Conclusion
Serenity BDD elevates behavior-driven development by transforming tests into comprehensive living documentation. Its seamless integration with Cucumber and JBehave, combined with the powerful Screenplay pattern and rich reporting capabilities, makes it an excellent choice for teams seeking stakeholder-friendly test automation.
Key advantages:
- Living Documentation: Automatic generation of narrative reports
- Screenplay Pattern: Maintainable, actor-centric test design
- Rich Reporting: Screenshots, history, and requirement tracing
- Framework Integration: Works with Cucumber, JBehave, JUnit
- Stakeholder Value: Non-technical readable reports
For teams prioritizing communication, traceability, and comprehensive documentation alongside test automation, Serenity BDD provides an unmatched solution that bridges the gap between technical testing and business understanding.