Serenity BDD – ‘Hello World’ Introduction with Page Objects

NEW Posts – Generating Spark and Pdf Extent Report for “Rest Assured with Maven plugin” and “Rest Assured and Cucumber with Maven plugin”

Introduction

This article attempts to introduce the Serenity BDD framework with a sample implementation using JUnit. The implementation will integrate Serenity with the Page Objects concept. The website used is https://www.saucedemo.com/ from Sauce Labs which can be used for learning browser automation.

Before we go any further, let me mention a few disclaimers. First, I was trying to learn Screenplay Pattern implementation in Serenity which was a struggle due to the different thought process involved. Therefore I decided to take a step back and learn Serenity from scratch and the article is a result of this. Second, Serenity does not integrate with TestNG. Third, this will not be an in-depth discussion about the features of Serenity which is beyond the scope of this article, though I will mention all the useful references.

The article will show case the ‘Hello World’ of browser automation, that is the Login use case of the Sauce Demo website.

What is Serenity?

Serenity BDD is an open source library that aims to make the idea of living documentation a reality.

This is the official definition from the Serenity guide book – https://serenity-bdd.github.io/theserenitybook/latest/index.html. This is not much different from what is mentioned for Cucumber – https://cucumber.io/docs/guides/overview/.

One key advantage of using Serenity BDD is that you do not have to invest time in building and maintaining your own automation framework.

This quote from the same document offers a much better reason why Serenity should be looked at as a viable alternative. Serenity offers support for Selenium with features like configurable driver management, exhaustive base page object methods, web element manipulation methods, comprehensive reporting and many others. This support is also extended to API testing using RESTAssured. Serenity also provides an implementation of the ScreenPlay pattern in Java.

Source

The main branch with the completed code is located here. The repository also has multiple branches at different development stages.

The official serenity junit starter repository can also be used as a starting point.

Branch NameBranch Description
navigate_loginOpen browser and navigate to login page
input_loginEnter login credentials and submit data
verify_loginVerify successful user login
product_cartSuccessful login, view product details and add to cart

Use Case

Dependencies

The minimum dependencies and plugins required to execute the article code is available in the POM. The main artifacts are serenity-junit dependency and serenity-maven-plugin for reporting.

Test Code Abstraction Levels

  • The business goal that is being tested. This is represented by a method with the @Test annotation.
  • The tasks needed to accomplish the goal. This is included in a Steps class which we will look at in more details.
  • The page actions which are included in the Page Objects class.

Lets start looking at some code. Many of the Serenity classes have similar or even same names to Selenium ones, so have a look at the imports section from the class github links.

Navigate to Sauce Login Page

In this section, we will open up a browser instance and navigate to the login page. We will take a bottom up approach, by looking at the Page Objects, then Steps and finally the Test class. Then we will look at the generated report. Lets begin.

The source code for this section is available here, in the navigate_login branch.

Page Object class

[Github class] The Page Object class needs to extend the PageObject Serenity class. This Serenity class provides multiple functionalities and aids in keeping the test project Page Object class clean.

@DefaultUrl("https://www.saucedemo.com/")
public class LoginPageObject extends PageObject {

	@FindBy(id = "login-button")
	private WebElementFacade loginButton;

	public void navigateToLogin() {
		open();
	}

	@WhenPageOpens
	public void checkLoginButtonIsVisible() {
		loginButton.waitUntilVisible();
	}
}

The @DefaultUrl annotation value is used by the open() method to navigate to the desired site. The method annotated with @WhenPageOpens is called after the open() method. One can use this to make sure all the elements in the page are loaded completely. In this case, we are waiting for the login button to be visible.

The login button variable belongs to the WebElementFacade which is similar to the WebElement interface but with additional Serenity methods. This is also true for the @FindBy annotation.

Steps class

[Github class] The Serenity Steps class contains the tasks required for accomplishing the business goal. The methods will call the page action methods from the Page Object class.

public class LoginSteps {

	private String actor;
	private LoginPageObject loginPageObject;

	@Step("'#actor' navigates to sauce demo site")
	public void navigateToWebApp() {
		loginPageObject.navigateToLogin();
	}
}

The Page Object instantiation is handled by Serenity and we just need to call the method which opens the Login page.

Each method has a @Step annotation which is displayed in the report. The #actor value is injected by Serenity. This by default takes the name of the instance variable of the Steps class in the Test class. This will be explained in the next section.

Test class

[Github class] At last we need a JUnit test class, annotated with the @RunWith(SerenityRunner.class) annotation. This is required for Serenity to do its magic.

The test class needs to have a WebDriver instance with a @Managed annotation for Serenity to manage it in the background. That is all is required, we do not need to manage the driver anymore. Each test class will need this driver variable declaration.

@RunWith(SerenityRunner.class)
public class SauceLogin {

	@Managed
	private WebDriver driver;

	@Steps(actor = "John Doe")
	private LoginSteps loginSteps;

	@Test
	@Title("Navigate To Sauce Login Test")
	public void navigateToLoginPage() {
		loginSteps.navigateToWebApp();
	}
}

To configure the webdriver the settings need to be mentioned in a serenity.conf file at the location src/test/resources folder. The minimum settings required are the webdriver.driver=chrome and webdriver.autodownloaded = true. The default browser in Serenity is Firefox. Other settings like start size, disable sandbox, disable gpu and others can be added to the chrome.switches setting. The Selenium webdriver instance can be accessed by using the getDriver() method of the PageObject class.

webdriver {
  driver = chrome
  autodownload = true
}
chrome {
  switches = "--start-maximized;--enable-automation;--no-sandbox;--disable-popup-blocking;--disable-default-apps;--disable-infobars;--disable-gpu;--disable-extensions;"
}

For further webdriver configuration settings and options, refer to the official document.

Next we declare an instance variable for the Steps class with a @Steps annotation. The actor parameter of the @Steps annotation will be injected into the actor instance variable of the Steps class. If the actor parameter is not present then the name of the Step class instance variable is injected.

Now we look at the test method which calls the Steps class method. The @Title annotation value is used in the report for the test name. This is optional and the default is derived from the method name, the camel case format is split with spaces added. The default test name will be ‘Navigate To Login Page’.

Report

The following settings can be added to the serenity.conf file to customize the report. The default output folder is target/site/serenity. This can be changed with the serenity.outputDirectory setting. The serenity.test.root setting is used to create a display hierarchy of features and stories.

serenity {
  project.name = "Login To Sauce (Hello World!)"
  test.root = "sauce"
  outputDirectory  = reports
}

The report of the test run can be found here. We can see the value of the @Title annotation, ‘Navigate To Sauce Login Test’, added as the heading. Also the value of the actor, ‘John Doe’, injected into the steps.

Submit Login Details

In this section, we will enter the login credentials and submit them. The source code for this section is available here, in the input_login branch.

Test class

[Github class] The test method in the previous section is removed and the contained logic is moved into a JUnit Before method. A new test method which attempts to successfully login into the application is added. A Step method is called with valid credentials.

@Before
public void navigateToLoginPage() {
	loginSteps.navigateToWebApp();
}

@Test
@Title("Submit Login Details Test")
public void shouldLoginSuccesfully() {
	loginSteps.attemptToLoginSuccessfully("standard_user", "secret_sauce");
}

Steps class

[Github class] The interesting part is the ‘{0}’ part in the @Step annotation. This is the string value of the parameters in the order of appearance in the method. This ensures that these values are included in the report.

@Step("'#actor' succesfully logs in with username '{0}' and password '{1}'")
public void attemptToLoginSuccessfully(String username, String password) {
	loginPageObject.attemptToLoginWithCredentials(username, password);
}

Page Object class

[Github class] Additional code is added for the two input text fields declaration and method to enter the details. The type() method first clears the input field and then enters the text.

@FindBy(id = "user-name")
private WebElementFacade usernameInput;

@FindBy(id = "password")
private WebElementFacade passwordInput;

public void attemptToLoginWithCredentials(String username, String password) {
	enterUserName(username);
	enterPassword(password);
	submitLogin();
}

public void enterUserName(String username) {
	usernameInput.type(username);
}

public void enterPassword(String password) {
	passwordInput.type(password);
}

public void submitLogin() {
	loginButton.submit();
}

Report

The report of the test run can be found here.

Verify Successful Login

In this section, we will assert the successful login.

The source code for this section is available here, in the verify_login branch.

Test class

[Github class] The inventory steps instance variable is added with the @Steps annotation and a call to the assertion method is made.

@Steps(actor = "John Doe")
private InventorySteps inventorySteps;

@Test
@Title("Successful Login Test")
public void shouldLoginSuccesfully() {
	loginSteps.attemptToLoginSuccessfully("standard_user", "secret_sauce");
	inventorySteps.verifyProductsDisplayed();
}

Steps class

[Github class] A call is made to the inventory page object class to retrieve the page heading and an assertion using AssertJ is performed.

public class InventorySteps {

	private String actor;

	private InventoryPageObject inventoryPageObject;

	@Step("'#actor' should see products displayed")
	public void verifyProductsDisplayed() {
assertThat(inventoryPageObject.getProductPageHeadingText()).isEqualTo("PRODUCTS");
	}
}

Page Object class

[Github class] This class is similar to the Login PageObject that we saw before. The strange looking method with the ‘$’ symbol finds the web element on the page. This returns an object of class WebElementFacade. The getTextValue() method will return either the text of an input element or the text of the ‘value’ attribute of an element.

public class InventoryPageObject extends PageObject {

	public String getProductPageHeadingText() {
		return $(By.xpath("//div[@id='header_container']//span[@class='title']")).getTextValue();
	}
}

To search for a list of elements, use the ‘$$‘ method. This will return an object of class ListOfWebElementFacades.

Report

The report of the test run can be found here.

Product View Details & Add To Cart

This branch consists of two extra test classes. The SauceViewProduct test class selects a product and displays the details. The SauceProductCart test class adds a product from the details page and also adds a product from the inventory page. Feel free to explore the code. The generated report can be found here.

Screenshots

In the reports the screenshots are by default taken for each step and user interface action. This can be modified by using the serenity.take.screenshots setting. The various options include for failure only, each action, before and after each step and after each step. For more details refer to the official documentation.

Shutterbug can be easily plugged into the framework by including the Maven dependency and setting serenity.screenshooter to shutterbug. A custom implementation can be added by extending the ScreenShooter interface and adding this to the serenity.screenshooter setting. More details can be found in this link.

Locator Factory

The element locator factory in Serenity has an implicit timeout set to 2 seconds as per the official documentation. This can be modified by using the webdriver.timeouts.implicitlywait setting in serenity.conf file. The webdriver locator factory can also be used by setting serenity.locator.factory to DefaultElementLocatorFactory, which has an implicit timeout of 0 seconds, as per the documentation.

Helpful Serenity Classes

The PageObject class is always a good starting point and then the WebElementFacade interface and its implementation class, WebElementFacadeImpl. The default locator factory SmartElementLocatorFactory and related classes, provides insight into element detection and timeouts.

References

Leave a Reply

Your email address will not be published. Required fields are marked *