log4p

Peter Maas’s Weblog

Archive for March, 2010

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:

  1. val driver = new FirefoxDriver
  2. driver.get("http://log4p.com/")

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:

  1. class BlogPage {
  2.   @FindBy(how = How.ID, using = "cat")
  3.   var categorySelect: WebElement = null
  4.  
  5.   @FindBy(how = How.ID, using = "content")
  6.   var content: WebElement = null
  7.  
  8.   ....
  9. }

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):

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

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:

  1. class Log4pSpec extends FeatureSpec with GivenWhenThen with ShouldMatchers {
  2.   lazy val driver = new FirefoxDriver
  3.  
  4.   feature("A weblog should have a archive based on post categories") {
  5.     info("As a visitor")
  6.     info("I want to be able to select a category")
  7.  
  8.     scenario("we select the 'scala' category to see posts about Scala") {
  9.       given("we open the frontpage page")
  10.  
  11.       driver.get("http://log4p.com/")
  12.       val indexPage = PageFactory.initElements(driver, classOf[BlogPage])
  13.  
  14.       then("we should see a widget containing categories")
  15.       indexPage.categorySelect.isEnabled should equal(true)
  16.  
  17.       given("we select 'scala' using the select widget")
  18.       indexPage.selectCategoryName("scala")
  19.  
  20.       then("the url for that category should be openend")
  21.       driver.getCurrentUrl should endWith("/category/scala/")
  22.  
  23.       then("all posts should have the selected category in their metadata")
  24.       val categoryPage = PageFactory.initElements(driver, classOf[BlogPage])
  25.       categoryPage.verifyPostCategories("scala") should equal(true)
  26.     }
  27.  
  28.   }
  29. }

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:

  1. Feature: A weblog should have a archive based on post categories
  2.   As a visitor
  3.   I want to be able to select a category
  4.   Scenario: we select the 'scala' category to see posts about Scala
  5.     Given we open the frontpage page
  6.     Then we should see a widget containing categories
  7.     Given we select 'scala' using the select widget
  8.     Then the url for that category should be openend
  9.     Then all posts should have the selected category in their metadata

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:

  1. class BlogPage {
  2.   @FindBy(how = How.ID, using = "cat")
  3.   var categorySelect: WebElement = null
  4.  
  5.   @FindBy(how = How.ID, using = "content")
  6.   var content: WebElement = null
  7.  
  8.  
  9.   def selectCategoryName(categoryName: String) = {
  10.     val options = categorySelect.findElements(By.tagName("option"))
  11.     val option: Option[WebElement] = options.find(_.getText.equals(categoryName))
  12.     if (option.isDefined)
  13.       option.get.setSelected
  14.   }
  15.  
  16.   def verifyPostCategories(categoryName: String): Boolean = {
  17.     val entries = content.findElements(By.className("entry"))
  18.     entries.forall {
  19.       entry =>
  20.         val entryMetaData = entry.findElement(By.className("entrymeta"))
  21.         entryMetaData.getText.split("Category:").last.split(",").map(_.trim).contains(categoryName)
  22.     }
  23.   }
  24. }

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!

No comments

Presentation: DSLs in Scala

At DuSe III I gave a presentation on writing Domain Specific Languages using Scala based on the work I did for my blogposts on internal and external with Scala.

After the SQL example I also showed some of the work I did on a custom templating language I wrote as a experiment for Ebay/Markplaats for which I wrote the parser and interpreter in Scala. Maybe I can give some more details on that in the future.

No comments

Introduction to Scala

Last Tuesday I gave a 'introduction to Scala' presentation during the monthly 'Tech Tuesday' at Ebay/Marktplaats. After going through the slides I did some live test driven development. I think I managed to get some more people interested!

No comments