If you are looking for a simple template for building your own test automation framework for a web-app using Java, Selenium / Selenide and Cucumber — you can find one here.
This is a brief overview of BDD with specifics on implementing an automated test suite using Java, Selenium and Cucumber.
When trying to leverage BDD in a project, people often get bogged down by the steps required to build an automated test suite as if it is a massive undertaking — it isn’t.
Hopefully this article will demystify the process somewhat and provide some useful tips to get you started with your project.
BDD (Behaviour Driven Development) is a set of development practices which aims to reduce the overhead caused by common dev headaches — like having to revisit work because requirements were misunderstood or not fully elaborated.
BDD is an extension of TDD with the only real differences in the methodologies being the way test cases in BDD are specified.
BDD uses clear, ubiquitous language for test-cases which are derived ‘from the outside-in’ — i.e. directly from the business requirements and desired business outcomes for the behaviour of the application.
The unambiguous language used in these test cases is typically written in the form of Gherkin DSL (domain specific language).
So you’ve had an idea for a killer app — you’re going to revolutionise the world by having the perfect login screen!
Let’s start by writing out how we want this to behave in Gherkin.
(Note: Background steps are a shorthand for making a set of steps that run for every single scenario)
Feature: Login page for web app
Background:Given I browse to the web app and I am not logged in
Scenario: Login page displayedThen I see a "login" pageAnd the login page has a login formAnd there is a logo on the login page
Scenario: I try to login with an invalid userWhen I login with an invalid userThen I see a "Username or password is incorrect" error message
Scenario: I try to login with a valid userWhen I login with a valid userThen I see a "home" page
The real advantage of writing test cases in this form are that they serve two purposes:
Step definitions map the steps defined in Gherkin (the feature file) to concrete actions. We’ve agreed how we want our application to behave and documented it, now we need to write code that tests that behaviour.
Using Cucumber we can write blank step definitions in the following form — where annotations and regular expressions map steps in a feature file to concrete actions:
...
@Given("^I browse to the web app and I am not logged in$")public void iBrowseToTheWebAppAndIAmNotLoggedIn(){// TODO}
@When("^I login with an invalid user$")public void iLoginWithAnInvalidUser(){// TODO}
@Then("^I see a \"([^\"]*)\" error message$")public void iSeeAErrorMessage(String errorMessage){// TODO}
...
(Note: Many IDEs have plugins which will auto-magically generate these for you from feature files)
We want to be able to run our tests against an actual running web application which we’re going to write. To do this we can combine our cucumber step definitions with the capabilities of the Selenium framework.
Selenium is a browser automation framework, you can leverage a WebDriver and interact with a web-page using a real browser and through selecting elements on the screen using CSS or XPath selectors. There are many different implementations of WebDriver — for various different browsers as well as ‘headless’ implementations where a browser is ran without a GUI.
public final class WebDriverUtils{
private static WebDriver _webDriver_;
public static synchronized WebDriver getWebDriverInstance()
{
if(_webDriver_ \== null)
{
DriverPathLoader._loadDriverPaths_(null);
_webDriver_ \= new ChromeDriver();
}
return _webDriver_;
}
}
Example of asserting an element is on a page using standard Selenium:
Assert.assertNotNull(webDriver.findElement(By.cssSelector("#username")).getText());
On its own, Selenium is incredibly powerful, but it can have a few drawbacks — e.g. the verbosity of selectors for selecting elements on a page, handling exceptions and how the framework handles timing and timeouts.
Example of asserting an element is on a page using Selenide:
This is where Selenide comes in. It’s essentially just a very clean and easy to use wrapper around the Selenium framework.
$("#username").shouldBe(visible);
You’ll agree the above is considerably less verbose and much more readable.
The page object model is a design pattern where pages and elements on a page are represented as object orientated classes — when you need to interact with an element on screen , you don’t call the WebDriver directly — instead you call the methods on a class representing the element on the page.
Let’s take the example for our login page app:
public class LoginPage extends AbstractPage{
private static final String _PAGE\_NAME_ \= "login";
LoginPage(String basePath)
{
super(basePath);
}
**@Override**
public String getPageName()
{
return _PAGE\_NAME_;
}
**@Override**
public void go()
{
URI loginUri = URI._create_(basePath).resolve(getPageName());
_open_(loginUri.toString());
}
public LoginFormElement loginForm()
{
return new LoginFormElement();
}
}
The class above represents our login page — it returns the main element that exists on the page (LoginFormElement) as a separate class which has its own methods and responsibilities which it handles (such as logging in).
public class LoginFormElement extends AbstractElement<LoginFormElement>{private static final String INPUT_USERNAME = "#username";private static final String INPUT_PASSWORD = "#password";private static final String SUBMIT = "#login_submit";
**@Override**
public LoginFormElement isVisible()
{
_$_(_INPUT\_USERNAME_).shouldBe(_visible_);
_$_(_INPUT\_PASSWORD_).shouldBe(_visible_);
_$_(_SUBMIT_).shouldBe(_visible_);
return this;
}
public void login(String username, String password)
{
_$_(_INPUT\_USERNAME_).setValue(username);
_$_(_INPUT\_PASSWORD_).setValue(password);
_$_(_SUBMIT_).click();
}
}
You will see that CSS element selectors have been defined here for username and password input fields, as well as for a login submit button.
When we build the web-app we just need to ensure that we label the fields and the button with these identifiers and if all has gone well — the tests will pass!
Here’s what we’ve put together for the step definitions for testing our login page based on the scenarios we described in the feature file.
We will almost certainly refactor this (for example we will need to figure out how to inject or know valid credentials for our ‘login with a valid user’ scenario) but this gives us a foundation to build upon.
public class LoginStepDefinition extends BaseStepDefinition{
private LoginPage loginPage = pages.loginPage();
private AlertElement alertElement = pages.alertComponent();
private LoginFormElement loginFormElement = loginPage.loginForm();
@Given("^I browse to the web app and I am not logged in$")
public void iBrowseToTheWebAppAndIAmNotLoggedIn()
{
loginPage.go();
}
@And("^the login page has a login form$")
public void theLoginPageHasALoginForm()
{
loginPage.loginForm().isVisible();
}
@When("^I login with an invalid user$")
public void iLoginWithAnInvalidUser()
{
loginFormElement.login("invalid", "invalid");
}
@Then("^I see a \\"(\[^\\"\]\*)\\" error message$")
public void iSeeAErrorMessage(String errorMessage)
{
alertElement.isVisible().hasMessage(errorMessage);
}
@When("^I login with a valid user$")
public void iLoginWithAValidUser()
{
loginFormElement.login("valid", "valid");
}
@And("^there is a logo on the login page$")
public void thereIsALogoOnTheFrontPage()
{
loginPage.logo().isVisible();
}
}
After some further work writing step definitions and writing page objects, we have an automated test suite which will reliably fail for our login page app (because we haven’t built anything to pass the tests yet).
As you build your application and add more functionality, more of the tests pass. To ensure tests pass, you just need to build the appropriate functionality with the appropriate selectors (as defined in your page object models and elements).
As more requirements get added, you add more tests. Simple.
I’d suggest pulling down the project and understanding how the framework fits together and trying to run it (and watching it fail).
The example was described using Java, Cucumber, Selenium and Selenide — it is definitely not the only way to write an automated test suite. Every language has its own set of frameworks but the underlying approaches are generally the same.
There is no reason why you can’t use an entirely different language for your automated tests rather than the language the project is written in - so long as it isn’t too exotic (don’t write it in FORTRAN or assembly!), isn’t completely against your project’s and company’s technology strategy and there are well-embedded skills in your team to support the language.
In fact I’d highly recommend this if the team member responsible for the automated test suite has skills that are more developed in different frameworks or languages. Figure out what works best for your project by considering the capabilities of your team.
Thank you for reading! 🙂