All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalatest.selenium.WebBrowser.scala Maven / Gradle / Ivy

/*
 * Copyright 2001-2012 Artima, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.scalatest.selenium

import org.openqa.selenium.WebDriver
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.firefox.FirefoxProfile
import org.openqa.selenium.safari.SafariDriver
import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.ie.InternetExplorerDriver
import org.openqa.selenium.htmlunit.HtmlUnitDriver
import org.openqa.selenium.By
import org.openqa.selenium.WebElement
import java.util.concurrent.TimeUnit
import org.openqa.selenium.support.ui.WebDriverWait
import org.openqa.selenium.support.ui.Clock
import org.openqa.selenium.support.ui.Sleeper
import org.openqa.selenium.support.ui.ExpectedCondition
import scala.collection.mutable.Buffer
import scala.collection.JavaConversions._
import org.openqa.selenium.Cookie
import java.util.Date
import org.scalatest.time.Span
import org.scalatest.time.Milliseconds
import org.openqa.selenium.TakesScreenshot
import org.openqa.selenium.OutputType
import java.io.File
import java.io.FileOutputStream
import java.io.FileInputStream
import org.openqa.selenium.Alert
import org.openqa.selenium.support.ui.Select
import org.scalatest.exceptions.TestFailedException
import org.scalatest.exceptions.StackDepthException
import org.openqa.selenium.JavascriptExecutor
import org.scalatest.ScreenshotCapturer

/**
 * Trait that provides a domain specific language (DSL) for writing browser-based tests using Selenium.  
 *
 * To use ScalaTest's Selenium DSL, mix trait WebBrowser into your test class. This trait provides the DSL in its
 * entirety except for one missing piece: an implicit org.openqa.selenium.WebDriver. One way to provide the missing
 * implicit driver is to declare one as a member of your test class, like this:
 * 
 * 
 * class BlogSpec extends FlatSpec with ShouldMatchers with WebBrowser {
 *
 *   implicit val webDriver: WebDriver = new HtmlUnitDriver
 *
 *   "The blog app home page" should "have the correct title" in {
 *     go to (host + "index.html")
 *     title should be ("Awesome Blog")
 *   }
 * }
 * 
* *

* For convenience, however, ScalaTest provides a WebBrowser subtrait containing an implicit WebDriver for each * driver provided by Selenium. * Thus a simpler way to use the HtmlUnit driver, for example, is to extend * ScalaTest's HtmlUnit trait, like this: *

* *
 * class BlogSpec extends FlatSpec with ShouldMatchers with HtmlUnit {
 *
 *   "The blog app home page" should "have the correct title" in {
 *     go to (host + "index.html")
 *     title should be ("Awesome Blog")
 *   }
 * }
 * 
* *

* The web driver traits provided by ScalaTest are: *

* * * * * * * * * * * * * * * * * * * * * * * *
DriverWebBrowser subtrait
* Google Chrome * * Chrome *
* Mozilla Firefox * * Firefox *
* HtmlUnit * * HtmlUnit *
* Microsoft Internet Explorer * * InternetExplorer *
* Apple Safari * * Safari *
* *

Navigation

* *

* You can ask the browser to retrieve a page (go to a URL) like this: *

* *
 * go to "http://www.artima.com"
 * 
* *

* Note: If you are using the page object pattern, you can also go to a page using the Page instance, as * illustrated in the section on page objects below. *

* *

* Once you have retrieved a page, you can fill in and submit forms, query for the values of page elements, and make assertions. * In the following example, selenium will go to http://www.google.com, fill in the text box with * Cheese!, press the submit button, and wait for result returned from an AJAX call: *

* *
 * go to "http://www.google.com"
 * click on "q"
 * textField("q").value = "Cheese!"
 * submit()
 * // Google's search is rendered dynamically with JavaScript.
 * eventually { title should be ("Cheese! - Google Search") }
 * 
* *

* In the above example, the "q" used in “click on "q"” and “textField("q")” * can be either the id or name of an element. ScalaTest's Selenium DSL will try to lookup by id first. If it cannot find * any element with an id equal to "q", it will then try lookup by name "q". *

* *

* Alternatively, you can be more specific: *

* *
 * click on id("q")   // to lookup by id "q" 
 * click on name("q") // to lookup by name "q" 
 * 
* *

* In addition to id and name, you can use the following approaches to lookup elements, just as you can do with * Selenium's org.openqa.selenium.By class: *

* *
    *
  • xpath
  • *
  • className
  • *
  • cssSelector
  • *
  • linkText
  • *
  • partialLinkText
  • *
  • tagName
  • *
* *

* For example, you can select by link text with: *

* *
 * click on linkText("click here!")
 * 
* *

* If an element is not found via any form of lookup, evaluation will complete abruptly with a TestFailedException. *

* *

Getting and setting input element values

* *

* ScalaTest's Selenium DSL provides a clear, simple syntax for accessing and updating the values of input elements such as * text fields, radio buttons, checkboxes, and selection lists. If a requested element is not found, or if it is found but is * not of the requested type, an exception will immediately result causing the test to fail. *

* *

Text fields and text areas

* *

* You can change a text field's value by assigning it via the = operator, like this: *

* *
 * textField("q").value = "Cheese!"
 * 
* *

* And you can access a text field's value by simply invoking value on it: *

* *
 * textField("q").value should be ("Cheese!")
 * 
* *

* If the text field is empty, value will return an empty string (""). *

* *

* You can use the same syntax with text areas by replacing textField with textArea, as in: *

* *
 * textArea("body").value = "I saw something cool today!"
 * textArea("body").value should be ("I saw something cool today!")
 * 
* *

Radio buttons

* *

* Radio buttons work together in groups. For example, you could have a group of radio buttons, like this: *

* *
 * <input type="radio" name="group1" value="Option 1"> Option 1<input>
 * <input type="radio" name="group1" value="Option 2"> Option 2<input>
 * <input type="radio" name="group1" value="Option 3"> Option 3<input>
 * 
* *

* You can select an option in either of two ways: *

* *
 * radioButton("group1").value = "Option 2"
 * radioButton("group1").selection = Some("Option 2")
 * 
* *

* Likewise, you can read the currently selected value of a group of radio buttons in two ways: *

* *
 * radioButton("group1").value should be ("Option 2")
 * radioButton("group1").selection should be (Some("Option 2"))
 * 
* *

* If the radio button has no selection at all, selection will return None whereas value * will throw a TestFailedException. By using value, you are indicating you expect a selection, and if there * isn't a selection that should result in a failed test. *

* *

Checkboxes

* *

* A checkbox in one of two states: selected or cleared. Here's how you select a checkbox: *

* *
 * checkbox("cbx1").select()
 * 
* *

* And here's how you'd clear one: *

* *
 * checkbox("cbx1").clear()
 * 
* *

* You can access the current state of a checkbox with isSelected: *

* *
 * checkbox("cbx1").isSelected should be (true)
 * 
* *

Single-selection dropdown lists

* *

* Given the following single-selection dropdown list: *

* *
 * <select id="select1">
 *  <option value="option1">Option 1</option>
 *  <option value="option2">Option 2</option>
 *  <option value="option3">Option 3</option>
 * </select>
 * 
* *

* You could select Option 2 in either of two ways: *

* *
 * singleSel("select1").value = "option2"
 * singleSel("select1").selection = Some("option2")
 * 
* *

* To clear the selection, either invoke clear or set selection to None: *

* *
 * singleSel.clear()
 * singleSel("select1").selection = None
 * 
* *

* You can read the currently selected value of a single-selection list in the same manner as radio buttons: *

* *
 * singleSel("select1").value should be ("option2")
 * singleSel("select1").selection should be (Some("option2"))
 * 
* *

* If the single-selection list has no selection at all, selection will return None whereas value * will throw a TestFailedException. By using value, you are indicating you expect a selection, and if there * isn't a selection that should result in a failed test. *

* *

Multiple-selection lists

* *

* Given the following multiple-selection list: *

* *
 * <select name="select2" multiple="multiple">
 *  <option value="option4">Option 4</option>
 *  <option value="option5">Option 5</option>
 *  <option value="option6">Option 6</option>
 * </select>
 * 
* *

* You could select Option 5 and Option 6 like this: *

* *
 * multiSel("select2").values = Seq("option5", "option6")
 * 
* *

* The previous command would essentially clear all selections first, then select Option 5 and Option 6. * If instead you want to not clear any existing selection, just additionally select Option 5 and Option 6, * you can use the += operator, like this. *

* *
 * multiSel("select2").values += "option5"
 * multiSel("select2").values += "option6"
 * 
* *

* To clear a specific option, pass its name to clear: *

* *
 * selectList("select2").clear("option5")
 * 
* *

* To clear all selections, call clearAll: *

* *
 * selectList("select2").clearAll()
 * 
* *

* You can access the current selections with values, which returns an IndexedSeq[String]: *

* *
 * multiSel("select2").values should have size 2
 * multiSel("select2").values(0) should be ("option5")
 * multiSel("select2").values(1) should be ("option6")
 * 
* *

Clicking and submitting

* *

* You can click on any element with “click on” as shown previously: *

* *
 * click on "aButton"
 * click on name("aTextField")
 * 
* *

* If the requested element is not found, click on will throw an exception, failing the test. *

* *
 * 
* *

* Clicking on a input element will give it the,focus. If current focus is in on an input element within a form, you can submit the form by * calling submit: *

* *
 * submit()
 * 
* *

Switching

* *

* You can switch to a popup alert using the following code: *

* *
 * switch to alert
 * 
* *

* to switch to a frame, you could: *

* *
 * switch to frame(0) // switch by index
 * switch to frame("name") // switch by name
 * 
* *

* If you have reference to a window handle (can be obtained from calling windowHandle/windowHandles), you can switch to a particular * window by: *

* *
 * switch to window(windowHandle)
 * 
* *

* Similar to what you got in Selenium, you can also switch to active element and default content: *

* *
 * switch to activeElement
 * switch to defaultContent
 * 
* *

Navigation history

* *

* In real web browser, you can press the 'Back' button to go back to previous page. To emulate that action in your test, you can call goBack: *

* *
 * goBack()
 * 
* *

* To emulate the 'Forward' button, you can call: *

* *
 * goForward()
 * 
* * And to refresh or reload the current page, you can call: * *
 * reloadPage()
 * 
* *

Cookies!

* *

To create a new cookie, you'll say:

* *
 * add cookie ("cookie_name", "cookie_value")
 * 
* *

* to read a cookie value, you do: *

* *
 * cookie("cookie_name").value should be ("cookie_value") // If value is undefined, throws TFE right then and there. Never returns null.
 * 
* *

* In addition to the common use of name-value cookie, you can pass these extra fields when creating the cookie, available ways are: *

* *
 * cookie(name: String, value: String)
 * cookie(name: String, value: String, path: String)
 * cookie(name: String, value: String, path: String, expiry: Date)
 * cookie(name: String, value: String, domain: String, path: String, expiry: Date)
 * cookie(name: String, value: String, domain: String, path: String, expiry: Date, secure: Boolean)
 * 
* * and to read those extra fields: * *
 * cookie("cookie_name").value   // Read cookie's value
 * cookie("cookie_name").path    // Read cookie's path
 * cookie("cookie_name").expiry  // Read cookie's expiry
 * cookie("cookie_name").domain  // Read cookie's domain
 * cookie("cookie_name").isSecure  // Read cookie's isSecure flag
 * 
* *

* In order to delete a cookie, you could use the following code: *

* *
 * delete cookie "cookie_name"
 * 
* *

* or to delete all cookies in the same domain:- *

* *
 * delete all cookies
 * 
* *

Implicit wait

* *

* To set the implicit wait, you can call implicitlyWait method: *

* *
 * implicitlyWait(Span(10, Seconds))
 * 
* *

Page source and current URL

* *

* It is possible to get the html source of currently loaded page, using: *

* *
 * pageSource
 * 
* *

* and if needed, get the current URL of currently loaded page: *

* *
 * currentUrl
 * 
* *

Screen capture

* *

* You can capture screen using the following code: *

* *
 * val file = capture
 * 
* *

* By default, the captured image file will be saved in temporary folder (returned by java.io.tmpdir property), with random file name * ends with .png extension. You can specify a fixed file name: *

* *
 * capture to "MyScreenShot.png"
 * 
* *

* or *

* *
 * capture to "MyScreenShot"
 * 
* *

* Both will result in a same file name MyScreenShot.png. *

* *

* You can also change the target folder screenshot file is written to, by saying: *

* *
 * capture set "/home/your_name/screenshots"
 * 
* *

Using the page object pattern

* *

* If you use the page object pattern, mixing trait Page into your page classes will allow you to use the go to * syntax with your page objects. Here's an example: *

* *
 * class HomePage extends Page {
 *   val url = "localhost:9000/index.html"
 * }
 *
 * val homePage = new HomePage
 * go to homePage
 * 
* *

Executing JavaScript

* *

* To execute arbitrary JavaScript, for example, to test some JavaScript functions on your page, pass it to executeScript: *

* *
 * 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")
 * 
* *

* To execute an asynchronous bit of JavaScript, pass it to executeAsyncScript. You can set the script timeout with setScriptTimeout: *

* *
 * val script = """
 *   var callback = arguments[arguments.length - 1];
 *   window.setTimeout(function() {callback('Hello ScalaTest')}, 500);
 * """
 * setScriptTimeout(1 second)
 * val result = executeAsyncScript(script)
 * result should be ("Hello ScalaTest")
 * 
* *

Querying for elements

* *

* You can query for arbitrary elements via find and findAll. The find method returns the first matching element, wrapped in a Some, * or None if no element is found. The findAll method returns an IndexedSeq of all matching elements. If no elements match the query, findAll * returns an empty IndexedSeq. These methods allow you to perform rich queries using for expressions. Here are some examples: *

* *
 * val ele: Option[Element] = find("q")
 *
 * val eles: IndexedSeq[Element] = findAll(className("small"))
 * for (e <- eles; if e.tagName != "input")
 *   e should be ('displayed)
 * val textFields = eles filter { tf.isInstanceOf[TextField] }
 * 
* *

Cleaning up

* *

* To close the current browser window, and exit the driver if the current window was the only one remaining, use close: *

* *
 * close()
 * 
* *

* To close all windows, and exit the driver, use quit: *

* *
 * quit()
 * 
* * @author Chua Chee Seng * @author Bill Venners */ trait WebBrowser { case class Point(x: Int, y: Int) case class Dimension(width: Int, height: Int) trait Element { def location: Point = Point(underlying.getLocation.getX, underlying.getLocation.getY) def size: Dimension = Dimension(underlying.getSize.getWidth, underlying.getSize.getHeight) def isDisplayed: Boolean = underlying.isDisplayed def isEnabled: Boolean = underlying.isEnabled def isSelected: Boolean = underlying.isSelected def tagName: String = underlying.getTagName def underlying: WebElement } trait Page { val url: String } // fluentLinium has a doubleClick. Wonder how they are doing that? class CookieWrapper(cookie: Cookie) extends Cookie(cookie.getName, cookie.getValue, cookie.getDomain, cookie.getPath, cookie.getExpiry, cookie.isSecure) { override def equals(o: Any): Boolean = cookie.equals(o) def domain: String = cookie.getDomain def expiry: Date = cookie.getExpiry def name: String = cookie.getName def path: String = cookie.getPath def value: String = cookie.getValue override def hashCode: Int = cookie.hashCode def secure: Boolean = cookie.isSecure override def toString: String = cookie.toString } class CookiesNoun sealed abstract class SwitchTarget[T] { def switch(driver: WebDriver): T } final class ActiveElementTarget extends SwitchTarget[WebElement] { def switch(driver: WebDriver): WebElement = { driver.switchTo.activeElement } } final class AlertTarget extends SwitchTarget[Alert] { def switch(driver: WebDriver): Alert = { driver.switchTo.alert } } final class DefaultContentTarget extends SwitchTarget[WebDriver] { def switch(driver: WebDriver): WebDriver = { driver.switchTo.defaultContent } } final class FrameIndexTarget(index: Int) extends SwitchTarget[WebDriver] { def switch(driver: WebDriver): WebDriver = try { driver.switchTo.frame(index) } catch { case e: org.openqa.selenium.NoSuchFrameException => throw new TestFailedException( sde => Some("Frame at index '" + index + "' not found."), None, getStackDepthFun("WebBrowser.scala", "switch", 1) ) } } final class FrameNameOrIdTarget(nameOrId: String) extends SwitchTarget[WebDriver] { def switch(driver: WebDriver): WebDriver = try { driver.switchTo.frame(nameOrId) } catch { case e: org.openqa.selenium.NoSuchFrameException => throw new TestFailedException( sde => Some("Frame with name or ID '" + nameOrId + "' not found."), None, getStackDepthFun("WebBrowser.scala", "switch", 1) ) } } final class FrameWebElementTarget(element: WebElement) extends SwitchTarget[WebDriver] { def switch(driver: WebDriver): WebDriver = try { driver.switchTo.frame(element) } catch { case e: org.openqa.selenium.NoSuchFrameException => throw new TestFailedException( sde => Some("Frame element '" + element + "' not found."), None, getStackDepthFun("WebBrowser.scala", "switch", 1) ) } } final class WindowTarget(nameOrHandle: String) extends SwitchTarget[WebDriver] { def switch(driver: WebDriver): WebDriver = try { driver.switchTo.window(nameOrHandle) } catch { case e: org.openqa.selenium.NoSuchWindowException => throw new TestFailedException( sde => Some("Window with nameOrHandle '" + nameOrHandle + "' not found."), None, getStackDepthFun("WebBrowser.scala", "switch", 1) ) } } private def isTextField(webElement: WebElement): Boolean = webElement.getTagName.toLowerCase == "input" && webElement.getAttribute("type").toLowerCase == "text" private def isTextArea(webElement: WebElement): Boolean = webElement.getTagName.toLowerCase == "textarea" private def isCheckBox(webElement: WebElement): Boolean = webElement.getTagName.toLowerCase == "input" && webElement.getAttribute("type").toLowerCase == "checkbox" private def isRadioButton(webElement: WebElement): Boolean = webElement.getTagName == "input" && webElement.getAttribute("type") == "radio" final class TextField(webElement: WebElement) extends Element { if(!isTextField(webElement)) throw new TestFailedException( sde => Some("Element " + webElement + " is not text field."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) def value: String = webElement.getAttribute("value") def value_=(value: String) { webElement.clear() webElement.sendKeys(value) } def text: String = webElement.getText def attribute(name: String): String = webElement.getAttribute(name) def underlying: WebElement = webElement } final class TextArea(webElement: WebElement) extends Element { if(!isTextArea(webElement)) throw new TestFailedException( sde => Some("Element " + webElement + " is not text area."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) def value: String = webElement.getAttribute("value") def value_=(value: String) { webElement.clear() webElement.sendKeys(value) } def text: String = webElement.getText def attribute(name: String): String = webElement.getAttribute(name) def underlying: WebElement = webElement } final class RadioButton(webElement: WebElement) extends Element { if(!isRadioButton(webElement)) throw new TestFailedException( sde => Some("Element " + webElement + " is not radio button."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) def value: String = webElement.getAttribute("value") def underlying: WebElement = webElement } final class RadioButtonGroup(groupName: String, driver: WebDriver) { private def groupElements = driver.findElements(By.name(groupName)).toList.filter(isRadioButton(_)) if (groupElements.length == 0) throw new TestFailedException( sde => Some("Radio Buttons with group name '" + groupName + "' not found."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) def value: String = selection match { case Some(v) => v case None => throw new TestFailedException( sde => Some("The Option on which value was invoked was not defined."), None, getStackDepthFun("WebBrowser.scala", "value", 1) ) } def selection: Option[String] = { groupElements.find(_.isSelected) match { case Some(radio) => Some(radio.getAttribute("value")) case None => None } } def value_=(value: String) { groupElements.find(_.getAttribute("value") == value) match { case Some(radio) => radio.click() case None => throw new org.openqa.selenium.NoSuchElementException("Radio button value '" + value + "' not found for group '" + groupName + "'.") } } } final class Checkbox(webElement: WebElement) extends Element { if(!isCheckBox(webElement)) throw new TestFailedException( sde => Some("Element " + webElement + " is not check box."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) def select() { if (!webElement.isSelected) webElement.click() } def clear() { if (webElement.isSelected()) webElement.click() } def value: String = webElement.getAttribute("value") def underlying: WebElement = webElement } class RichIndexedSeq(seq: Seq[String]) { def +(value: String): Seq[String] = seq :+ value def -(value: String): Seq[String] = seq.filter(_ != value) } implicit def vector2RichIndexedSeq(seq: Seq[String]): RichIndexedSeq = new RichIndexedSeq(seq) // Should never return null. class SingleSel(webElement: WebElement) extends Element { if(webElement.getTagName.toLowerCase != "select") throw new TestFailedException( sde => Some("Element " + webElement + " is not select."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) private val select = new Select(webElement) if (select.isMultiple) throw new TestFailedException( sde => Some("Element " + webElement + " is not a single-selection list."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) def selection = { val first = select.getFirstSelectedOption if (first == null) None else Some(first.getAttribute("value")) } def value: String = selection match { case Some(v) => v case None => throw new TestFailedException( sde => Some("The Option on which value was invoked was not defined."), None, getStackDepthFun("WebBrowser.scala", "value", 1) ) } def value_=(value : String) { try { select.selectByValue(value) } catch { case e: org.openqa.selenium.NoSuchElementException => throw new TestFailedException( sde => Some(e.getMessage), None, getStackDepthFun("WebBrowser.scala", "value_=", 1) ) } } def underlying: WebElement = webElement } class MultiSel(webElement: WebElement) extends Element { if(webElement.getTagName.toLowerCase != "select") throw new TestFailedException( sde => Some("Element " + webElement + " is not select."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) private val select = new Select(webElement) if (!select.isMultiple) throw new TestFailedException( sde => Some("Element " + webElement + " is not a multi-selection list."), None, getStackDepthFun("WebBrowser.scala", "this", 1) ) def clear(value: String) { select.deselectByValue(value) } def selections: Option[Seq[String]] = { val elementSeq = select.getAllSelectedOptions.toIndexedSeq val valueSeq = elementSeq.map(_.getAttribute("value")) if (valueSeq.length > 0) Some(valueSeq) else None } def values: Seq[String] = selections match { case Some(v) => v case None => IndexedSeq.empty } def values_=(values: Seq[String]) { try { clearAll() values.foreach(select.selectByValue(_)) } catch { case e: org.openqa.selenium.NoSuchElementException => throw new TestFailedException( sde => Some(e.getMessage), None, getStackDepthFun("WebBrowser.scala", "value_=", 1) ) } } def clearAll() { select.deselectAll() } def underlying: WebElement = webElement } object go { def to(url: String)(implicit driver: WebDriver) { driver.get(url) } def to(page: Page)(implicit driver: WebDriver) { driver.get(page.url) } } def close()(implicit driver: WebDriver) { driver.close() } def title(implicit driver: WebDriver): String = driver.getTitle def pageSource(implicit driver: WebDriver): String = driver.getPageSource def currentUrl(implicit driver: WebDriver): String = driver.getCurrentUrl sealed trait Query { val by: By val queryString: String def getWebElement(implicit driver: WebDriver): WebElement = { findWebElement(driver) match { case Some(element) => element case None => throw new TestFailedException( sde => Some("Element '" + queryString + "' not found."), None, getStackDepthFun("WebBrowser.scala", "name", 1) ) } } def findWebElement(implicit driver: WebDriver): Option[WebElement] = { try { Some(driver.findElement(by)) } catch { case e: org.openqa.selenium.NoSuchElementException => None } } def findAllWebElements(implicit driver: WebDriver): Seq[WebElement] = driver.findElements(by).toSeq } case class IdQuery(queryString: String) extends Query { val by = By.id(queryString)} case class NameQuery(queryString: String) extends Query { val by = By.name(queryString) } case class XPathQuery(queryString: String) extends Query { val by = By.xpath(queryString) } case class ClassNameQuery(queryString: String) extends Query { val by = By.className(queryString) } case class CssSelectorQuery(queryString: String) extends Query { val by = By.cssSelector(queryString) } case class LinkTextQuery(queryString: String) extends Query { val by = By.linkText(queryString) } case class PartialLinkTextQuery(queryString: String) extends Query { val by = By.partialLinkText(queryString) } case class TagNameQuery(queryString: String) extends Query { val by = By.tagName(queryString) } def id(elementId: String): IdQuery = new IdQuery(elementId) def name(elementName: String): NameQuery = new NameQuery(elementName) def xpath(xpath: String): XPathQuery = new XPathQuery(xpath) def className(className: String): ClassNameQuery = new ClassNameQuery(className) def cssSelector(cssSelector: String): CssSelectorQuery = new CssSelectorQuery(cssSelector) def linkText(linkText: String): LinkTextQuery = new LinkTextQuery(linkText) def partialLinkText(partialLinkText: String): PartialLinkTextQuery = new PartialLinkTextQuery(partialLinkText) def tagName(tagName: String): TagNameQuery = new TagNameQuery(tagName) private def createTypedElement(element: WebElement): Element = { if (isTextField(element)) new TextField(element) else if (isTextArea(element)) new TextArea(element) else if (isCheckBox(element)) new Checkbox(element) else if (isRadioButton(element)) new RadioButton(element) else if (element.getTagName.toLowerCase == "select") { val select = new Select(element) if (select.isMultiple) new MultiSel(element) else new SingleSel(element) } else new Element() { def underlying = element } } def find(query: Query)(implicit driver: WebDriver): Option[Element] = query.findWebElement match { case Some(webElement) => Some(createTypedElement(webElement)) case None => None } def find(queryString: String)(implicit driver: WebDriver): Option[Element] = new IdQuery(queryString).findWebElement match { case Some(webElement) => Some(createTypedElement(webElement)) case None => new NameQuery(queryString).findWebElement match { case Some(webElement) => Some(createTypedElement(webElement)) case None => None } } def findAll(query: Query)(implicit driver: WebDriver): Seq[Element] = query.findAllWebElements.map { e => createTypedElement(e) } def findAll(queryString: String)(implicit driver: WebDriver): Seq[Element] = { val byIdSeq = new IdQuery(queryString).findAllWebElements if (byIdSeq.size > 0) byIdSeq.map { e => createTypedElement(e) } else new NameQuery(queryString).findAllWebElements.map { e => createTypedElement(e) } } def textField(query: Query)(implicit driver: WebDriver) = new TextField(query.getWebElement) def textField(queryString: String)(implicit driver: WebDriver): TextField = try { new TextField(new IdQuery(queryString).getWebElement) } catch { case _ => new TextField(new NameQuery(queryString).getWebElement) } def textArea(query: Query)(implicit driver: WebDriver) = new TextArea(query.getWebElement) def textArea(queryString: String)(implicit driver: WebDriver): TextArea = try { new TextArea(new IdQuery(queryString).getWebElement) } catch { case _ => new TextArea(new NameQuery(queryString).getWebElement) } def radioButtonGroup(groupName: String)(implicit driver: WebDriver) = new RadioButtonGroup(groupName, driver) def radioButton(query: Query)(implicit driver: WebDriver) = new RadioButton(query.getWebElement) def radioButton(queryString: String)(implicit driver: WebDriver): RadioButton = try { new RadioButton(new IdQuery(queryString).getWebElement) } catch { case _ => new RadioButton(new NameQuery(queryString).getWebElement) } def checkbox(query: Query)(implicit driver: WebDriver) = new Checkbox(query.getWebElement) def checkbox(queryString: String)(implicit driver: WebDriver): Checkbox = try { new Checkbox(new IdQuery(queryString).getWebElement) } catch { case _ => new Checkbox(new NameQuery(queryString).getWebElement) } def singleSel(query: Query)(implicit driver: WebDriver) = new SingleSel(query.getWebElement) def singleSel(queryString: String)(implicit driver: WebDriver): SingleSel = try { new SingleSel(new IdQuery(queryString).getWebElement) } catch { case _ => new SingleSel(new NameQuery(queryString).getWebElement) } def multiSel(query: Query)(implicit driver: WebDriver) = new MultiSel(query.getWebElement) def multiSel(queryString: String)(implicit driver: WebDriver): MultiSel = try { new MultiSel(new IdQuery(queryString).getWebElement) } catch { case _ => new MultiSel(new NameQuery(queryString).getWebElement) } def button(webElement: WebElement): WebElement = webElement // enable syntax 'click on aButton', where aButton is a WebElement. def button(queryString: String)(implicit driver: WebDriver): WebElement = try { new IdQuery(queryString).getWebElement } catch { case _ => new NameQuery(queryString).getWebElement } object click { def on(element: WebElement) { element.click() } def on(queryString: String)(implicit driver: WebDriver) { // stack depth is not correct if just call the button("...") directly. val target = try { new IdQuery(queryString).getWebElement } catch { case _ => new NameQuery(queryString).getWebElement } on(target) } } def submit()(implicit driver: WebDriver) { (switch to activeElement).submit() } def implicitlyWait(timeout: Span)(implicit driver: WebDriver) { driver.manage.timeouts.implicitlyWait(timeout.totalNanos, TimeUnit.NANOSECONDS) } def wait[T](timeout: Span, interval: Span = Span(500L, Milliseconds))(f: => T)(implicit driver: WebDriver) { new WebDriverWait(driver, timeout.totalNanos / 1000000000L, interval.totalNanos / 1000000) .until(new ExpectedCondition[T]() { override def apply(driver: WebDriver) = { f } }) } def quit()(implicit driver: WebDriver) { driver.quit() } def windowHandle(implicit driver: WebDriver): String = driver.getWindowHandle def windowHandles(implicit driver: WebDriver): Set[String] = driver.getWindowHandles.toSet object switch { def to[T](target: SwitchTarget[T])(implicit driver: WebDriver): T = { target.switch(driver) } } val activeElement = new ActiveElementTarget() val alert = new AlertTarget() val defaultContent = new DefaultContentTarget() def frame(index: Int) = new FrameIndexTarget(index) def frame(nameOrId: String) = new FrameNameOrIdTarget(nameOrId) def frame(element: WebElement) = new FrameWebElementTarget(element) def frame(query: Query)(implicit driver: WebDriver) = new FrameWebElementTarget(query.getWebElement) def window(nameOrHandle: String) = new WindowTarget(nameOrHandle) def goBack()(implicit driver: WebDriver) { driver.navigate.back() } def goForward()(implicit driver: WebDriver) { driver.navigate.forward() } def reloadPage()(implicit driver: WebDriver) { driver.navigate.refresh() } // TODO: Maybe use a single cookie method with default param values instead of overloading object add { private def addCookie(cookie: Cookie)(implicit driver: WebDriver) { driver.manage.addCookie(cookie) } def cookie(name: String, value: String)(implicit driver: WebDriver) { addCookie(new Cookie(name, value)) } def cookie(name: String, value: String, path: String)(implicit driver: WebDriver) { addCookie(new Cookie(name, value, path)) } def cookie(name: String, value: String, path: String, expiry: Date)(implicit driver: WebDriver) { addCookie(new Cookie(name, value, path, expiry)) } def cookie(name: String, value: String, domain: String, path: String, expiry: Date)(implicit driver: WebDriver) { addCookie(new Cookie(name, value, domain, path, expiry)) } def cookie(name: String, value: String, domain: String, path: String, expiry: Date, secure: Boolean)(implicit driver: WebDriver) { addCookie(new Cookie(name, value, domain, path, expiry, secure)) } } def cookie(name: String)(implicit driver: WebDriver): CookieWrapper = { getCookie(name) } private def getCookie(name: String)(implicit driver: WebDriver): CookieWrapper = { driver.manage.getCookies.toList.find(_.getName == name) match { case Some(cookie) => new CookieWrapper(cookie) case None => throw new TestFailedException( sde => Some("Cookie '" + name + "' not found."), None, getStackDepthFun("WebBrowser.scala", "getCookie", 1) ) } } object delete { private def deleteCookie(name: String)(implicit driver: WebDriver) { val cookie = getCookie(name) if (cookie == null) throw new org.openqa.selenium.NoSuchElementException("Cookie '" + name + "' not found.") driver.manage.deleteCookie(cookie) } def cookie(name: String)(implicit driver: WebDriver) { deleteCookie(name) } def all(cookies: CookiesNoun)(implicit driver: WebDriver) { driver.manage.deleteAllCookies() } } val cookies = new CookiesNoun def isScreenshotSupported(implicit driver: WebDriver): Boolean = driver.isInstanceOf[TakesScreenshot] object capture { private var targetDir = new File(System.getProperty("java.io.tmpdir")) def set(targetDirPath: String) { targetDir = if (targetDirPath.endsWith(File.separator)) new File(targetDirPath) else new File(targetDirPath + File.separator) if (!targetDir.exists) targetDir.mkdirs() } def to(fileName: String)(implicit driver: WebDriver) { driver match { case takesScreenshot: TakesScreenshot => val tmpFile = takesScreenshot.getScreenshotAs(OutputType.FILE) val outFile = new File(targetDir, if (fileName.toLowerCase.endsWith(".png")) fileName else fileName + ".png") new FileOutputStream(outFile) getChannel() transferFrom( new FileInputStream(tmpFile) getChannel, 0, Long.MaxValue ) case _ => throw new UnsupportedOperationException("Screen capture is not support by " + driver.getClass.getName) } } def apply()(implicit driver: WebDriver): File = { driver match { case takesScreenshot: TakesScreenshot => val tmpFile = takesScreenshot.getScreenshotAs(OutputType.FILE) val fileName = tmpFile.getName val outFile = new File(targetDir, if (fileName.toLowerCase.endsWith(".png")) fileName else fileName + ".png") new FileOutputStream(outFile) getChannel() transferFrom( new FileInputStream(tmpFile) getChannel, 0, Long.MaxValue ) outFile case _ => throw new UnsupportedOperationException("Screen capture is not support by " + driver.getClass.getName) } } } def withScreenshot(fun: => Unit)(implicit driver: WebDriver) { try { fun } catch { case e: org.scalatest.exceptions.ModifiableMessage[_] => throw e.modifyMessage{ (currentMessage: Option[String]) => val captureFile: File = capture.apply() currentMessage match { case Some(currentMsg) => Some(currentMsg + "; screenshot captured in " + captureFile.getAbsolutePath) case None => Some("screenshot captured in " + captureFile.getAbsolutePath) } } } } /** * Executes JavaScript in the context of the currently selected frame or window. */ def executeScript(script: String, args: AnyRef*)(implicit driver: WebDriver): AnyRef = driver match { case executor: JavascriptExecutor => executor.executeScript(script, args.toArray : _*) case _ => throw new UnsupportedOperationException("Web driver " + driver.getClass.getName + " does not support javascript execution.") } /** * Executes an asynchronous piece of JavaScript in the context of the currently selected frame or window. */ def executeAsyncScript(script: String, args: AnyRef*)(implicit driver: WebDriver): AnyRef = driver match { case executor: JavascriptExecutor => executor.executeAsyncScript(script, args.toArray : _*) case _ => throw new UnsupportedOperationException("Web driver " + driver.getClass.getName + " does not support javascript execution.") } /** * Sets the amount of time to wait for an asynchronous script to finish execution before throwing an exception. */ def setScriptTimeout(timeout: Span)(implicit driver: WebDriver) { driver.manage().timeouts().setScriptTimeout(timeout.totalNanos, TimeUnit.NANOSECONDS); } private def getStackDepthFun(fileName: String, methodName: String, adjustment: Int = 0): (StackDepthException => Int) = { sde => getStackDepth(sde.getStackTrace, fileName, methodName, adjustment) } private def getStackDepth(stackTrace: Array[StackTraceElement], fileName: String, methodName: String, adjustment: Int = 0) = { val stackTraceList = stackTrace.toList val fileNameIsDesiredList: List[Boolean] = for (element <- stackTraceList) yield element.getFileName == fileName // such as "Checkers.scala" val methodNameIsDesiredList: List[Boolean] = for (element <- stackTraceList) yield element.getMethodName == methodName // such as "check" // For element 0, the previous file name was not desired, because there is no previous // one, so you start with false. For element 1, it depends on whether element 0 of the stack trace // had the desired file name, and so forth. val previousFileNameIsDesiredList: List[Boolean] = false :: (fileNameIsDesiredList.dropRight(1)) // Zip these two related lists together. They now have two boolean values together, when both // are true, that's a stack trace element that should be included in the stack depth. val zipped1 = methodNameIsDesiredList zip previousFileNameIsDesiredList val methodNameAndPreviousFileNameAreDesiredList: List[Boolean] = for ((methodNameIsDesired, previousFileNameIsDesired) <- zipped1) yield methodNameIsDesired && previousFileNameIsDesired // Zip the two lists together, that when one or the other is true is an include. val zipped2 = fileNameIsDesiredList zip methodNameAndPreviousFileNameAreDesiredList val includeInStackDepthList: List[Boolean] = for ((fileNameIsDesired, methodNameAndPreviousFileNameAreDesired) <- zipped2) yield fileNameIsDesired || methodNameAndPreviousFileNameAreDesired val includeDepth = includeInStackDepthList.takeWhile(include => include).length val depth = if (includeDepth == 0 && stackTrace(0).getFileName != fileName && stackTrace(0).getMethodName != methodName) stackTraceList.takeWhile(st => st.getFileName != fileName || st.getMethodName != methodName).length else includeDepth depth + adjustment } } /** * Companion object that facilitates the importing of WebBrowser members as * an alternative to mixing it in. One use case is to import WebBrowser members so you can use * them in the Scala interpreter. */ object WebBrowser extends WebBrowser /** * WebBrowser subtrait that defines an implicit WebDriver for HTMLUnit (an org.openqa.selenium.htmlunit.HtmlUnitDriver), with JavaScript * enabled by default. * *

* Note: You can disable JavaScript with: *

* *
 * webDriver.setJavascriptEnabled(false)
 * 
*/ trait HtmlUnit extends WebBrowser with ScreenshotCapturer { /** * WebBrowser subtrait that defines an implicit WebDriver for HTMLUnit (an org.openqa.selenium.htmlunit.HtmlUnitDriver), with JavaScript * enabled by default. * *

* Note: You can disable JavaScript with: *

* *
   * webDriver.setJavascriptEnabled(false)
   * 
*/ implicit val webDriver = new HtmlUnitDriver() webDriver.setJavascriptEnabled(true) /** * Captures a screenshot and saves it as a file in the specified directory. */ def captureScreenshot(directory: String) { capture to directory } } /** * Companion object that facilitates the importing of HtmlUnit members as * an alternative to mixing it in. One use case is to import HtmlUnit members so you can use * them in the Scala interpreter. */ object HtmlUnit extends HtmlUnit /** * WebBrowser subtrait that defines an implicit WebDriver for Firefox (an org.openqa.selenium.firefox.FirefoxDriver). * *

* The FirefoxDriver uses the FirefoxProfile defined as firefoxProfile. By default this is just a new FirefoxProfile. * You can mutate this object to modify the profile, or override firefoxProfile. *

*/ trait Firefox extends WebBrowser with ScreenshotCapturer { /** * The FirefoxProfile passed to the constructor of the FirefoxDriver returned by webDriver. * *

* The FirefoxDriver uses the FirefoxProfile defined as firefoxProfile. By default this is just a new FirefoxProfile. * You can mutate this object to modify the profile, or override firefoxProfile. *

*/ val firefoxProfile = new FirefoxProfile() /** * WebBrowser subtrait that defines an implicit WebDriver for Firefox (an org.openqa.selenium.firefox.FirefoxDriver), with a default * Firefox profile. * *

* The FirefoxDriver uses the FirefoxProfile defined as firefoxProfile. By default this is just a new FirefoxProfile. * You can mutate this object to modify the profile, or override firefoxProfile. *

*/ implicit val webDriver = new FirefoxDriver(firefoxProfile) /** * Captures a screenshot and saves it as a file in the specified directory. */ def captureScreenshot(directory: String) { capture to directory } } /** * Companion object that facilitates the importing of Firefox members as * an alternative to mixing it in. One use case is to import Firefox members so you can use * them in the Scala interpreter. */ object Firefox extends Firefox /** * WebBrowser subtrait that defines an implicit WebDriver for Safari (an org.openqa.selenium.safari.SafariDriver). */ trait Safari extends WebBrowser with ScreenshotCapturer { /** * WebBrowser subtrait that defines an implicit WebDriver for Safari (an org.openqa.selenium.safari.SafariDriver). */ implicit val webDriver = new SafariDriver() /** * Captures a screenshot and saves it as a file in the specified directory. */ def captureScreenshot(directory: String) { capture to directory } } /** * Companion object that facilitates the importing of Safari members as * an alternative to mixing it in. One use case is to import Safari members so you can use * them in the Scala interpreter. */ object Safari extends Safari /** * WebBrowser subtrait that defines an implicit WebDriver for Chrome (an org.openqa.selenium.chrome.ChromeDriver). */ trait Chrome extends WebBrowser with ScreenshotCapturer { /** * WebBrowser subtrait that defines an implicit WebDriver for Chrome (an org.openqa.selenium.chrome.ChromeDriver). */ implicit val webDriver = new ChromeDriver() /** * Captures a screenshot and saves it as a file in the specified directory. */ def captureScreenshot(directory: String) { capture to directory } } /** * Companion object that facilitates the importing of Chrome members as * an alternative to mixing it in. One use case is to import Chrome members so you can use * them in the Scala interpreter. */ object Chrome extends Chrome /** * WebBrowser subtrait that defines an implicit WebDriver for Internet Explorer (an org.openqa.selenium.ie.InternetExplorerDriver). */ trait InternetExplorer extends WebBrowser with ScreenshotCapturer { /** * WebBrowser subtrait that defines an implicit WebDriver for Internet Explorer (an org.openqa.selenium.ie.InternetExplorerDriver). */ implicit val webDriver = new InternetExplorerDriver() /** * Captures a screenshot and saves it as a file in the specified directory. */ def captureScreenshot(directory: String) { capture to directory } } /** * Companion object that facilitates the importing of InternetExplorer members as * an alternative to mixing it in. One use case is to import InternetExplorer members so you can use * them in the Scala interpreter. */ object InternetExplorer extends InternetExplorer /* *

* If you mix in ScreenshotOnFailure, ScalaTest will capture a screenshot and store it to either the system temp directory * or a directory you choose, and send the filename to the report, associated with the failed test. The ScreenshotOnFailure trait requires that it be * mixed into a ScreenshotCapturer, which trait WebBrowser does not extend. To satisfy this * requirement, you can extend one of WebBrowser's subtraits, such as: *

* *
 * class WebAppSpec extends Firefox with ScreenshotOnFailure {
 *   // ...
 * }
 * 
* */




© 2015 - 2025 Weber Informatics LLC | Privacy Policy