Selenium2 from Scalatest

Scalatest offers some very elegant ways to layout your tests. After using Selenium2 at work I started thinking how I could leverage the BDD-like goodness in combination with Selenium2. The combination proves to be very useful.

Some basics
Selenium2 offers a couple of drivers which allow your code to spawn webbrowsers (or a virtual browser) and control and monitor them.

In Scala starting a Firefox instace and opening a url would look like:

[code]
val driver = new FirefoxDriver
driver.get("http://log4p.com/")
[/code]

After opening the page you can use the driver to browse the actual Dom of the response. Methods for this include stuff like findElementsByTagName, ClassName, Id and XPath. It even allows you to make a PNG image of the page.

Bind pages to classes
Next to the above methods the driver can ‘bind’ values in the page to bean properties. I discovered that Selenium has no problems with populating annotated Scala classes:

[code]
class BlogPage {
@FindBy(how = How.ID, using = "cat")
var categorySelect: WebElement = null

@FindBy(how = How.ID, using = "content")
var content: WebElement = null

....
}
[/code]

The FindBy annotations can also be configure to use classes, XPaths, partial url texts etc. Populating instances can be done via the driver (after having it request a url):

[code]
driver.get("http://log4p.com/")
val indexPage = PageFactory.initElements(driver, classOf[BlogPage])
[/code]

combined with FeatureSpec
FeatureSpecs allow you to describe scenarios of features, and has options to create ‘pending’ tests; very useful for test driven development. If we combine Selenium with this type of test we get something like this:

[code]
class Log4pSpec extends FeatureSpec with GivenWhenThen with ShouldMatchers {
lazy val driver = new FirefoxDriver

feature("A weblog should have a archive based on post categories") {
info("As a visitor")
info("I want to be able to select a category")

scenario("we select the 'scala' category to see posts about Scala") {
given("we open the frontpage page")

driver.get("http://log4p.com/")
val indexPage = PageFactory.initElements(driver, classOf[BlogPage])

then("we should see a widget containing categories")
indexPage.categorySelect.isEnabled should equal(true)

given("we select 'scala' using the select widget")
indexPage.selectCategoryName("scala")

then("the url for that category should be openend")
driver.getCurrentUrl should endWith("/category/scala/")

then("all posts should have the selected category in their metadata")
val categoryPage = PageFactory.initElements(driver, classOf[BlogPage])
categoryPage.verifyPostCategories("scala") should equal(true)
}

}
}

[/code]

This test will:

  • Start Firefox
  • Open the frontpage of this blog
  • Select ‘scala’ in the category box in the menu on the right
  • Verify the opened url
  • Verify that all posts in the category page have the selected category in their metadata

If you run the test, scalatest will create some very nice output:

[code]
Feature: A weblog should have a archive based on post categories
As a visitor
I want to be able to select a category
Scenario: we select the 'scala' category to see posts about Scala
Given we open the frontpage page
Then we should see a widget containing categories
Given we select 'scala' using the select widget
Then the url for that category should be openend
Then all posts should have the selected category in their metadata
[/code]

Keen observers might have noticed some helper methods on the BlogPage class, like ‘selectCategoryName’ and ‘verifyPostCategories’. Using the collection conversions from Scala 2.8 we don’t even have to wrap the Selenium API to make them look nice:

[code]
class BlogPage {
@FindBy(how = How.ID, using = "cat")
var categorySelect: WebElement = null

@FindBy(how = How.ID, using = "content")
var content: WebElement = null

def selectCategoryName(categoryName: String) = {
val options = categorySelect.findElements(By.tagName("option"))
val option: Option[WebElement] = options.find(_.getText.equals(categoryName))
if (option.isDefined)
option.get.setSelected
}

def verifyPostCategories(categoryName: String): Boolean = {
val entries = content.findElements(By.className("entry"))
entries.forall {
entry =>
val entryMetaData = entry.findElement(By.className("entrymeta"))
entryMetaData.getText.split("Category:").last.split(",").map(_.trim).contains(categoryName)
}
}
}
[/code]

As you can see translating stories into real integration tests is quite simple; and it should be possible to run the tests in different browsers (Chrome, IE) as well. And since you won’t be writing production code it could be a very nice way to give scala into your project!

This entry was posted in scala, testing. Bookmark the permalink.

2 Responses to Selenium2 from Scalatest

  1. Jakub says:

    Very interesting post! I think writing tests it is a good way for QA people to get into Scala.

  2. James Kearney says:

    Really great post, thanks

Leave a Reply

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

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>