SCALATEST SELENIUM DSL
Scala days 2014 - Berlin
MATTHEW FARWELL
• Over 20 years development experience
• Project Lead on Scalastyle, style checker for Scala http://www.scalastyle.org
• Contributor to various open source projects, notably Scalatest, JUnit, Scala-
IDE
• Co-author with Josh Suereth of "sbt in Action" http://manning.com/suereth2/
• https://github.com/matthewfarwell/scaladays-2014-selenium
GOALS
• Present the Selenium DSL
• Show how it simplifies the writing of selenium tests.
• Show how it can be used to test a javascript heavy application
• Show how it can be integrated with other tests to make more useful
integration tests
• The future, well what I would like anyway
SELENIUM
• Selenium automates browsers.
• Firefox, Chrome, Internet Explorer, Safari, Opera, HtmlUnit
• Selenium has interfaces for Java, Python, Ruby, etc.
• We'll be looking at from the point of view of testing
• But we could use it for other stuff – automation of administration for example.
EXAMPLE (JAVA)
final WebDriver driver = new FirefoxDriver();
Wait<WebDriver> wait = new WebDriverWait(driver, 30);
driver.get("http://www.google.ch/");
driver.findElement(By.name("q")).sendKeys("Matthew Farwell");
driver.findElement(By.name("btnG")).click();
wait.until(new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver webDriver) {
return webDriver.findElement(By.id("resultStats")) != null;
}});
boolean b = driver.findElement(By.tagName("body"))
.getText().contains("farwell.co.uk");
System.out.println((found ? "You are famous" : "Not famous enough, sorry"));
EXAMPLE (PYTHON)
driver = webdriver.Firefox()
driver.get("http://www.google.ch")
elem = driver.find_element_by_name("q")
elem.send_keys("Farwell")
elem.send_keys(Keys.RETURN)
assert "farwell.co.uk" in driver.find_by_element_id("body")
EXAMPLE (SCALA)
go to "http://www.google.ch/"
textField("q").value = "Farwell"
click on "btnG"
eventually {
val t = find(tagName("body")).get.text
}
t should include("farwell.co.uk")
println("Yay, you are famous")
- AmIFamousScala.scala
OUR APPLICATION
• https://github.com/matthewfarwell/scaladays-2014-selenium
$ sbt
> re-start
- Run tests from Eclipse
$ sbt
> re-start
> test
A REAL TEST
val baseUrl = "http://localhost:9100/"
"slash" should "redirect to admin index.html" in {
go to baseUrl
currentUrl should be (baseUrl + "admin/index.html#/users")
find("pageTitle").get.text should be("Users")
textField("userSearch").value should be('empty)
find("userUsername_2").get.text should be("matthew")
}
- UserSpec1.scala
ADDING EVENTUALLY
eventually {
find("userUsername_2").get.text should be("matthew")
}
- UserSpec2.scala
ADDING EVENTUALLY (2)
eventually {
find("pageTitle").get.text should be("Users")
textField("userSearch").value should be('empty)
find("userUsername_2").get.text should be("matthew")
}
- UserSpec2.scala
GETTING / SETTING VALUES
go to (baseUrl + "admin/index.html#/users")
eventually {
click on "userEdit_2"
}
eventually {
val tf = textField("userFullname")
tf.value should be("Matthew Farwell")
tf.isEnabled should be (true)
}
textField("userFullname").value = "Matthew Farwell is great"
- UserSpec3.scala
GETTING / SETTING VALUES
pwdField("password").value = "hello world"
pwdField("confirmPassword").value = "hello world"
find("error").get.text should be("Passwords do not match")
- UserSpec3.scala
DEBUGGING
capture to "foo.png"
println(currentUrl)
println(pageSource)
withScreenshot {
// some stuff
// if ModifiableMessage is thrown, screenshot is taken
}
DEBUGGING - REPORTERS
event match {
case e: TestFailed => {
val name = e.testName
web.captureTo(s"${name}.png")
println(web.pageSource)
}
}
- Common.scala
OTHER INPUT TYPES
• textField
• pwdField
• checkBox
• radioButton
• singleSel, multiSel
• HTML5 - emailField, colorField, dateField, dateTimeField, dateTimeLocalField,
monthField, numberField, rangeField, searchField, telField, timeField, urlField,
weekField
XPATH LOOKUPS
"list" should "default to 10 items" in {
go to (baseUrl + "admin/index.html#/users")
eventually {
val xp = "//tr//td[starts-with(@id, 'userUsername_')]"
val tds = findAll(xpath(xp)).map(_.text).toList
tds.size should be(10)
}
}
- UserSpec5.scala
PAGE OBJECT PATTERN
class HomePage extends Page {
val url = "localhost:9100/index.html"
}
val homePage = new HomePage
go to homePage
COOKIES
add cookie ("cookie_name", "cookie_value")
cookie("cookie_name").value should be ("cookie_value")
delete cookie "cookie_name"
ALERTS, FRAMES, WINDOWS
switch to alert
switch to frame(0) // switch by index
switch to frame("name") // switch by name
switch to window(windowHandle)
switch to activeElement
switch to defaultContent
NAVIGATION
goBack()
goForward()
reloadPage()
JAVASCRIPT
go to (host + "index.html")
val result1 = executeScript("return document.title;")
result1 should be ("Test Title")
val result2 = executeScript("return 'Hello ' + arguments[0]", "ScalaTest")
result2 should be ("Hello ScalaTest")
INTEGRATION TESTING
// create a new user
click on "newUser"
textField("username").value = "user"
// etc.
click on "submit"
// how do we test that the user has been created?
- UserSpec4.scala
INTEGRATION
• A lot of the time, we would like to assert something which isn't available
through the HTML front end
INTEGRATION TESTING
eventually {
val after = Services.listUsers().unmarshall
after.size should be(before.size + 1)
val u = after.find(_.username == "user").get
u.fullName should be(Some("Mr. User"))
u.id should not be (None)
}
- UserSpec4.scala
MULTI-BROWSER TESTING
abstract class UserSpecBase with Driver {
// tests here
}
class UserSpecChrome extends UserSpecBase with Chrome
class UserSpecFirefox extends UserSpecBase with Firefox
class UserSpecSafari extends UserSpecBase with Safari
- UserSpec6.scala
OUR CONTEXT
• Lots of legacy code, partially tested with selenium in Java / Python.
• Lots of new code, which has been tested with Scalatest selenium DSL
• A simple click on a web page can have effects way beyond CRUD
• Selenium used for testing pure HTML, GWT, Angular JS applications
OUR EXPERIENCE
• DSL is good for walking through a test – clearer and more readable than the
Java equivalent
• We have tests written by "developers" and "testers"
• Consistent tests – we use Scalatest for unit tests, UI tests and integration tests.
• Selenium DSL is integrated into Scalatest
• Always have ids on elements
• Our integration tests work with heterogeneous actions in the same test:
• we can perform an action in the Browser,
• then check call rest service,
• then check the database,
• call a command via ssh on a remote machine.
WHAT WE'D LIKE TO IMPROVE
IMPLICIT EVENTUALLY
eventually {
click on "userEdit_2"
}
eventually {
textField("foo").get.value should be ("bar")
}
eventually {
click on "btn"
}
eventually {
textField("baz").get.value should be ("flobble")
}
WHAT WE'D LIKE TO IMPROVE
SLOWING DOWN
eventually {
click on "userEdit_2"
}
eventually {
textField("foo").get.value should be ("bar")
}
eventually {
click on "userEdit_2"
}
eventually {
textField("foo").get.value should be ("bar")
}
ME AGAIN
• Matthew Farwell
• Twitter: @matthewfarwell
• Blog: http://randomallsorts.blogspot.ch/
• Scalastyle: http://www.scalastyle.org
• sbt in Action: http://manning.com/suereth2

Scaladays 2014 introduction to scalatest selenium dsl

  • 1.
    SCALATEST SELENIUM DSL Scaladays 2014 - Berlin
  • 2.
    MATTHEW FARWELL • Over20 years development experience • Project Lead on Scalastyle, style checker for Scala http://www.scalastyle.org • Contributor to various open source projects, notably Scalatest, JUnit, Scala- IDE • Co-author with Josh Suereth of "sbt in Action" http://manning.com/suereth2/ • https://github.com/matthewfarwell/scaladays-2014-selenium
  • 3.
    GOALS • Present theSelenium DSL • Show how it simplifies the writing of selenium tests. • Show how it can be used to test a javascript heavy application • Show how it can be integrated with other tests to make more useful integration tests • The future, well what I would like anyway
  • 4.
    SELENIUM • Selenium automatesbrowsers. • Firefox, Chrome, Internet Explorer, Safari, Opera, HtmlUnit • Selenium has interfaces for Java, Python, Ruby, etc. • We'll be looking at from the point of view of testing • But we could use it for other stuff – automation of administration for example.
  • 5.
    EXAMPLE (JAVA) final WebDriverdriver = new FirefoxDriver(); Wait<WebDriver> wait = new WebDriverWait(driver, 30); driver.get("http://www.google.ch/"); driver.findElement(By.name("q")).sendKeys("Matthew Farwell"); driver.findElement(By.name("btnG")).click(); wait.until(new ExpectedCondition<Boolean>() { public Boolean apply(WebDriver webDriver) { return webDriver.findElement(By.id("resultStats")) != null; }}); boolean b = driver.findElement(By.tagName("body")) .getText().contains("farwell.co.uk"); System.out.println((found ? "You are famous" : "Not famous enough, sorry"));
  • 6.
    EXAMPLE (PYTHON) driver =webdriver.Firefox() driver.get("http://www.google.ch") elem = driver.find_element_by_name("q") elem.send_keys("Farwell") elem.send_keys(Keys.RETURN) assert "farwell.co.uk" in driver.find_by_element_id("body")
  • 7.
    EXAMPLE (SCALA) go to"http://www.google.ch/" textField("q").value = "Farwell" click on "btnG" eventually { val t = find(tagName("body")).get.text } t should include("farwell.co.uk") println("Yay, you are famous") - AmIFamousScala.scala
  • 8.
    OUR APPLICATION • https://github.com/matthewfarwell/scaladays-2014-selenium $sbt > re-start - Run tests from Eclipse $ sbt > re-start > test
  • 9.
    A REAL TEST valbaseUrl = "http://localhost:9100/" "slash" should "redirect to admin index.html" in { go to baseUrl currentUrl should be (baseUrl + "admin/index.html#/users") find("pageTitle").get.text should be("Users") textField("userSearch").value should be('empty) find("userUsername_2").get.text should be("matthew") } - UserSpec1.scala
  • 10.
  • 11.
    ADDING EVENTUALLY (2) eventually{ find("pageTitle").get.text should be("Users") textField("userSearch").value should be('empty) find("userUsername_2").get.text should be("matthew") } - UserSpec2.scala
  • 12.
    GETTING / SETTINGVALUES go to (baseUrl + "admin/index.html#/users") eventually { click on "userEdit_2" } eventually { val tf = textField("userFullname") tf.value should be("Matthew Farwell") tf.isEnabled should be (true) } textField("userFullname").value = "Matthew Farwell is great" - UserSpec3.scala
  • 13.
    GETTING / SETTINGVALUES pwdField("password").value = "hello world" pwdField("confirmPassword").value = "hello world" find("error").get.text should be("Passwords do not match") - UserSpec3.scala
  • 14.
    DEBUGGING capture to "foo.png" println(currentUrl) println(pageSource) withScreenshot{ // some stuff // if ModifiableMessage is thrown, screenshot is taken }
  • 15.
    DEBUGGING - REPORTERS eventmatch { case e: TestFailed => { val name = e.testName web.captureTo(s"${name}.png") println(web.pageSource) } } - Common.scala
  • 16.
    OTHER INPUT TYPES •textField • pwdField • checkBox • radioButton • singleSel, multiSel • HTML5 - emailField, colorField, dateField, dateTimeField, dateTimeLocalField, monthField, numberField, rangeField, searchField, telField, timeField, urlField, weekField
  • 17.
    XPATH LOOKUPS "list" should"default to 10 items" in { go to (baseUrl + "admin/index.html#/users") eventually { val xp = "//tr//td[starts-with(@id, 'userUsername_')]" val tds = findAll(xpath(xp)).map(_.text).toList tds.size should be(10) } } - UserSpec5.scala
  • 18.
    PAGE OBJECT PATTERN classHomePage extends Page { val url = "localhost:9100/index.html" } val homePage = new HomePage go to homePage
  • 19.
    COOKIES add cookie ("cookie_name","cookie_value") cookie("cookie_name").value should be ("cookie_value") delete cookie "cookie_name"
  • 20.
    ALERTS, FRAMES, WINDOWS switchto alert switch to frame(0) // switch by index switch to frame("name") // switch by name switch to window(windowHandle) switch to activeElement switch to defaultContent
  • 21.
  • 22.
    JAVASCRIPT go to (host+ "index.html") val result1 = executeScript("return document.title;") result1 should be ("Test Title") val result2 = executeScript("return 'Hello ' + arguments[0]", "ScalaTest") result2 should be ("Hello ScalaTest")
  • 23.
    INTEGRATION TESTING // createa new user click on "newUser" textField("username").value = "user" // etc. click on "submit" // how do we test that the user has been created? - UserSpec4.scala
  • 24.
    INTEGRATION • A lotof the time, we would like to assert something which isn't available through the HTML front end
  • 25.
    INTEGRATION TESTING eventually { valafter = Services.listUsers().unmarshall after.size should be(before.size + 1) val u = after.find(_.username == "user").get u.fullName should be(Some("Mr. User")) u.id should not be (None) } - UserSpec4.scala
  • 26.
    MULTI-BROWSER TESTING abstract classUserSpecBase with Driver { // tests here } class UserSpecChrome extends UserSpecBase with Chrome class UserSpecFirefox extends UserSpecBase with Firefox class UserSpecSafari extends UserSpecBase with Safari - UserSpec6.scala
  • 27.
    OUR CONTEXT • Lotsof legacy code, partially tested with selenium in Java / Python. • Lots of new code, which has been tested with Scalatest selenium DSL • A simple click on a web page can have effects way beyond CRUD • Selenium used for testing pure HTML, GWT, Angular JS applications
  • 28.
    OUR EXPERIENCE • DSLis good for walking through a test – clearer and more readable than the Java equivalent • We have tests written by "developers" and "testers" • Consistent tests – we use Scalatest for unit tests, UI tests and integration tests. • Selenium DSL is integrated into Scalatest • Always have ids on elements • Our integration tests work with heterogeneous actions in the same test: • we can perform an action in the Browser, • then check call rest service, • then check the database, • call a command via ssh on a remote machine.
  • 29.
    WHAT WE'D LIKETO IMPROVE IMPLICIT EVENTUALLY eventually { click on "userEdit_2" } eventually { textField("foo").get.value should be ("bar") } eventually { click on "btn" } eventually { textField("baz").get.value should be ("flobble") }
  • 30.
    WHAT WE'D LIKETO IMPROVE SLOWING DOWN eventually { click on "userEdit_2" } eventually { textField("foo").get.value should be ("bar") } eventually { click on "userEdit_2" } eventually { textField("foo").get.value should be ("bar") }
  • 31.
    ME AGAIN • MatthewFarwell • Twitter: @matthewfarwell • Blog: http://randomallsorts.blogspot.ch/ • Scalastyle: http://www.scalastyle.org • sbt in Action: http://manning.com/suereth2

Editor's Notes

  • #7 No loop – just an example
  • #10 Find returns an Option