I've been working with Scala for awhile, and I've just dipped my toe into Selenium. Selenium is a web testing tool that allows you to drive a browser like Firefox, to do more detailed testing of web pages. Coupled with Cucumber, doing BDD based testing is pretty cool.
Setting this up in Java is a fair bit of work, and I managed to get it going with a little help from the internet. Then I wondered what it would look like in Scala. He below lies a configuration for settings up Selenium testing using Cucumber in Scala.
You can find all the pieces for this out there, but I hope having them all in one happy place will save a few folks some time! I could have used sbt, but chances are, if you're using sbt, you can easily figure out how to convert this pom. I use maven here because it's a common denominator (perhaps the lowest, but still), you can import it easily into IntelliJ or Eclipse or Netbeans.
As per usual, I'm no Scala expert, but in my project this works though there may be more idiomatic ways of solving some of the things in here. Please please comment if you have thoughts or ideas on this. The pom file:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>selenium-tests-scala</artifactId> <version>1.0.0</version> <inceptionYear>2012</inceptionYear> <packaging>jar</packaging> <properties> <scala.version>2.9.1</scala.version> </properties> <repositories> <repository> <id>scala-tools.org</id> <name>Scala-Tools Maven2 Repository</name> <url>http://scala-tools.org/repo-releases</url> </repository> <repository> <id>sonatype-snapshots</id> <url>https://oss.sonatype.org/content/repositories/snapshots</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>scala-tools.org</id> <name>Scala-Tools Maven2 Repository</name> <url>http://scala-tools.org/repo-releases</url> </pluginRepository> </pluginRepositories> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-compiler</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <dependency> <groupId>org.specs</groupId> <artifactId>specs</artifactId> <version>1.2.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>2.17.0</version> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-junit</artifactId> <version>1.0.0.RC16</version> <scope>test</scope> </dependency> <dependency> <groupId>info.cukes</groupId> <artifactId>cucumber-scala</artifactId> <version>1.0.0.RC16</version> <scope>test</scope> </dependency> </dependencies> <build> <sourceDirectory>src/main/scala</sourceDirectory> <testSourceDirectory>src/test/scala</testSourceDirectory> <plugins> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion>${scala.version}</scalaVersion> <args> <arg>-target:jvm-1.5</arg> </args> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <configuration> <downloadSources>true</downloadSources> <buildcommands> <buildcommand>ch.epfl.lamp.sdt.core.scalabuilder</buildcommand> </buildcommands> <additionalProjectnatures> <projectnature>ch.epfl.lamp.sdt.core.scalanature</projectnature> </additionalProjectnatures> <classpathContainers> <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer> <classpathContainer>ch.epfl.lamp.sdt.launching.SCALA_CONTAINER</classpathContainer> </classpathContainers> </configuration> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <configuration> <scalaVersion>${scala.version}</scalaVersion> </configuration> </plugin> </plugins> </reporting> </project>
The features file is used to speak BDD language. This file is processed by Cucumber and matched against the step definitions file.
Feature: The home page should show useful content Scenario: Load the home page Given I want to see my home page Then I should see valid content
This class functions essentially as a marker to JUnit to execute Cucumber to build the test instead of scanning this class for methods directly. You'd think there'd be an easier way to do this without having to have something that amounts to no more than a stub here.
package com.example.selenium.cucumber import cucumber.junit.Cucumber import cucumber.junit.Feature import org.junit.runner.RunWith /** * Test definition class that indicates this test is to be run using Cucumber and with the given feature definition. * The feature definition is normally put in src/main/resources */ @RunWith(classOf[Cucumber]) @Feature("Home.feature") class HomePageTest { }
The step definitions file is used by Cucumber to match the features text agains. There are several basic predicates in Cucumber like "When" and "Then" and "And" that you can use via the DSL. Cucumber supports many languages in this regard so you're not limited to English for this.
package com.example.selenium.cucumber import org.openqa.selenium.WebDriver import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue import com.example.selenium.pageobject.HomePage import com.example.selenium.WebDriverFactory import cucumber.runtime.{EN, ScalaDsl} /** * Step definitions for the Home Page behaviours */ class HomeStepDefinitions extends ScalaDsl with EN { Before() { driver = new FirefoxDriver() } After() { driver.close() } Given("^I want to see my home page$") { home = new HomePage(driver) } Then("^I should see valid content$") { var actualHeadLine: String = home.getTitle assertEquals(actualHeadLine, "My Awesome Home Page!") assertTrue("Page content should be longer than this", home.getContent.length > 4096) } private var driver: WebDriver = null private var home: HomePage = null }
The HomePage object here is used to store information about the home page, and potentially actions that you can perform whilst on the HomePage which I've omitted here for simplicity. In my working system, I make most of this live in a base class that all my page classes extend. Things like the ability to screen shot are useful everywhere, as is reading the title and page content. Some generic tests that can simply match the title and content for a whole bunch of pages can be very useful for bootstrapping the test process. They might not be good test, but they're definitely better than nothing!
package com.example.selenium.pageobject import org.openqa.selenium.By import org.openqa.selenium.WebDriver import org.openqa.selenium.WebElement /** * A page object used to represent the home page and associated functionality. */ class HomePage(driver: WebDriver, baseUrl: String) { private var driver: WebDriver = null private var baseUrl: String = null def getDriver: WebDriver = driver protected def takeScreenShot(e: RuntimeException, fileName: String): Throwable = { FileUtils.copyFile( (driver.asInstanceOf[TakesScreenshot]).getScreenshotAs(OutputType.FILE), new File(fileName + ".png") ) e } def getContent: String = getDriver.getPageSource def getTitle: String = getDriver.findElement(By.tagName("title")).getText }
I hope I haven't left anything critical out that would be required to get this system bootstrapped. Obviously this set up doesn't do very much, but, it will at least get you up and running. You can probably figure out the rest from the Cucumber and Selenium documentation at this point. I might post a follow up with some more detail, but it took me a few weeks to get this posted!