easySince Selenium was introduced a few years back, it has continued to wow developers with how easily a user acceptance test can be knocked out– simply fire up an instance of a Selenium server in the background and then either write a table test or a RC style test– it’s that easy.

RC style testing is particularly powerful as you have full access to programming languages– for instance, with RC, you can write a functional web test in Java by leveraging a framework like JUnit or TestNG. But what’s often lacking with testing frameworks is a more natural way of expressing behavior– or indeed, scenarios and stories.

For instance, a user acceptance test is really a scenario– a user logs into a website, purchases an item, pays, and logs out. That was a sunny day scenario– there are other scenarios that deal with various other paths– user fails to pay, credit card was invalid, etc. All of these scenarios are logically a story– a story about buying something.

Using a standard scenario language, I can more specifically write a scenario (in a story regarding a website for race registrations) like so:

  • given a user is on the race report page
  • when someone enters a first name and last name in the race report form for someone who has signed up for a race
  • then they should receive a list of all races that person has singed up for

That is a happy day scenario isn’t it? One particular negative path would be:

  • given a user is on the race report page
  • when someone enters a first name and last name in the race report form who hasn’t signed up for any races
  • then they should receive a message indicating the person hasn’t signed up for any races

These scenarios can be easily created using easyb, which is a BDD framework for the Java platform– easyb leverages a domain specific language (or DSL) which supports the following syntax for scenarios:

scenario{
 given "", {}
 when "", {}
 then "", {}
}

This DSL is highly flexible– you can chain phrases together with an and phrase and you can have multiple givens or whens or thens if you’d like. Also too, the scenario phrase isn’t required either.

The DSL makes the assumption that scenarios are in files that are either named YourNameStory.groovy or YourName.story– note that YourName is what ever you’d like.

Using easyb then, I can create a story file, which contains two scenarios– my file will be called RaceReport.story and I’ll start by defining two scenarios:

scenario "a valid person has been entered", {}
scenario "an invalid person has been entered", {}

Given that I plan to leverage Selenium, I’ll have to introduce a few new phrases– for instance, a then one that shuts down Selenium.

I’ll start the RC instance in a given phrase like so:

given "selenium is up and running", {
 selenium = new DefaultSelenium("localhost",
  4444, "*firefox", "http://acme.racing.net/greport")
 selenium.start()
}

Note how I’m connecting to a server instance running on the same machine, which will utilize Firefox.

Next, I can chain two when clauses to simulate a user interacting with the report page.

when "filling out the person form with a first and last name", {
 selenium.open("http://acme.racing.net/greport/personracereport.html")
 selenium.type("fname", "Britney")
 selenium.type("lname", "Smith")
}

and

when "the submit link has been clicked", {
 selenium.click("submit")
}

My then clause then verifies that 4 race instances have been returned for my user– note how I’m able to use a nice Groovy for loop that uses a positional index to grab items from a list and from an XPath expression. Not bad, eh?

then "the report should have a list of races for that person", {
 selenium.waitForPageToLoad("5000")
 values = ["Mclean 1/2 Marathon", "Reston 5K", "Herndon 10K", "Leesburg 10K"]
 for(i in 0..<values.size()){
  selenium.getText("//table//tr[${(i+3)}]/td").shouldBeEqualTo values[i]
 }
}

Lastly, I need to shut down selenium:

and

then "selenium should be shutdown", {
 selenium.stop()
}

The entire first scenario looks like this once you put it all together:

scenario "a valid person has been entered", {

 given "selenium is up and running", {
  selenium = new DefaultSelenium("localhost",
   4444, "*firefox", "http://acme.racing.net/greport")
  selenium.start()
 }

 when "filling out the person form with a first and last name", {
  selenium.open("http://acme.racing.net/greport/personracereport.html")
  selenium.type("fname", "Britney")
  selenium.type("lname", "Smith")
 }

 and

 when "the submit link has been clicked", {
  selenium.click("submit")
 }

 then "the report should have a list of races for that person", {
  selenium.waitForPageToLoad("5000")
  values = ["Mclean 1/2 Marathon", "Reston 5K", "Herndon 10K", "Leesburg 10K"]
  for(i in 0..<values.size()){
   selenium.getText("//table//tr[${(i+3)}]/td").shouldBeEqualTo values[i]
  }
 }

 and

 then "selenium should be shutdown", {
  selenium.stop()
 }

}

That’s pretty easy, don’t you think? Of course, my next step is to implement some additional scenarios, such as negative paths with an non-existing runner, etc.

When I run this via the easyb runner, I can get a story printout that looks something like this:

12 behavior steps executed successfully
 scenario a valid person has been entered
  given selenium is up and running on website
  when filling out the person form with a first and last name
  when the submit link has been clicked
  then the report should have a list of races for that person
  then selenium should be shutdown
 scenario an invalid person has been entered
  given selenium is up and running on website
  when filling out the person form with a first and last name
  when the submit link has been clicked
  then the report should have a list of races for that person
  then selenium should be shutdown

The scenarios are slight variations of one another, hence the report looks quite similar — each step is the same, just the data varies.

Functional web stories are a powerful mechanism to verify the proper behavior of web applications from a user’s standpoint. Combining a framework that supports stories and scenarios with Selenium yields an easy way to deliver software more quickly and collaboratively.